public enum GoProMode {
    VIDEO (0, R.string.video, GoProCommand.SET_VIDEO),
    PHOTO (1, R.string.photo, GoProCommand.SET_PHOTO),
    BURST (2, R.string.burst, GoProCommand.SET_BURST),
    TIMELAPSE (3, R.string.timelapse, GoProCommand.SET_TIMELAPSE);

    int index;
    int labelResId;
    GoProCommand command;

    GoProMode(int index, @StringRes int labelResId, GoProCommand command) {
        this.index = index;
        this.labelResId = labelResId;
        this.command = command;
    }

    private int getIndex() {
        return index;
    }

    @StringRes
    public int getLabelResId() {
        return labelResId;
    }

    public GoProCommand getCommand() {
        return command;
    }

    public static GoProMode from(int modeIndex) {
        for (GoProMode mode : GoProMode.values()) {
            if (modeIndex == mode.getIndex()) {
                return mode;
            }
        }
        return VIDEO;
    }
} @AutoValue
public abstract class GoProState {

    public static final GoProState CAMERA_OFF = GoProStateParser.cameraOff();

    private static final int NO_SD_CARD = 255;

    public abstract byte[] rawState();
    public abstract GoProMode currentMode();
    public abstract GoProMode startupMode();
    public abstract int recordingMinutes();
    public abstract int recordingSeconds();
    public abstract int beepVolume();
    public abstract LedState ledState();
    //public abstract boolean previewEnabled();
    public abstract Orientation orientation();
    public abstract int batteryPercent(); // 99 for 99%
    public abstract int photosAvailable();
    public abstract int photoCount();
    public abstract int videoAvailableMinutes();
    public abstract int videoCount();
    public abstract boolean recording();

    public static Builder builder() {
        return new AutoValue_GoProState.Builder();
    }

    @AutoValue.Builder
    public abstract static class Builder {
        public abstract Builder rawState(byte[] rawState);
        public abstract Builder currentMode(GoProMode mode);
        public abstract Builder startupMode(GoProMode mode);
        public abstract Builder recordingMinutes(int minutes);
        public abstract Builder recordingSeconds(int seconds);
        public abstract Builder beepVolume(int volume);
        public abstract Builder ledState(LedState state);
        //public abstract Builder previewEnabled(boolean enabled);
        public abstract Builder orientation(Orientation orientation);
        public abstract Builder batteryPercent(int percent); // 99 for 99%
        public abstract Builder photosAvailable(int photos);
        public abstract Builder photoCount(int count);
        public abstract Builder videoAvailableMinutes(int availableMinutes);
        public abstract Builder videoCount(int count);
        public abstract Builder recording(boolean recording);
        public abstract GoProState build();
    }

    public boolean isPowerOn() {
        return this != CAMERA_OFF;
    }

    // TODO: confirm this or if just high-byte (of 2) must be 255
    public boolean hasSdCard() {
        return photosAvailable() == NO_SD_CARD;
    }

    public String logRawState() {
        return String.format("camera current state: " + Arrays.toString(rawState()));
    }

    public boolean isRecordingCurrently() {
        return recording() && currentMode() != GoProMode.BURST;
    }
} public class GoProStateParser {
    public static GoProState from(byte[] bytes) {
        if (bytes.length > 30) {
            // Camera is On
            return GoProState.builder()
                    .rawState(bytes)
                    .currentMode(GoProMode.values()[bytes[1]])
                    .startupMode(GoProMode.values()[bytes[3]])
                    .recordingMinutes(bytes[13])
                    .recordingSeconds(bytes[14])
                    .beepVolume(bytes[16])
                    .ledState(LedState.values()[bytes[17]])
                    .orientation(Orientation.UP) // TODO: figure out how to get this
                    .batteryPercent(bytes[19])
                    .photosAvailable(toInt(bytes[21], bytes[22]))
                    .photoCount(toInt(bytes[23], bytes[24]))
                    .videoAvailableMinutes(toInt(bytes[25], bytes[26]))
                    .videoCount(toInt(bytes[27], bytes[28]))
                    .recording(bytes[29] > 0)
                    .build();
        } else {
            // Camera is OFF
            return GoProState.CAMERA_OFF;
        }
    }

    public static GoProState cameraOff() {
        return GoProState.builder()
                .rawState(new byte[0])
                .currentMode(GoProMode.VIDEO)
                .startupMode(GoProMode.VIDEO)
                .recordingMinutes(0)
                .recordingSeconds(0)
                .beepVolume(0)
                .ledState(LedState.NONE)
                .orientation(Orientation.UP)
                .batteryPercent(0)
                .photosAvailable(0)
                .photoCount(0)
                .videoAvailableMinutes(0)
                .videoCount(0)
                .recording(false)
                .build();
    }

    static int toInt(byte highByte, byte lowByte) {
        return highByte << 8 | lowByte;
    }
} public class RxUtils {

    private static final Transformer<Object, Object> API_REQUEST_SCHEDULERS_TRANSFORMER = new Transformer<Object, Object>() {
        @Override
        public Observable<Object> call(Observable<Object> observable) {
            return observable
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .unsubscribeOn(Schedulers.io());
        }
    };

    private RxUtils() { }

    @SuppressWarnings("unchecked")
    public static <T> Observable.Transformer<T, T> applyApiRequestSchedulers() {
        return (Observable.Transformer<T, T>) API_REQUEST_SCHEDULERS_TRANSFORMER;
    }

    public static void unsubscribeSafely(@NonNull Subscription... subscriptions) {
        for (Subscription subscription : subscriptions) {
            if (subscription != null && !subscription.isUnsubscribed()) {
                subscription.unsubscribe();
            }
        }
    }
} public final class Apis {

    private static final String GO_PRO_ENDPOINT_URL = "http://10.5.5.9/";

    private static GoProApi goProApi;
    private static OkHttpClient okHttpClient;

    private Apis() { }

    public static GoProApi getGoProApi() {
        if (goProApi == null) {
            goProApi = new Retrofit.Builder()
                    .client(getOkHttpClient())
                    .addConverterFactory(MoshiConverterFactory.create(Parser.getMoshi()))
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .baseUrl(HttpUrl.parse(GO_PRO_ENDPOINT_URL))
                    .build()
                    .create(GoProApi.class);
        }
        return goProApi;
    }

    public static OkHttpClient getOkHttpClient() {
        if (okHttpClient == null) {
            ConfigStorage configStorage = new ConfigStorage(GoProRemoteApp.getInstance());
            PasswordSigningInterceptor signingInterceptor = new PasswordSigningInterceptor(configStorage);

            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.HEADERS : HttpLoggingInterceptor.Level.NONE);

            okHttpClient = new OkHttpClient.Builder()
                    .connectTimeout(5, TimeUnit.SECONDS)
                    .connectionSpecs(Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT))
                    //.cookieJar(CookieHandler.getDefault())
                    .addInterceptor(signingInterceptor)
//                    .addInterceptor(loggingInterceptor)
//                    .addInterceptor(new CurlLoggingInterceptor())
//                    .addNetworkInterceptor(new StethoInterceptor())
                    .build();
        }
        return okHttpClient;
    }

    public static void setOkHttpClient(OkHttpClient okHttpClient) {
        Apis.okHttpClient = okHttpClient;
        Apis.goProApi = null;
        Apis.goProApi = getGoProApi();
    }
} public interface GoProApi {

    @GET("bacpac/PW?p=%01")
    Observable<Response<ResponseBody>> powerOn();

    @GET("bacpac/PW?p=%00")
    Observable<Response<ResponseBody>> powerOff();

    @GET("camera/CM?p=%00")
    Observable<Response<ResponseBody>> setVideoMode();

    @GET("camera/CM?p=%01")
    Observable<Response<ResponseBody>> setPhotoMode();

    @GET("camera/CM?p=%02")
    Observable<Response<ResponseBody>> setBurstMode();

    @GET("camera/CM?p=%03")
    Observable<Response<ResponseBody>> setTimelapseMode();

    @GET("camera/SH?p=%01")
    Observable<Response<ResponseBody>> startRecording();

    @GET("camera/SH?p=%00")
    Observable<Response<ResponseBody>> stopRecording();

    @GET("camera/SH?p=%01")
    Observable<Response<ResponseBody>> takePhoto();

    @GET("camera/se")
    Observable<Response<ResponseBody>> fetchCameraStateWhileOn();

    @GET("bacpac/se")
    Observable<Response<ResponseBody>> fetchCameraStateWhileOff();
} public final class GoProUtils {

    public static final int HTTP_GONE = 410;

    private GoProUtils() { }

    /**
     * Fetches the GoPro camera state. First, we assume that the camera is on.
     * Then, if we get a GONE error response, we try to the camera off request.
     */
    public static Observable<byte[]> fetchCameraState(GoProApi goProApi) {
        return goProApi.fetchCameraStateWhileOn()
                .map(new CameraStateMapper(false))
                .onErrorResumeNext(throwable -> {
                    if (throwable instanceof HttpException) {
                        HttpException exception = (HttpException) throwable;
                        if (exception.code() == HTTP_GONE) {
                            return Observable.just(GoProState.CAMERA_OFF.rawState());
//                            return goProApi.fetchCameraStateWhileOff()
//                                    .map(new CameraStateMapper(false));
                        }
                    }
                    return Observable.error(throwable);
                });
    }

    static class CameraStateMapper implements Func1<Response<ResponseBody>, byte[]> {
        private boolean throwError = false;

        public CameraStateMapper(boolean throwError) {
            this.throwError = throwError;
        }

        @Override
        public byte[] call(Response<ResponseBody> response) {
            Throwable throwable = null;
            byte[] bytes = new byte[0];
            try {
                if (response != null && response.body() != null) {
                    bytes = response.body().bytes();
                }
            } catch (IOException e) {
                throwable = e;
            }
            if (throwable != null || bytes.length == 0) {
                if (throwError) {
                    throw new RuntimeException("Camera state is empty");
                } else {
                    bytes = GoProState.CAMERA_OFF.rawState();
                }
            }
            return bytes;
        }
    }
} public final class WifiUtils {

    private WifiUtils() { }

    public static enum SecurityType { WEP, PSK }

    public static final int WIFI_CONFIG_MAX_PRIORITY = 99999;

    public static boolean isConnectedToGoProWifi(@NonNull Context context) {
        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        if (wifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED) {
            ConfigStorage configStorage = new ConfigStorage(context);
            WifiInfo wifiInfo = wifiManager.getConnectionInfo();
            String ssid = wifiInfo != null ? wifiInfo.getSSID() : null;
            return !TextUtils.isEmpty(ssid) && ssid.replaceAll("\"", "").equals(configStorage.getWifiSsid());
        }
        return false;
    }

    public static Observable<WifiConfiguration> addGoProWifiNetwork(@NonNull Context context) {
        ConfigStorage configStorage = new ConfigStorage(context);
        String ssid = configStorage.getWifiSsid();
        String password = configStorage.getWifiPassword();
        int priority = WIFI_CONFIG_MAX_PRIORITY; // we are controlling the GoPro, so use max so we aren't disconnected
        return addWifiNetwork(context, ssid, password, SecurityType.PSK, priority);
    }

    public static Observable<WifiConfiguration> addWifiNetwork(@NonNull Context context,
            String ssid, String password, SecurityType securityType, int priority) {
        return Observable.defer(() -> {
            try {
                WifiConfiguration wifiConfig = new WifiConfiguration();
                wifiConfig.SSID = String.format("\"%s\"", ssid);
                wifiConfig.preSharedKey = String.format("\"%s\"", password);
                wifiConfig.priority = clamp(priority, 0, WIFI_CONFIG_MAX_PRIORITY);
                setNetworkValidatedFlag(wifiConfig);

                setSecurityModes(wifiConfig, securityType);

                WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
                int networkId = wifiManager.addNetwork(wifiConfig);
                boolean isDisconnected = wifiManager.disconnect();
                boolean isNewNetworkEnabled = wifiManager.enableNetwork(networkId, true);
                boolean isReconnected = wifiManager.reconnect();
                Timber.d("isDisconnected = %b, isNewNetworkEnabled = %b, isReconnected = %b", isDisconnected, isNewNetworkEnabled, isReconnected);

                return Observable.just(wifiConfig);
            } catch (Exception e) {
                return Observable.error(e);
            }
        });
    }

    @TargetApi(Build.VERSION_CODES.M)
    public static void setNetworkValidatedFlag(WifiConfiguration wifiConfig) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            try {
                Class<WifiConfiguration> clazz = WifiConfiguration.class;
                Field validatedInternetAccess = clazz.getDeclaredField("validatedInternetAccess");
                validatedInternetAccess.setAccessible(true);
                validatedInternetAccess.set(wifiConfig, true);

                Field numNoInternetAccessReports = clazz.getDeclaredField("numNoInternetAccessReports");
                numNoInternetAccessReports.setAccessible(true);
                numNoInternetAccessReports.set(wifiConfig, 0);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                Timber.e(e, "Failed to accept unvalidated Wi-Fi network");
            }
        }
    }

    private static void setSecurityModes(WifiConfiguration wifiConfig, SecurityType securityType) {
        switch (securityType) {
            case PSK:
                wifiConfig.status = WifiConfiguration.Status.ENABLED;
                wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
                wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
                wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
                wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
                wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
                wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
                wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
                break;
            case WEP:
                wifiConfig.wepTxKeyIndex = 0;
                wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
                wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
                break;
        }
    }

    public static String ssidFromWifiChangedIntent(Intent intent) {
        WifiInfo wifiInfo = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
        return ssidFromWifiInfo(wifiInfo);
    }

    public static String ssidFromWifiInfo(WifiInfo info) {
        return info != null ? info.getSSID().replaceAll("\"", "") : null;
    }

    private static int clamp(int value, int min, int max) {
        return Math.min(Math.max(value, min), max);
    }
} public class ConfigStorage {

    private static final String KEY_WIFI_SSID = "ssid";
    private static final String KEY_WIFI_PASSWORD = "password";

    private static final String DEFAULT_PASSWORD = "goprohero";

    private SharedPreferences prefs;

    public ConfigStorage(@NonNull Context context) {
        prefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
    }

    public String getWifiSsid() {
        return prefs.getString(KEY_WIFI_SSID, null);
    }

    public boolean saveWifiSsid(String ssid) {
        return prefs.edit()
                .putString(KEY_WIFI_SSID, ssid)
                .commit();
    }

    public String getWifiPassword() {
        return prefs.getString(KEY_WIFI_PASSWORD, null);
    }

    public String getWifiPasswordOrDefault() {
        return prefs.getString(KEY_WIFI_PASSWORD, DEFAULT_PASSWORD);
    }

    public boolean saveWifiPassword(String password) {
        return prefs.edit()
                .putString(KEY_WIFI_PASSWORD, password)
                .commit();
    }
} public static Drawable tintedImage(@DrawableRes int drawableRes, @ColorRes int colorRes) {
    Resources res = GoProRemoteApp.getInstance().getResources();
    Drawable drawable = res.getDrawable(drawableRes).mutate();
    Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
    DrawableCompat.setTint(wrappedDrawable, res.getColor(colorRes));
    return wrappedDrawable;
}
import android.annotation.TargetApi;
import android.content.Context;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.SwitchCompat;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextUtils;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import com.f2prateek.rx.receivers.RxBroadcastReceiver;
import com.github.jbarr21.goproremote.R;
import com.github.jbarr21.goproremote.common.data.GoProMode;
import com.github.jbarr21.goproremote.common.data.GoProState;
import com.github.jbarr21.goproremote.common.utils.GoProStateParser;
import com.github.jbarr21.goproremote.common.utils.RxUtils;
import com.github.jbarr21.goproremote.data.api.Apis;
import com.github.jbarr21.goproremote.data.api.GoProApi;
import com.github.jbarr21.goproremote.data.api.GoProUtils;
import com.github.jbarr21.goproremote.data.network.WifiUtils;
import com.github.jbarr21.goproremote.data.storage.ConfigStorage;
import com.twotoasters.servos.util.butterknife.EnabledSetter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnCheckedChanged;
import butterknife.OnClick;
import butterknife.OnTextChanged;
import retrofit2.adapter.rxjava.HttpException;
import rx.Observable;
import rx.Subscription;
import rx.schedulers.Schedulers;
import timber.log.Timber;
import static com.github.jbarr21.goproremote.ui.ViewUtils.tintedImage;
package com.github.jbarr21.goproremote.ui;






public class HomeActivity extends AppCompatActivity {

    private static final boolean ANIMATE = true;
    private static final boolean DONT_ANIMATE = false;

    @Bind(R.id.coordinator)             CoordinatorLayout coordinator;
    @Bind(R.id.toolbar)                 Toolbar toolbar;
    @Bind(R.id.notificationSwitch)      SwitchCompat notificationSwitch;
    @Bind(R.id.ssid)                    EditText ssidInput;
    @Bind(R.id.password)                EditText passwordInput;
    @Bind(R.id.mode)                    RadioGroup modeGroup;
    @Bind(R.id.fab)                     FloatingActionButton fab;

    @Bind({ R.id.mode_video, R.id.mode_photo, R.id.mode_burst, R.id.mode_timelapse })
    List<RadioButton> modeButtons;

    private HashMap<Integer, GoProMode> radioButtonToModeMap;

    private GoProNotificationManager notificationManager;
    private ConfigStorage configStorage;
    private Subscription wifiChangedSubscription;
    private GoProApi goProApi;