/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.mozstumbler.client.subactivities; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.os.Environment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.ActionBarActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import org.mozilla.mozstumbler.R; import org.mozilla.mozstumbler.client.ClientPrefs; import org.mozilla.mozstumbler.client.MainApp; import org.mozilla.mozstumbler.client.serialize.GPXFragment; import org.mozilla.mozstumbler.client.serialize.KMLFragment; import org.mozilla.mozstumbler.service.AppGlobals; import org.mozilla.mozstumbler.service.Prefs; import org.mozilla.mozstumbler.service.core.http.IHttpUtil; import org.mozilla.mozstumbler.service.core.logging.ClientLog; import org.mozilla.mozstumbler.service.stumblerthread.motiondetection.LocationChangeSensor; import org.mozilla.mozstumbler.service.stumblerthread.motiondetection.MotionSensor; import org.mozilla.mozstumbler.service.utils.BatteryCheckReceiver; import org.mozilla.mozstumbler.svclocator.ServiceLocator; import org.mozilla.mozstumbler.svclocator.services.log.LoggerUtil; import java.io.File; import java.util.HashMap; import static org.mozilla.mozstumbler.client.ClientDataStorageManager.sdcardArchivePath; public class DeveloperActivity extends ActionBarActivity { private final String LOG_TAG = LoggerUtil.makeLogTag(DeveloperActivity.class); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_developer); if (savedInstanceState == null) { FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); ft.add(R.id.frame0, new GPXFragment()); ft.add(R.id.frame1, new KMLFragment()); ft.add(R.id.frame2, new DeveloperOptions()); ft.commit(); } TextView tv = (TextView) findViewById(R.id.textViewDeveloperTitle); tv.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { final AlertDialog.Builder b = new AlertDialog.Builder(DeveloperActivity.this); final String[] menuList = {"ACRA Crash Test", "Fake no motion", "Fake motion", "Battery Low", "Battery OK"}; b.setTitle("Secret testing.. shhh."); b.setItems(menuList, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { switch (item) { case 0: // Trigger a bad HTTPS POST to see what the TLS stuff does. IHttpUtil httpUtil = (IHttpUtil) ServiceLocator.getInstance().getService(IHttpUtil.class); httpUtil.post("https://location.services.mozilla.com/", new byte[0], new HashMap<String, String>(), false); Object a = null; a.hashCode(); break; case 1: LocationChangeSensor.debugSendLocationUnchanging(); break; case 2: MotionSensor.debugMotionDetected(); break; case 3: int pct = ClientPrefs.getInstance(DeveloperActivity.this).getMinBatteryPercent(); BatteryCheckReceiver.debugSendBattery(pct - 1); break; case 4: BatteryCheckReceiver.debugSendBattery(99); break; } } }); b.create().show(); return true; } }); } // For misc developer options public static class DeveloperOptions extends Fragment { private final String LOG_TAG = LoggerUtil.makeLogTag(DeveloperOptions.class); private View mRootView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mRootView = inflater.inflate(R.layout.fragment_developer_options, container, false); // Setup for any logical group of config options should self contained in their // own methods. This is mostly to help with merges in the event that multiple // source branches update the developer options. setupOfflineGeoToggle(); setupHighPowerMode(); setupSaveJSONLogs(); setupSimulationPreference(); setupLocationChangeSpinners(); setupMinPauseTime(); setupPassiveMode(); return mRootView; } private void checkRestartScanning() { MainApp mainApp = ((MainApp) getActivity().getApplication()); if (mainApp.isScanningOrPaused()) { mainApp.stopScanning(); mainApp.startScanning(); } } private void setupOfflineGeoToggle() { boolean useOfflineGeo = Prefs.getInstance(mRootView.getContext()).useOfflineGeo(); CheckBox button = (CheckBox) mRootView.findViewById(R.id.toggleOfflineGeo); button.setChecked(useOfflineGeo); button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) { // do nothing here as we are just going to query the value // when we trigger an AsyncGeolocate call Prefs.getInstance(mRootView.getContext()).setOfflineGeo(isChecked); } }); } private void setupHighPowerMode() { boolean isHighPowerMode = Prefs.getInstance(mRootView.getContext()).isHighPowerMode(); CheckBox button = (CheckBox) mRootView.findViewById(R.id.toggleHighPowerMode); button.setChecked(isHighPowerMode); button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) { ClientPrefs.getInstance(mRootView.getContext()).setIsHighPowerMode(isChecked); checkRestartScanning(); } }); } private void setupPassiveMode() { boolean isPassive = ClientPrefs.getInstance(mRootView.getContext()).isScanningPassive(); CheckBox button = (CheckBox) mRootView.findViewById(R.id.togglePassiveScanning); button.setChecked(isPassive); button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) { ClientPrefs.getInstance(mRootView.getContext()).setIsScanningPassive(isChecked); checkRestartScanning(); } }); } private void setupMinPauseTime() { ClientPrefs cPrefs = ClientPrefs.getInstance(mRootView.getContext()); final String[] timeArray = {"5 s", "10 s", "20 s", "30 s", "60 s", "120 s"}; final ArrayAdapter<String> timeAdapter = new ArrayAdapter<String>(this.getActivity(), android.R.layout.simple_spinner_item, timeArray); final Spinner timeSpinner = (Spinner) mRootView.findViewById(R.id.spinnerMotionDetectionPauseTimeSeconds); timeSpinner.setAdapter(timeAdapter); final int time = (int) cPrefs.getMotionDetectionMinPauseTime(); timeSpinner.setSelection(findIndexOf(time, timeArray)); timeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View arg1, int position, long id) { String item = parent.getItemAtPosition(position).toString(); int val = Integer.valueOf(item.substring(0, item.indexOf(" "))); Prefs.getInstance(mRootView.getContext()).setMotionDetectionMinPauseTime(val); } @Override public void onNothingSelected(AdapterView<?> arg0) { } }); } private void setupSaveJSONLogs() { boolean saveStumbleLogs = Prefs.getInstance(mRootView.getContext()).isSaveStumbleLogs(); CheckBox button = (CheckBox) mRootView.findViewById(R.id.toggleSaveStumbleLogs); button.setChecked(saveStumbleLogs); button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) { onToggleSaveStumbleLogs(isChecked); } }); } private void onToggleSimulation(boolean isChecked) { Prefs.getInstance(mRootView.getContext()).setSimulateStumble(isChecked); } private void setupSimulationPreference() { boolean simulationEnabled = Prefs.getInstance(mRootView.getContext()).isSimulateStumble(); final CheckBox simCheckBox = (CheckBox) mRootView.findViewById(R.id.toggleSimulation); final Button simResetBtn = (Button) mRootView.findViewById(R.id.buttonClearSimulationDefault); if (!AppGlobals.isDebug) { simCheckBox.setEnabled(false); simResetBtn.setEnabled(false); return; } simCheckBox.setChecked(simulationEnabled); simCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { onToggleSimulation(isChecked); } }); simResetBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(android.view.View view) { ClientPrefs cPrefs = ClientPrefs.getInstance(mRootView.getContext()); cPrefs.clearSimulationStart(); Context btnCtx = simResetBtn.getContext(); Toast.makeText(btnCtx, btnCtx.getText(R.string.reset_simulation_start), Toast.LENGTH_SHORT).show(); } }); } private void setupLocationChangeSpinners() { final ClientPrefs cPrefs = ClientPrefs.getInstance(mRootView.getContext()); final String[] distanceArray = {"30 m", "50 m", "75 m", "100 m", "125 m", "150 m", "175 m", "200 m"}; final ArrayAdapter<String> distanceAdapter = new ArrayAdapter<String>(this.getActivity(), android.R.layout.simple_spinner_item, distanceArray); final Spinner distanceSpinner = (Spinner) mRootView.findViewById(R.id.spinnerMotionDetectionDistanceMeters); distanceSpinner.setAdapter(distanceAdapter); final int dist = cPrefs.getMotionChangeDistanceMeters(); distanceSpinner.setSelection(findIndexOf(dist, distanceArray)); distanceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View arg1, int position, long id) { changeOfMotionDetectionDistanceOrTime(parent, position, IsDistanceOrTime.DISTANCE); } @Override public void onNothingSelected(AdapterView<?> arg0) { } }); final String[] timeArray = {"5 s", "30 s", "60 s", "90 s", "120 s", "180 s", "210 s", "240 s", "270 s", "300 s"}; final ArrayAdapter<String> timeAdapter = new ArrayAdapter<String>(this.getActivity(), android.R.layout.simple_spinner_item, timeArray); final Spinner timeSpinner = (Spinner) mRootView.findViewById(R.id.spinnerMotionDetectionTimeSeconds); timeSpinner.setAdapter(timeAdapter); final int time = cPrefs.getMotionChangeTimeWindowSeconds(); timeSpinner.setSelection(findIndexOf(time, timeArray)); timeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View arg1, int position, long id) { changeOfMotionDetectionDistanceOrTime(parent, position, IsDistanceOrTime.TIME); } @Override public void onNothingSelected(AdapterView<?> arg0) { } }); } private void onToggleSaveStumbleLogs(boolean isChecked) { if (isChecked) { Context viewCtx = mRootView.getContext(); if (!archiveDirCreatedAndMounted()) { Toast.makeText(viewCtx, viewCtx.getString(R.string.create_log_archive_failure), Toast.LENGTH_SHORT).show(); isChecked = false; CheckBox button = (CheckBox) mRootView.findViewById(R.id.toggleSaveStumbleLogs); button.setChecked(isChecked); } else { Toast.makeText(viewCtx, viewCtx.getString(R.string.create_log_archive_success) + sdcardArchivePath(), Toast.LENGTH_LONG).show(); } } Prefs.getInstance(mRootView.getContext()).setSaveStumbleLogs(isChecked); } public boolean archiveDirCreatedAndMounted() { File saveDir = new File(sdcardArchivePath()); String storageState = Environment.getExternalStorageState(); // You have to check the mount state of the external storage. // Using the mkdirs() result isn't good enough. if (!storageState.equals(Environment.MEDIA_MOUNTED)) { return false; } saveDir.mkdirs(); if (!saveDir.exists()) { return false; } ClientLog.d(LOG_TAG, "Created: [" + saveDir.getAbsolutePath() + "]"); return true; } private void changeOfMotionDetectionDistanceOrTime(AdapterView<?> parent, int position, IsDistanceOrTime isDistanceOrTime) { String item = parent.getItemAtPosition(position).toString(); final int val = Integer.valueOf(item.substring(0, item.indexOf(" "))); boolean changed; ClientPrefs prefs = ClientPrefs.getInstance(getActivity().getApplicationContext()); if (isDistanceOrTime == IsDistanceOrTime.DISTANCE) { changed = (val != prefs.getMotionChangeDistanceMeters()); prefs.setMotionChangeDistanceMeters(val); } else { changed = (val != prefs.getMotionChangeTimeWindowSeconds()); prefs.setMotionChangeTimeWindowSeconds(val); } // avoid restart when initially setting the pref if (changed) { checkRestartScanning(); } } private int findIndexOf(int needle, String[] haystack) { int i = 0; for (String item : haystack) { int val = Integer.valueOf(item.substring(0, item.indexOf(" "))); if (val == needle) { return i; } i++; } return 0; } private enum IsDistanceOrTime {DISTANCE, TIME} } }