summaryrefslogtreecommitdiffstats
path: root/src/com/android
diff options
context:
space:
mode:
authorRicardo Cerqueira <ricardo@cyngn.com>2015-11-05 02:00:43 +0000
committerRicardo Cerqueira <ricardo@cyngn.com>2015-11-05 15:44:26 +0000
commit90852d4675c9b7df6faf036efb7fe73b5f816293 (patch)
tree18ea2755ab5ea68669a7951d5a07daadc4870b0a /src/com/android
parent3dbac3fff036f5a464f6322200c59f9934ebf610 (diff)
parent310423e73156dab1c7c8f50dc67fdbc326cd37f7 (diff)
downloadpackages_apps_Settings-90852d4675c9b7df6faf036efb7fe73b5f816293.zip
packages_apps_Settings-90852d4675c9b7df6faf036efb7fe73b5f816293.tar.gz
packages_apps_Settings-90852d4675c9b7df6faf036efb7fe73b5f816293.tar.bz2
Merge tag 'android-6.0.0_r26' into HEAD
Android 6.0.0 release 26 Conflicts: res/values-fr/strings.xml res/values-it/strings.xml res/values-ja/strings.xml res/values-nl/strings.xml res/values-pl/strings.xml res/values-ro/strings.xml res/values-ru/strings.xml res/values-zh-rCN/strings.xml res/values/bools.xml res/values/strings.xml res/xml/development_prefs.xml src/com/android/settings/DevelopmentSettings.java src/com/android/settings/DeviceInfoSettings.java src/com/android/settings/DisplaySettings.java Change-Id: I9fd9e793cf6097d950f8a1e30771c8bdf5067906
Diffstat (limited to 'src/com/android')
-rw-r--r--src/com/android/settings/ApnSettings.java14
-rw-r--r--src/com/android/settings/ChooseLockGeneric.java26
-rw-r--r--src/com/android/settings/ChooseLockPassword.java183
-rw-r--r--src/com/android/settings/ChooseLockPattern.java157
-rw-r--r--src/com/android/settings/ChooseLockSettingsHelper.java15
-rw-r--r--src/com/android/settings/ColorModePreference.java151
-rw-r--r--src/com/android/settings/ConfirmDeviceCredentialActivity.java21
-rw-r--r--src/com/android/settings/ConfirmDeviceCredentialBaseActivity.java15
-rw-r--r--src/com/android/settings/ConfirmLockPassword.java72
-rw-r--r--src/com/android/settings/ConfirmLockPattern.java94
-rw-r--r--src/com/android/settings/CredentialCheckResultTracker.java79
-rw-r--r--src/com/android/settings/DevelopmentSettings.java22
-rw-r--r--src/com/android/settings/DeviceInfoSettings.java4
-rw-r--r--src/com/android/settings/DisplaySettings.java64
-rw-r--r--src/com/android/settings/HotspotOffReceiver.java5
-rw-r--r--src/com/android/settings/InstrumentedActivity.java47
-rw-r--r--src/com/android/settings/ManualDisplayActivity.java85
-rw-r--r--src/com/android/settings/SaveChosenLockWorkerBase.java106
-rw-r--r--src/com/android/settings/SecuritySettings.java8
-rw-r--r--src/com/android/settings/SettingsPreferenceFragment.java5
-rw-r--r--src/com/android/settings/TetherService.java65
-rw-r--r--src/com/android/settings/deviceinfo/UsbBackend.java153
-rw-r--r--src/com/android/settings/deviceinfo/UsbModeChooserActivity.java144
-rw-r--r--src/com/android/settings/fingerprint/FingerprintEnrollBase.java24
-rw-r--r--src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java75
-rw-r--r--src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java24
-rw-r--r--src/com/android/settings/fingerprint/FingerprintEnrollFinish.java6
-rw-r--r--src/com/android/settings/fingerprint/FingerprintEnrollIntroduction.java6
-rw-r--r--src/com/android/settings/fingerprint/FingerprintEnrollOnboard.java6
-rw-r--r--src/com/android/settings/fingerprint/FingerprintEnrollSidecar.java13
-rw-r--r--src/com/android/settings/fingerprint/FingerprintSettings.java33
-rw-r--r--src/com/android/settings/fingerprint/SetupFingerprintEnrollEnrolling.java61
-rw-r--r--src/com/android/settings/fingerprint/SetupFingerprintEnrollFindSensor.java6
-rw-r--r--src/com/android/settings/fingerprint/SetupFingerprintEnrollFinish.java6
-rw-r--r--src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroduction.java6
-rw-r--r--src/com/android/settings/fingerprint/SetupFingerprintEnrollOnboard.java6
-rw-r--r--src/com/android/settings/fuelgauge/FakeUid.java2
-rw-r--r--src/com/android/settings/widget/ExploreByTouchHelper.java724
-rw-r--r--src/com/android/settings/widget/LinkAccessibilityHelper.java169
-rw-r--r--src/com/android/settings/widget/LinkTextView.java49
-rw-r--r--src/com/android/settings/wifi/WifiConfigController.java14
41 files changed, 2404 insertions, 361 deletions
diff --git a/src/com/android/settings/ApnSettings.java b/src/com/android/settings/ApnSettings.java
index 202b43e..1ffd9bd 100644
--- a/src/com/android/settings/ApnSettings.java
+++ b/src/com/android/settings/ApnSettings.java
@@ -33,12 +33,14 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.PersistableBundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.preference.Preference;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.provider.Telephony;
+import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.text.TextUtils;
@@ -112,6 +114,8 @@ public class ApnSettings extends SettingsPreferenceFragment implements
private boolean mUnavailable;
+ private boolean mHideImsApn;
+
private final BroadcastReceiver mMobileStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -163,6 +167,11 @@ public class ApnSettings extends SettingsPreferenceFragment implements
mSubscriptionInfo = SubscriptionManager.from(activity).getActiveSubscriptionInfo(subId);
mUiccController = UiccController.getInstance();
+
+ CarrierConfigManager configManager = (CarrierConfigManager)
+ getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ PersistableBundle b = configManager.getConfig();
+ mHideImsApn = b.getBoolean(CarrierConfigManager.KEY_HIDE_IMS_APN_BOOL);
}
@Override
@@ -228,9 +237,12 @@ public class ApnSettings extends SettingsPreferenceFragment implements
final String mccmnc = mSubscriptionInfo == null ? ""
: tm.getIccOperatorNumericForData(mSubscriptionInfo.getSubscriptionId());
Log.d(TAG, "mccmnc = " + mccmnc);
- final String where = "numeric=\""
+ String where = "numeric=\""
+ mccmnc
+ "\" AND NOT (type='ia' AND (apn=\"\" OR apn IS NULL))";
+ if (mHideImsApn) {
+ where = where + " AND NOT (type='ims')";
+ }
Cursor cursor = getContentResolver().query(Telephony.Carriers.CONTENT_URI, new String[] {
"_id", "name", "apn", "type", "mvno_type", "mvno_match_data", "read_only"}, where,
diff --git a/src/com/android/settings/ChooseLockGeneric.java b/src/com/android/settings/ChooseLockGeneric.java
index 708b871..44a5c51 100644
--- a/src/com/android/settings/ChooseLockGeneric.java
+++ b/src/com/android/settings/ChooseLockGeneric.java
@@ -109,11 +109,18 @@ public class ChooseLockGeneric extends SettingsActivity {
@Override
public void onRemovalSucceeded(Fingerprint fingerprint) {
Log.v(TAG, "Fingerprint removed: " + fingerprint.getFingerId());
+ if (mFingerprintManager.getEnrolledFingerprints().size() == 0) {
+ finish();
+ }
}
@Override
public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) {
- Toast.makeText(getActivity(), errString, Toast.LENGTH_SHORT);
+ Activity activity = getActivity();
+ if (activity != null) {
+ Toast.makeText(getActivity(), errString, Toast.LENGTH_SHORT);
+ }
+ finish();
}
};
@@ -355,6 +362,10 @@ public class ChooseLockGeneric extends SettingsActivity {
boolean visible = true;
if (KEY_UNLOCK_SET_OFF.equals(key)) {
enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ if (getResources().getBoolean(R.bool.config_hide_none_security_option)) {
+ enabled = false;
+ visible = false;
+ }
} else if (KEY_UNLOCK_SET_NONE.equals(key)) {
enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
} else if (KEY_UNLOCK_SET_PATTERN.equals(key)) {
@@ -491,18 +502,19 @@ public class ChooseLockGeneric extends SettingsActivity {
mChooseLockSettingsHelper.utils().clearLock(UserHandle.myUserId());
mChooseLockSettingsHelper.utils().setLockScreenDisabled(disabled,
UserHandle.myUserId());
- removeAllFingerprintTemplates();
+ removeAllFingerprintTemplatesAndFinish();
getActivity().setResult(Activity.RESULT_OK);
- finish();
} else {
- removeAllFingerprintTemplates();
- finish();
+ removeAllFingerprintTemplatesAndFinish();
}
}
- private void removeAllFingerprintTemplates() {
- if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) {
+ private void removeAllFingerprintTemplatesAndFinish() {
+ if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()
+ && mFingerprintManager.getEnrolledFingerprints().size() > 0) {
mFingerprintManager.remove(new Fingerprint(null, 0, 0, 0), mRemovalCallback);
+ } else {
+ finish();
}
}
diff --git a/src/com/android/settings/ChooseLockPassword.java b/src/com/android/settings/ChooseLockPassword.java
index 64aaaca..54c3620 100644
--- a/src/com/android/settings/ChooseLockPassword.java
+++ b/src/com/android/settings/ChooseLockPassword.java
@@ -17,11 +17,11 @@
package com.android.settings;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.PasswordEntryKeyboardHelper;
import com.android.internal.widget.PasswordEntryKeyboardView;
import com.android.internal.widget.TextViewInputDisabler;
+import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
import com.android.settings.notification.RedactionInterstitial;
import android.app.Activity;
@@ -30,7 +30,6 @@ import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.inputmethodservice.KeyboardView;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -122,12 +121,15 @@ public class ChooseLockPassword extends SettingsActivity {
}
public static class ChooseLockPasswordFragment extends InstrumentedFragment
- implements OnClickListener, OnEditorActionListener, TextWatcher {
+ implements OnClickListener, OnEditorActionListener, TextWatcher,
+ SaveAndFinishWorker.Listener {
private static final String KEY_FIRST_PIN = "first_pin";
private static final String KEY_UI_STAGE = "ui_stage";
private static final String KEY_CURRENT_PASSWORD = "current_password";
+ private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
private String mCurrentPassword;
+ private String mChosenPassword;
private boolean mHasChallenge;
private long mChallenge;
private TextView mPasswordEntry;
@@ -141,11 +143,11 @@ public class ChooseLockPassword extends SettingsActivity {
private int mPasswordMinNumeric = 0;
private int mPasswordMinNonLetter = 0;
private LockPatternUtils mLockPatternUtils;
- private AsyncTask<?, ?, ?> mPendingLockCheck;
+ private SaveAndFinishWorker mSaveAndFinishWorker;
private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
private ChooseLockSettingsHelper mChooseLockSettingsHelper;
private Stage mUiStage = Stage.Introduction;
- private boolean mDone = false;
+
private TextView mHeaderText;
private String mFirstPin;
private KeyboardView mKeyboardView;
@@ -301,8 +303,11 @@ public class ChooseLockPassword extends SettingsActivity {
if (mCurrentPassword == null) {
mCurrentPassword = savedInstanceState.getString(KEY_CURRENT_PASSWORD);
}
+
+ // Re-attach to the exiting worker if there is one.
+ mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
+ FRAGMENT_TAG_SAVE_AND_FINISH);
}
- mDone = false;
if (activity instanceof SettingsActivity) {
final SettingsActivity sa = (SettingsActivity) activity;
int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header
@@ -321,16 +326,18 @@ public class ChooseLockPassword extends SettingsActivity {
public void onResume() {
super.onResume();
updateStage(mUiStage);
- mPasswordEntryInputDisabler.setInputEnabled(true);
- mKeyboardView.requestFocus();
+ if (mSaveAndFinishWorker != null) {
+ mSaveAndFinishWorker.setListener(this);
+ } else {
+ mKeyboardView.requestFocus();
+ }
}
@Override
public void onPause() {
mHandler.removeMessages(MSG_SHOW_ERROR);
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
- mPendingLockCheck = null;
+ if (mSaveAndFinishWorker != null) {
+ mSaveAndFinishWorker.setListener(null);
}
super.onPause();
@@ -479,36 +486,22 @@ public class ChooseLockPassword extends SettingsActivity {
}
public void handleNext() {
- if (mDone) return;
-
- final String pin = mPasswordEntry.getText().toString();
- if (TextUtils.isEmpty(pin)) {
+ if (mSaveAndFinishWorker != null) return;
+ mChosenPassword = mPasswordEntry.getText().toString();
+ if (TextUtils.isEmpty(mChosenPassword)) {
return;
}
String errorMsg = null;
if (mUiStage == Stage.Introduction) {
- errorMsg = validatePassword(pin);
+ errorMsg = validatePassword(mChosenPassword);
if (errorMsg == null) {
- mFirstPin = pin;
+ mFirstPin = mChosenPassword;
mPasswordEntry.setText("");
updateStage(Stage.NeedToConfirm);
}
} else if (mUiStage == Stage.NeedToConfirm) {
- if (mFirstPin.equals(pin)) {
- boolean wasSecureBefore = mLockPatternUtils.isSecure(UserHandle.myUserId());
- final boolean required = getActivity().getIntent().getBooleanExtra(
- EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
- mLockPatternUtils.setCredentialRequiredToDecrypt(required);
- mLockPatternUtils.saveLockPassword(pin, mCurrentPassword, mRequestedQuality,
- UserHandle.myUserId());
-
- if (mHasChallenge) {
- startVerifyPassword(pin, wasSecureBefore);
- return;
- } else {
- getActivity().setResult(RESULT_FINISHED);
- }
- finishConfirmStage(wasSecureBefore);
+ if (mFirstPin.equals(mChosenPassword)) {
+ startSaveAndFinish();
} else {
CharSequence tmp = mPasswordEntry.getText();
if (tmp != null) {
@@ -522,50 +515,6 @@ public class ChooseLockPassword extends SettingsActivity {
}
}
- private void startVerifyPassword(final String pin, final boolean wasSecureBefore) {
- mPasswordEntryInputDisabler.setInputEnabled(false);
- setNextEnabled(false);
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
- }
-
- mPendingLockCheck = LockPatternChecker.verifyPassword(
- mLockPatternUtils,
- pin,
- mChallenge,
- UserHandle.myUserId(),
- new LockPatternChecker.OnVerifyCallback() {
- @Override
- public void onVerified(byte[] token, int timeoutMs) {
- if (token == null) {
- Log.e(TAG, "critical: no token returned from known good password");
- }
-
- mPasswordEntryInputDisabler.setInputEnabled(true);
- setNextEnabled(true);
- mPendingLockCheck = null;
-
- Intent intent = new Intent();
- intent.putExtra(
- ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
- token);
- getActivity().setResult(RESULT_FINISHED, intent);
- finishConfirmStage(wasSecureBefore);
- }
- });
- }
-
- private void finishConfirmStage(boolean wasSecureBefore) {
- getActivity().finish();
- mDone = true;
- if (!wasSecureBefore) {
- Intent intent = getRedactionInterstitialIntent(getActivity());
- if (intent != null) {
- startActivity(intent);
- }
- }
- }
-
protected void setNextEnabled(boolean enabled) {
mNextButton.setEnabled(enabled);
}
@@ -609,6 +558,7 @@ public class ChooseLockPassword extends SettingsActivity {
* Update the hint based on current Stage and length of password entry
*/
private void updateUi() {
+ final boolean canInput = mSaveAndFinishWorker == null;
String password = mPasswordEntry.getText().toString();
final int length = password.length();
if (mUiStage == Stage.Introduction) {
@@ -629,9 +579,10 @@ public class ChooseLockPassword extends SettingsActivity {
}
} else {
mHeaderText.setText(mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint);
- setNextEnabled(length > 0);
+ setNextEnabled(canInput && length > 0);
}
setNextText(mUiStage.buttonText);
+ mPasswordEntryInputDisabler.setInputEnabled(canInput);
}
public void afterTextChanged(Editable s) {
@@ -649,5 +600,83 @@ public class ChooseLockPassword extends SettingsActivity {
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
+
+ private void startSaveAndFinish() {
+ if (mSaveAndFinishWorker != null) {
+ Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
+ return;
+ }
+
+ mPasswordEntryInputDisabler.setInputEnabled(false);
+ setNextEnabled(false);
+
+ mSaveAndFinishWorker = new SaveAndFinishWorker();
+ getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
+ FRAGMENT_TAG_SAVE_AND_FINISH).commit();
+ mSaveAndFinishWorker.setListener(this);
+
+ final boolean required = getActivity().getIntent().getBooleanExtra(
+ EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
+ mSaveAndFinishWorker.start(mLockPatternUtils, required, mHasChallenge, mChallenge,
+ mChosenPassword, mCurrentPassword, mRequestedQuality);
+ }
+
+ @Override
+ public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
+ getActivity().setResult(RESULT_FINISHED, resultData);
+ getActivity().finish();
+
+ if (!wasSecureBefore) {
+ Intent intent = getRedactionInterstitialIntent(getActivity());
+ if (intent != null) {
+ startActivity(intent);
+ }
+ }
+ }
+ }
+
+ private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
+
+ private String mChosenPassword;
+ private String mCurrentPassword;
+ private int mRequestedQuality;
+
+ public void start(LockPatternUtils utils, boolean required,
+ boolean hasChallenge, long challenge,
+ String chosenPassword, String currentPassword, int requestedQuality) {
+ prepare(utils, required, hasChallenge, challenge);
+
+ mChosenPassword = chosenPassword;
+ mCurrentPassword = currentPassword;
+ mRequestedQuality = requestedQuality;
+
+ start();
+ }
+
+ @Override
+ protected Intent saveAndVerifyInBackground() {
+ Intent result = null;
+ final int userId = UserHandle.myUserId();
+ mUtils.saveLockPassword(mChosenPassword, mCurrentPassword, mRequestedQuality,
+ userId);
+
+ if (mHasChallenge) {
+ byte[] token;
+ try {
+ token = mUtils.verifyPassword(mChosenPassword, mChallenge, userId);
+ } catch (RequestThrottledException e) {
+ token = null;
+ }
+
+ if (token == null) {
+ Log.e(TAG, "critical: no token returned for known good password.");
+ }
+
+ result = new Intent();
+ result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
+ }
+
+ return result;
+ }
}
}
diff --git a/src/com/android/settings/ChooseLockPattern.java b/src/com/android/settings/ChooseLockPattern.java
index 087a23e..1dd24f2 100644
--- a/src/com/android/settings/ChooseLockPattern.java
+++ b/src/com/android/settings/ChooseLockPattern.java
@@ -19,8 +19,8 @@ package com.android.settings;
import com.android.internal.logging.MetricsLogger;
import com.google.android.collect.Lists;
import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
-import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
import com.android.internal.widget.LockPatternView;
import com.android.internal.widget.LockPatternView.Cell;
import com.android.settings.notification.RedactionInterstitial;
@@ -31,7 +31,6 @@ import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
@@ -125,7 +124,7 @@ public class ChooseLockPattern extends SettingsActivity {
}
public static class ChooseLockPatternFragment extends InstrumentedFragment
- implements View.OnClickListener {
+ implements View.OnClickListener, SaveAndFinishWorker.Listener {
public static final int CONFIRM_EXISTING_REQUEST = 55;
@@ -137,6 +136,8 @@ public class ChooseLockPattern extends SettingsActivity {
private static final int ID_EMPTY_MESSAGE = -1;
+ private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
+
private String mCurrentPattern;
private boolean mHasChallenge;
private long mChallenge;
@@ -346,7 +347,6 @@ public class ChooseLockPattern extends SettingsActivity {
}
private Stage mUiStage = Stage.Introduction;
- private boolean mDone = false;
private Runnable mClearPatternRunnable = new Runnable() {
public void run() {
@@ -355,7 +355,7 @@ public class ChooseLockPattern extends SettingsActivity {
};
private ChooseLockSettingsHelper mChooseLockSettingsHelper;
- private AsyncTask<?, ?, ?> mPendingLockCheck;
+ private SaveAndFinishWorker mSaveAndFinishWorker;
private static final String KEY_UI_STAGE = "uiStage";
private static final String KEY_PATTERN_CHOICE = "chosenPattern";
@@ -434,22 +434,29 @@ public class ChooseLockPattern extends SettingsActivity {
mCurrentPattern = savedInstanceState.getString(KEY_CURRENT_PATTERN);
}
updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
+
+ // Re-attach to the exiting worker if there is one.
+ mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
+ FRAGMENT_TAG_SAVE_AND_FINISH);
}
- mDone = false;
}
@Override
public void onResume() {
super.onResume();
- mLockPatternView.enableInput();
+ updateStage(mUiStage);
+
+ if (mSaveAndFinishWorker != null) {
+ setRightButtonEnabled(false);
+ mSaveAndFinishWorker.setListener(this);
+ }
}
@Override
public void onPause() {
super.onPause();
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
- mPendingLockCheck = null;
+ if (mSaveAndFinishWorker != null) {
+ mSaveAndFinishWorker.setListener(null);
}
}
@@ -483,7 +490,7 @@ public class ChooseLockPattern extends SettingsActivity {
throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed
+ " when button is " + RightButtonMode.Confirm);
}
- saveChosenPatternAndFinish();
+ startSaveAndFinish();
} else if (mUiStage.rightMode == RightButtonMode.Ok) {
if (mUiStage != Stage.HelpScreen) {
throw new IllegalStateException("Help screen is only mode with ok button, "
@@ -570,7 +577,7 @@ public class ChooseLockPattern extends SettingsActivity {
setRightButtonText(stage.rightMode.text);
setRightButtonEnabled(stage.rightMode.enabled);
- // same for whether the patten is enabled
+ // same for whether the pattern is enabled
if (stage.patternEnabled) {
mLockPatternView.enableInput();
} else {
@@ -615,7 +622,6 @@ public class ChooseLockPattern extends SettingsActivity {
}
}
-
// clear the wrong pattern unless they have started a new one
// already
private void postClearPatternRunnable() {
@@ -623,77 +629,90 @@ public class ChooseLockPattern extends SettingsActivity {
mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
}
- private void saveChosenPatternAndFinish() {
- if (mDone) return;
- LockPatternUtils utils = mChooseLockSettingsHelper.utils();
- final boolean lockVirgin = !utils.isPatternEverChosen(UserHandle.myUserId());
+ private void startSaveAndFinish() {
+ if (mSaveAndFinishWorker != null) {
+ Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
+ return;
+ }
+
+ setRightButtonEnabled(false);
- boolean wasSecureBefore = utils.isSecure(UserHandle.myUserId());
+ mSaveAndFinishWorker = new SaveAndFinishWorker();
+ getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
+ FRAGMENT_TAG_SAVE_AND_FINISH).commit();
+ mSaveAndFinishWorker.setListener(this);
final boolean required = getActivity().getIntent().getBooleanExtra(
EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
+ mSaveAndFinishWorker.start(mChooseLockSettingsHelper.utils(), required,
+ mHasChallenge, mChallenge, mChosenPattern, mCurrentPattern);
+ }
- utils.setCredentialRequiredToDecrypt(required);
- utils.saveLockPattern(mChosenPattern, mCurrentPattern, UserHandle.myUserId());
+ @Override
+ public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
+ getActivity().setResult(RESULT_FINISHED, resultData);
+ getActivity().finish();
- if (lockVirgin) {
- utils.setVisiblePatternEnabled(true, UserHandle.myUserId());
+ if (!wasSecureBefore) {
+ Intent intent = getRedactionInterstitialIntent(getActivity());
+ if (intent != null) {
+ startActivity(intent);
+ }
}
+ }
+ }
+
+ private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
+
+ private List<LockPatternView.Cell> mChosenPattern;
+ private String mCurrentPattern;
+ private boolean mLockVirgin;
+
+ public void start(LockPatternUtils utils, boolean credentialRequired,
+ boolean hasChallenge, long challenge,
+ List<LockPatternView.Cell> chosenPattern, String currentPattern) {
+ prepare(utils, credentialRequired, hasChallenge, challenge);
+
+ mCurrentPattern = currentPattern;
+ mChosenPattern = chosenPattern;
+
+ mLockVirgin = !mUtils.isPatternEverChosen(UserHandle.myUserId());
+
+ start();
+ }
+
+ @Override
+ protected Intent saveAndVerifyInBackground() {
+ Intent result = null;
+ final int userId = UserHandle.myUserId();
+ mUtils.saveLockPattern(mChosenPattern, mCurrentPattern, userId);
if (mHasChallenge) {
- startVerifyPattern(utils, wasSecureBefore);
- } else {
- if (!wasSecureBefore) {
- Intent intent = getRedactionInterstitialIntent(getActivity());
- if (intent != null) {
- startActivity(intent);
- }
+ byte[] token;
+ try {
+ token = mUtils.verifyPattern(mChosenPattern, mChallenge, userId);
+ } catch (RequestThrottledException e) {
+ token = null;
+ }
+
+ if (token == null) {
+ Log.e(TAG, "critical: no token returned for known good pattern");
}
- getActivity().setResult(RESULT_FINISHED);
- doFinish();
- }
- }
- private void startVerifyPattern(LockPatternUtils utils, final boolean wasSecureBefore) {
- mLockPatternView.disableInput();
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
+ result = new Intent();
+ result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
}
- mPendingLockCheck = LockPatternChecker.verifyPattern(
- utils,
- mChosenPattern,
- mChallenge,
- UserHandle.myUserId(),
- new LockPatternChecker.OnVerifyCallback() {
- @Override
- public void onVerified(byte[] token, int timeoutMs) {
- if (token == null) {
- Log.e(TAG, "critical: no token returned for known good pattern");
- }
-
- mLockPatternView.enableInput();
- mPendingLockCheck = null;
-
- if (!wasSecureBefore) {
- Intent intent = getRedactionInterstitialIntent(getActivity());
- if (intent != null) {
- startActivity(intent);
- }
- }
-
- Intent intent = new Intent();
- intent.putExtra(
- ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
- getActivity().setResult(RESULT_FINISHED, intent);
- doFinish();
- }
- });
+ return result;
}
- private void doFinish() {
- getActivity().finish();
- mDone = true;
+ @Override
+ protected void finish(Intent resultData) {
+ if (mLockVirgin) {
+ mUtils.setVisiblePatternEnabled(true, UserHandle.myUserId());
+ }
+
+ super.finish(resultData);
}
}
}
diff --git a/src/com/android/settings/ChooseLockSettingsHelper.java b/src/com/android/settings/ChooseLockSettingsHelper.java
index 327e622..665bffe 100644
--- a/src/com/android/settings/ChooseLockSettingsHelper.java
+++ b/src/com/android/settings/ChooseLockSettingsHelper.java
@@ -158,10 +158,19 @@ public final class ChooseLockSettingsHelper {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, hasChallenge);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge);
intent.setClassName(ConfirmDeviceCredentialBaseFragment.PACKAGE, activityClass.getName());
- if (mFragment != null) {
- mFragment.startActivityForResult(intent, request);
+ if (external) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ if (mFragment != null) {
+ mFragment.startActivity(intent);
+ } else {
+ mActivity.startActivity(intent);
+ }
} else {
- mActivity.startActivityForResult(intent, request);
+ if (mFragment != null) {
+ mFragment.startActivityForResult(intent, request);
+ } else {
+ mActivity.startActivityForResult(intent, request);
+ }
}
return true;
}
diff --git a/src/com/android/settings/ColorModePreference.java b/src/com/android/settings/ColorModePreference.java
new file mode 100644
index 0000000..14ffe48
--- /dev/null
+++ b/src/com/android/settings/ColorModePreference.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings;
+
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Handler;
+import android.os.Looper;
+import android.preference.DialogPreference;
+import android.preference.SwitchPreference;
+import android.util.AttributeSet;
+import android.view.Display;
+import android.view.Display.ColorTransform;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Checkable;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class ColorModePreference extends SwitchPreference implements DisplayListener {
+
+ private DisplayManager mDisplayManager;
+ private Display mDisplay;
+
+ private int mCurrentIndex;
+ private ArrayList<ColorTransformDescription> mDescriptions;
+
+ public ColorModePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mDisplayManager = getContext().getSystemService(DisplayManager.class);
+ }
+
+ public int getTransformsCount() {
+ return mDescriptions.size();
+ }
+
+ public void startListening() {
+ mDisplayManager.registerDisplayListener(this, new Handler(Looper.getMainLooper()));
+ }
+
+ public void stopListening() {
+ mDisplayManager.unregisterDisplayListener(this);
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ updateCurrentAndSupported();
+ }
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ updateCurrentAndSupported();
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ }
+
+ public void updateCurrentAndSupported() {
+ mDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+
+ mDescriptions = new ArrayList<>();
+
+ Resources resources = getContext().getResources();
+ int[] transforms = resources.getIntArray(
+ com.android.internal.R.array.config_colorTransforms);
+ String[] titles = resources.getStringArray(R.array.color_mode_names);
+ String[] descriptions = resources.getStringArray(R.array.color_mode_descriptions);
+ // Map the resource information describing color transforms.
+ for (int i = 0; i < transforms.length; i++) {
+ if (transforms[i] != -1 && i != 1 /* Skip Natural for now. */) {
+ ColorTransformDescription desc = new ColorTransformDescription();
+ desc.colorTransform = transforms[i];
+ desc.title = titles[i];
+ desc.summary = descriptions[i];
+ mDescriptions.add(desc);
+ }
+ }
+ // Match up a ColorTransform to every description.
+ ColorTransform[] supportedColorTransforms = mDisplay.getSupportedColorTransforms();
+ for (int i = 0; i < supportedColorTransforms.length; i++) {
+ for (int j = 0; j < mDescriptions.size(); j++) {
+ if (mDescriptions.get(j).colorTransform
+ == supportedColorTransforms[i].getColorTransform()
+ && mDescriptions.get(j).transform == null) {
+ mDescriptions.get(j).transform = supportedColorTransforms[i];
+ break;
+ }
+ }
+ }
+ // Remove any extras that don't have a transform for some reason.
+ for (int i = 0; i < mDescriptions.size(); i++) {
+ if (mDescriptions.get(i).transform == null) {
+ mDescriptions.remove(i--);
+ }
+ }
+
+ ColorTransform currentTransform = mDisplay.getColorTransform();
+ mCurrentIndex = -1;
+ for (int i = 0; i < mDescriptions.size(); i++) {
+ if (mDescriptions.get(i).colorTransform == currentTransform.getColorTransform()) {
+ mCurrentIndex = i;
+ break;
+ }
+ }
+ setChecked(mCurrentIndex == 1);
+ }
+
+ @Override
+ protected boolean persistBoolean(boolean value) {
+ // Right now this is a switch, so we only support two modes.
+ if (mDescriptions.size() == 2) {
+ ColorTransformDescription desc = mDescriptions.get(value ? 1 : 0);
+
+ mDisplay.requestColorTransform(desc.transform);
+ mCurrentIndex = mDescriptions.indexOf(desc);
+ }
+
+ return true;
+ }
+
+ private static class ColorTransformDescription {
+ private int colorTransform;
+ private String title;
+ private String summary;
+ private ColorTransform transform;
+ }
+}
diff --git a/src/com/android/settings/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/ConfirmDeviceCredentialActivity.java
index da39a0f..86935c3 100644
--- a/src/com/android/settings/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/ConfirmDeviceCredentialActivity.java
@@ -58,23 +58,12 @@ public class ConfirmDeviceCredentialActivity extends Activity {
String title = intent.getStringExtra(KeyguardManager.EXTRA_TITLE);
String details = intent.getStringExtra(KeyguardManager.EXTRA_DESCRIPTION);
- // Ignore rotates and ensure we only launch this once
- if (savedInstanceState == null) {
- ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this);
- if (!helper.launchConfirmationActivity(0 /* request code */, null /* title */, title,
- details, false /* returnCredentials */, true /* isExternal */)) {
- Log.d(TAG, "No pattern, password or PIN set.");
- setResult(Activity.RESULT_OK);
- finish();
- }
+ ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this);
+ if (!helper.launchConfirmationActivity(0 /* request code */, null /* title */, title,
+ details, false /* returnCredentials */, true /* isExternal */)) {
+ Log.d(TAG, "No pattern, password or PIN set.");
+ setResult(Activity.RESULT_OK);
}
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- boolean credentialsConfirmed = (resultCode == Activity.RESULT_OK);
- Log.d(TAG, "Device credentials confirmed: " + credentialsConfirmed);
- setResult(credentialsConfirmed ? Activity.RESULT_OK : Activity.RESULT_CANCELED);
finish();
}
}
diff --git a/src/com/android/settings/ConfirmDeviceCredentialBaseActivity.java b/src/com/android/settings/ConfirmDeviceCredentialBaseActivity.java
index 176efbc..d9af800 100644
--- a/src/com/android/settings/ConfirmDeviceCredentialBaseActivity.java
+++ b/src/com/android/settings/ConfirmDeviceCredentialBaseActivity.java
@@ -19,6 +19,7 @@ package com.android.settings;
import android.app.Fragment;
import android.app.KeyguardManager;
import android.os.Bundle;
+import android.os.Handler;
import android.view.MenuItem;
import android.view.WindowManager;
@@ -28,6 +29,7 @@ public abstract class ConfirmDeviceCredentialBaseActivity extends SettingsActivi
private boolean mDark;
private boolean mEnterAnimationPending;
private boolean mFirstTimeVisible = true;
+ private final Handler mHandler = new Handler();
@Override
protected void onCreate(Bundle savedState) {
@@ -67,6 +69,7 @@ public abstract class ConfirmDeviceCredentialBaseActivity extends SettingsActivi
mFirstTimeVisible = false;
prepareEnterAnimation();
mEnterAnimationPending = true;
+ mHandler.postDelayed(mEnterAnimationCompleteTimeoutRunnable, 1000);
}
}
@@ -82,6 +85,7 @@ public abstract class ConfirmDeviceCredentialBaseActivity extends SettingsActivi
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
if (mEnterAnimationPending) {
+ mHandler.removeCallbacks(mEnterAnimationCompleteTimeoutRunnable);
startEnterAnimation();
mEnterAnimationPending = false;
}
@@ -94,4 +98,15 @@ public abstract class ConfirmDeviceCredentialBaseActivity extends SettingsActivi
public void startEnterAnimation() {
getFragment().startEnterAnimation();
}
+
+ /**
+ * Workaround for a bug in window manager which results that onEnterAnimationComplete doesn't
+ * get called in all cases.
+ */
+ private final Runnable mEnterAnimationCompleteTimeoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ onEnterAnimationComplete();
+ }
+ };
}
diff --git a/src/com/android/settings/ConfirmLockPassword.java b/src/com/android/settings/ConfirmLockPassword.java
index 1c42045..cce29dd 100644
--- a/src/com/android/settings/ConfirmLockPassword.java
+++ b/src/com/android/settings/ConfirmLockPassword.java
@@ -16,10 +16,8 @@
package com.android.settings;
-import android.os.UserHandle;
import android.text.TextUtils;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.TextViewInputDisabler;
@@ -78,19 +76,20 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
}
public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment
- implements OnClickListener, OnEditorActionListener {
- private static final String KEY_NUM_WRONG_CONFIRM_ATTEMPTS
- = "confirm_lock_password_fragment.key_num_wrong_confirm_attempts";
+ implements OnClickListener, OnEditorActionListener,
+ CredentialCheckResultTracker.Listener {
private static final long ERROR_MESSAGE_TIMEOUT = 3000;
+ private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
private TextView mPasswordEntry;
private TextViewInputDisabler mPasswordEntryInputDisabler;
private LockPatternUtils mLockPatternUtils;
private AsyncTask<?, ?, ?> mPendingLockCheck;
+ private CredentialCheckResultTracker mCredentialCheckResultTracker;
+ private boolean mDisappearing = false;
private TextView mHeaderTextView;
private TextView mDetailsTextView;
private TextView mErrorTextView;
private Handler mHandler = new Handler();
- private int mNumWrongConfirmAttempts;
private CountDownTimer mCountdownTimer;
private boolean mIsAlpha;
private InputMethodManager mImm;
@@ -110,11 +109,6 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
super.onCreate(savedInstanceState);
mLockPatternUtils = new LockPatternUtils(getActivity());
mEffectiveUserId = Utils.getEffectiveUserId(getActivity());
-
- if (savedInstanceState != null) {
- mNumWrongConfirmAttempts = savedInstanceState.getInt(
- KEY_NUM_WRONG_CONFIRM_ATTEMPTS, 0);
- }
}
@Override
@@ -165,6 +159,15 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
0.5f /* delayScale */, AnimationUtils.loadInterpolator(
getContext(), android.R.interpolator.fast_out_linear_in));
setAccessibilityTitle(mHeaderTextView.getText());
+
+ mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
+ .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
+ if (mCredentialCheckResultTracker == null) {
+ mCredentialCheckResultTracker = new CredentialCheckResultTracker();
+ getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
+ FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
+ }
+
return view;
}
@@ -227,10 +230,7 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
mCountdownTimer.cancel();
mCountdownTimer = null;
}
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
- mPendingLockCheck = null;
- }
+ mCredentialCheckResultTracker.setListener(null);
}
@Override
@@ -243,21 +243,17 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
super.onResume();
long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
if (deadline != 0) {
+ mCredentialCheckResultTracker.clearResult();
handleAttemptLockout(deadline);
} else {
resetState();
}
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putInt(KEY_NUM_WRONG_CONFIRM_ATTEMPTS, mNumWrongConfirmAttempts);
+ mCredentialCheckResultTracker.setListener(this);
}
@Override
protected void authenticationSucceeded() {
- startDisappearAnimation(new Intent());
+ mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
}
@Override
@@ -298,11 +294,12 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
}
private void handleNext() {
- mPasswordEntryInputDisabler.setInputEnabled(false);
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
+ if (mPendingLockCheck != null || mDisappearing) {
+ return;
}
+ mPasswordEntryInputDisabler.setInputEnabled(false);
+
final String pin = mPasswordEntry.getText().toString();
final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
@@ -317,7 +314,7 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
return;
}
- onPasswordChecked(false, intent, 0, mEffectiveUserId);
+ mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
}
private boolean isInternalActivity() {
@@ -344,7 +341,8 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
token);
}
- onPasswordChecked(matched, intent, timeoutMs, localEffectiveUserId);
+ mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
+ localEffectiveUserId);
}
});
}
@@ -366,16 +364,27 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
intent.putExtra(
ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin);
}
- onPasswordChecked(matched, intent, timeoutMs, localEffectiveUserId);
+ mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
+ localEffectiveUserId);
}
});
}
private void startDisappearAnimation(final Intent intent) {
+ if (mDisappearing) {
+ return;
+ }
+ mDisappearing = true;
+
if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) {
mDisappearAnimationUtils.startAnimation(getActiveViews(), new Runnable() {
@Override
public void run() {
+ // Bail if there is no active activity.
+ if (getActivity() == null || getActivity().isFinishing()) {
+ return;
+ }
+
getActivity().setResult(RESULT_OK, intent);
getActivity().finish();
getActivity().overridePendingTransition(
@@ -405,6 +414,12 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
}
}
+ @Override
+ public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
+ int effectiveUserId) {
+ onPasswordChecked(matched, intent, timeoutMs, effectiveUserId);
+ }
+
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
long elapsedRealtime = SystemClock.elapsedRealtime();
mPasswordEntry.setEnabled(false);
@@ -424,7 +439,6 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
public void onFinish() {
resetState();
mErrorTextView.setText("");
- mNumWrongConfirmAttempts = 0;
}
}.start();
}
diff --git a/src/com/android/settings/ConfirmLockPattern.java b/src/com/android/settings/ConfirmLockPattern.java
index 51cde60..94ba01a 100644
--- a/src/com/android/settings/ConfirmLockPattern.java
+++ b/src/com/android/settings/ConfirmLockPattern.java
@@ -32,7 +32,6 @@ import android.os.CountDownTimer;
import android.os.SystemClock;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
@@ -76,17 +75,18 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
}
public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment
- implements AppearAnimationCreator<Object> {
+ implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener {
// how long we wait to clear a wrong pattern
private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
- private static final String KEY_NUM_WRONG_ATTEMPTS = "num_wrong_attempts";
+ private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
private LockPatternView mLockPatternView;
private LockPatternUtils mLockPatternUtils;
private AsyncTask<?, ?, ?> mPendingLockCheck;
- private int mNumWrongConfirmAttempts;
+ private CredentialCheckResultTracker mCredentialCheckResultTracker;
+ private boolean mDisappearing = false;
private CountDownTimer mCountdownTimer;
private TextView mHeaderTextView;
@@ -148,9 +148,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
updateStage(Stage.NeedToUnlock);
- if (savedInstanceState != null) {
- mNumWrongConfirmAttempts = savedInstanceState.getInt(KEY_NUM_WRONG_ATTEMPTS);
- } else {
+ if (savedInstanceState == null) {
// on first launch, if no lock pattern is set, then finish with
// success (don't want user to get stuck confirming something that
// doesn't exist).
@@ -174,13 +172,21 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
}
});
setAccessibilityTitle(mHeaderTextView.getText());
+
+ mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
+ .findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
+ if (mCredentialCheckResultTracker == null) {
+ mCredentialCheckResultTracker = new CredentialCheckResultTracker();
+ getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
+ FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
+ }
+
return view;
}
@Override
public void onSaveInstanceState(Bundle outState) {
// deliberately not calling super since we are managing this in full
- outState.putInt(KEY_NUM_WRONG_ATTEMPTS, mNumWrongConfirmAttempts);
}
@Override
@@ -190,10 +196,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
if (mCountdownTimer != null) {
mCountdownTimer.cancel();
}
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
- mPendingLockCheck = null;
- }
+ mCredentialCheckResultTracker.setListener(null);
}
@Override
@@ -208,13 +211,14 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
// if the user is currently locked out, enforce it.
long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
if (deadline != 0) {
+ mCredentialCheckResultTracker.clearResult();
handleAttemptLockout(deadline);
} else if (!mLockPatternView.isEnabled()) {
// The deadline has passed, but the timer was cancelled. Or the pending lock
// check was cancelled. Need to clean up.
- mNumWrongConfirmAttempts = 0;
updateStage(Stage.NeedToUnlock);
}
+ mCredentialCheckResultTracker.setListener(this);
}
@Override
@@ -317,16 +321,26 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
@Override
protected void authenticationSucceeded() {
- startDisappearAnimation(new Intent());
+ mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
}
private void startDisappearAnimation(final Intent intent) {
+ if (mDisappearing) {
+ return;
+ }
+ mDisappearing = true;
+
if (getActivity().getThemeResId() == R.style.Theme_ConfirmDeviceCredentialsDark) {
mLockPatternView.clearPattern();
mDisappearAnimationUtils.startAnimation2d(getActiveViews(),
new Runnable() {
@Override
public void run() {
+ // Bail if there is no active activity.
+ if (getActivity() == null || getActivity().isFinishing()) {
+ return;
+ }
+
getActivity().setResult(RESULT_OK, intent);
getActivity().finish();
getActivity().overridePendingTransition(
@@ -370,11 +384,12 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
}
public void onPatternDetected(List<LockPatternView.Cell> pattern) {
- mLockPatternView.setEnabled(false);
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
+ if (mPendingLockCheck != null || mDisappearing) {
+ return;
}
+ mLockPatternView.setEnabled(false);
+
final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
Intent intent = new Intent();
@@ -388,7 +403,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
return;
}
- onPatternChecked(pattern, false, intent, 0, mEffectiveUserId);
+ mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
}
private boolean isInternalActivity() {
@@ -416,8 +431,8 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
token);
}
- onPatternChecked(pattern,
- matched, intent, timeoutMs, localEffectiveUserId);
+ mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
+ localEffectiveUserId);
}
});
}
@@ -425,7 +440,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
private void startCheckPattern(final List<LockPatternView.Cell> pattern,
final Intent intent) {
if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
- onPatternChecked(pattern, false, intent, 0, mEffectiveUserId);
+ mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
return;
}
@@ -444,29 +459,35 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
LockPatternUtils.patternToString(pattern));
}
- onPatternChecked(pattern, matched, intent, timeoutMs,
+ mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
localEffectiveUserId);
}
});
}
+ };
- private void onPatternChecked(List<LockPatternView.Cell> pattern,
- boolean matched, Intent intent, int timeoutMs, int effectiveUserId) {
- mLockPatternView.setEnabled(true);
- if (matched) {
- startDisappearAnimation(intent);
+ private void onPatternChecked(boolean matched, Intent intent, int timeoutMs,
+ int effectiveUserId) {
+ mLockPatternView.setEnabled(true);
+ if (matched) {
+ startDisappearAnimation(intent);
+ } else {
+ if (timeoutMs > 0) {
+ long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
+ effectiveUserId, timeoutMs);
+ handleAttemptLockout(deadline);
} else {
- if (timeoutMs > 0) {
- long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
- effectiveUserId, timeoutMs);
- handleAttemptLockout(deadline);
- } else {
- updateStage(Stage.NeedToUnlockWrong);
- postClearPatternRunnable();
- }
+ updateStage(Stage.NeedToUnlockWrong);
+ postClearPatternRunnable();
}
}
- };
+ }
+
+ @Override
+ public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
+ int effectiveUserId) {
+ onPatternChecked(matched, intent, timeoutMs, effectiveUserId);
+ }
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
updateStage(Stage.LockedOut);
@@ -485,7 +506,6 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
@Override
public void onFinish() {
- mNumWrongConfirmAttempts = 0;
updateStage(Stage.NeedToUnlock);
}
}.start();
diff --git a/src/com/android/settings/CredentialCheckResultTracker.java b/src/com/android/settings/CredentialCheckResultTracker.java
new file mode 100644
index 0000000..5c55073
--- /dev/null
+++ b/src/com/android/settings/CredentialCheckResultTracker.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * An invisible retained fragment to track lock check result.
+ */
+public class CredentialCheckResultTracker extends Fragment {
+
+ private Listener mListener;
+ private boolean mHasResult = false;
+
+ private boolean mResultMatched;
+ private Intent mResultData;
+ private int mResultTimeoutMs;
+ private int mResultEffectiveUserId;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setRetainInstance(true);
+ }
+
+ public void setListener(Listener listener) {
+ if (mListener == listener) {
+ return;
+ }
+
+ mListener = listener;
+ if (mListener != null && mHasResult) {
+ mListener.onCredentialChecked(mResultMatched, mResultData, mResultTimeoutMs,
+ mResultEffectiveUserId);
+ }
+ }
+
+ public void setResult(boolean matched, Intent intent, int timeoutMs, int effectiveUserId) {
+ mResultMatched = matched;
+ mResultData = intent;
+ mResultTimeoutMs = timeoutMs;
+ mResultEffectiveUserId = effectiveUserId;
+
+ mHasResult = true;
+ if (mListener != null) {
+ mListener.onCredentialChecked(mResultMatched, mResultData, mResultTimeoutMs,
+ mResultEffectiveUserId);
+ }
+ }
+
+ public void clearResult() {
+ mHasResult = false;
+ mResultMatched = false;
+ mResultData = null;
+ mResultTimeoutMs = 0;
+ mResultEffectiveUserId = 0;
+ }
+
+ interface Listener {
+ public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
+ int effectiveUserId);
+ }
+}
diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java
index 916788d..d27d89b 100644
--- a/src/com/android/settings/DevelopmentSettings.java
+++ b/src/com/android/settings/DevelopmentSettings.java
@@ -162,6 +162,7 @@ public class DevelopmentSettings extends SettingsPreferenceFragment
private static final String USB_CONFIGURATION_KEY = "select_usb_configuration";
private static final String WIFI_LEGACY_DHCP_CLIENT_KEY = "legacy_dhcp_client";
private static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on";
+ private static final String KEY_COLOR_MODE = "color_mode";
private static final String INACTIVE_APPS_KEY = "inactive_apps";
@@ -269,6 +270,7 @@ public class DevelopmentSettings extends SettingsPreferenceFragment
private ListPreference mRootAccess;
private Object mSelectedRootValue;
private PreferenceScreen mDevelopmentTools;
+ private ColorModePreference mColorModePreference;
private final ArrayList<Preference> mAllPrefs = new ArrayList<Preference>();
@@ -439,6 +441,13 @@ public class DevelopmentSettings extends SettingsPreferenceFragment
mDevelopmentTools = (PreferenceScreen) findPreference(DEVELOPMENT_TOOLS);
mAllPrefs.add(mDevelopmentTools);
+
+ mColorModePreference = (ColorModePreference) findPreference(KEY_COLOR_MODE);
+ mColorModePreference.updateCurrentAndSupported();
+ if (mColorModePreference.getTransformsCount() < 2) {
+ removePreference(KEY_COLOR_MODE);
+ mColorModePreference = null;
+ }
}
private ListPreference addListPreference(String prefKey) {
@@ -556,6 +565,19 @@ public class DevelopmentSettings extends SettingsPreferenceFragment
setPrefsEnabledState(mLastEnabledState);
}
mSwitchBar.show();
+
+ if (mColorModePreference != null) {
+ mColorModePreference.startListening();
+ mColorModePreference.updateCurrentAndSupported();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mColorModePreference != null) {
+ mColorModePreference.stopListening();
+ }
}
@Override
diff --git a/src/com/android/settings/DeviceInfoSettings.java b/src/com/android/settings/DeviceInfoSettings.java
index fd3dae1..0cccc01 100644
--- a/src/com/android/settings/DeviceInfoSettings.java
+++ b/src/com/android/settings/DeviceInfoSettings.java
@@ -65,6 +65,7 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In
private static final String FILENAME_PROC_VERSION = "/proc/version";
private static final String FILENAME_MSV = "/sys/board_properties/soc/msv";
+ private static final String KEY_MANUAL = "manual";
private static final String KEY_REGULATORY_INFO = "regulatory_info";
private static final String KEY_SYSTEM_UPDATE_SETTINGS = "system_update_settings";
private static final String PROPERTY_URL_SAFETYLEGAL = "ro.url.safetylegal";
@@ -188,6 +189,9 @@ public class DeviceInfoSettings extends SettingsPreferenceFragment implements In
removePreferenceIfBoolFalse(KEY_UPDATE_SETTING,
R.bool.config_additional_system_update_setting_enable);
+ // Remove manual entry if none present.
+ removePreferenceIfBoolFalse(KEY_MANUAL, R.bool.config_show_manual);
+
// Remove regulatory information if none present or config_show_regulatory_info is disabled
final Intent intent = new Intent(Settings.ACTION_SHOW_REGULATORY_INFO);
if (getPackageManager().queryIntentActivities(intent, 0).isEmpty()
diff --git a/src/com/android/settings/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java
index 6d716f0..6582c26 100644
--- a/src/com/android/settings/DisplaySettings.java
+++ b/src/com/android/settings/DisplaySettings.java
@@ -24,6 +24,9 @@ import com.android.internal.view.RotationPolicy;
import com.android.settings.DropDownPreference.Callback;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
+
+import static android.provider.Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED;
+import static android.provider.Settings.Secure.CAMERA_GESTURE_DISABLED;
import static android.provider.Settings.Secure.DOUBLE_TAP_TO_WAKE;
import static android.provider.Settings.Secure.DOZE_ENABLED;
import static android.provider.Settings.Secure.WAKE_GESTURE_ENABLED;
@@ -85,6 +88,9 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
private static final String KEY_AUTO_BRIGHTNESS = "auto_brightness";
private static final String KEY_AUTO_ROTATE = "auto_rotate";
private static final String KEY_NIGHT_MODE = "night_mode";
+ private static final String KEY_CAMERA_GESTURE = "camera_gesture";
+ private static final String KEY_CAMERA_DOUBLE_TAP_POWER_GESTURE
+ = "camera_double_tap_power_gesture";
private static final String KEY_PROXIMITY_WAKE = "proximity_on_wake";
private static final String KEY_DISPLAY_ROTATION = "display_rotation";
private static final String KEY_WAKE_WHEN_PLUGGED_OR_UNPLUGGED = "wake_when_plugged_or_unplugged";
@@ -120,6 +126,8 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
updateDisplayRotationPreferenceDescription();
}
};
+ private SwitchPreference mCameraGesturePreference;
+ private SwitchPreference mCameraDoubleTapPowerGesturePreference;
@Override
protected int getMetricsCategory() {
@@ -182,6 +190,21 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
removePreference(KEY_DOZE);
}
+ if (isCameraGestureAvailable(getResources())) {
+ mCameraGesturePreference = (SwitchPreference) findPreference(KEY_CAMERA_GESTURE);
+ mCameraGesturePreference.setOnPreferenceChangeListener(this);
+ } else {
+ removePreference(KEY_CAMERA_GESTURE);
+ }
+
+ if (isCameraDoubleTapPowerGestureAvailable(getResources())) {
+ mCameraDoubleTapPowerGesturePreference
+ = (SwitchPreference) findPreference(KEY_CAMERA_DOUBLE_TAP_POWER_GESTURE);
+ mCameraDoubleTapPowerGesturePreference.setOnPreferenceChangeListener(this);
+ } else {
+ removePreference(KEY_CAMERA_DOUBLE_TAP_POWER_GESTURE);
+ }
+
mNightModePreference = (ListPreference) findPreference(KEY_NIGHT_MODE);
if (mNightModePreference != null) {
final UiModeManager uiManager = (UiModeManager) getSystemService(
@@ -275,6 +298,18 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
mDisplayRotationPreference.setSummary(summary);
}
+ private static boolean isCameraGestureAvailable(Resources res) {
+ boolean configSet = res.getInteger(
+ com.android.internal.R.integer.config_cameraLaunchGestureSensorType) != -1;
+ return configSet &&
+ !SystemProperties.getBoolean("gesture.disable_camera_launch", false);
+ }
+
+ private static boolean isCameraDoubleTapPowerGestureAvailable(Resources res) {
+ return res.getBoolean(
+ com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled);
+ }
+
private void updateTimeoutPreferenceDescription(long currentTimeout) {
ListPreference preference = mScreenTimeoutPreference;
String summary;
@@ -421,6 +456,19 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
int value = Settings.Secure.getInt(getContentResolver(), DOUBLE_TAP_TO_WAKE, 0);
mTapToWakePreference.setChecked(value != 0);
}
+
+ // Update camera gesture #1 if it is available.
+ if (mCameraGesturePreference != null) {
+ int value = Settings.Secure.getInt(getContentResolver(), CAMERA_GESTURE_DISABLED, 0);
+ mCameraGesturePreference.setChecked(value == 0);
+ }
+
+ // Update camera gesture #2 if it is available.
+ if (mCameraDoubleTapPowerGesturePreference != null) {
+ int value = Settings.Secure.getInt(
+ getContentResolver(), CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0);
+ mCameraDoubleTapPowerGesturePreference.setChecked(value == 0);
+ }
}
private void updateScreenSaverSummary() {
@@ -499,6 +547,16 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
boolean value = (Boolean) objValue;
Settings.Secure.putInt(getContentResolver(), DOUBLE_TAP_TO_WAKE, value ? 1 : 0);
}
+ if (preference == mCameraGesturePreference) {
+ boolean value = (Boolean) objValue;
+ Settings.Secure.putInt(getContentResolver(), CAMERA_GESTURE_DISABLED,
+ value ? 0 : 1 /* Backwards because setting is for disabling */);
+ }
+ if (preference == mCameraDoubleTapPowerGesturePreference) {
+ boolean value = (Boolean) objValue;
+ Settings.Secure.putInt(getContentResolver(), CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED,
+ value ? 0 : 1 /* Backwards because setting is for disabling */);
+ }
if (preference == mNightModePreference) {
try {
final int value = Integer.parseInt((String) objValue);
@@ -569,6 +627,12 @@ public class DisplaySettings extends SettingsPreferenceFragment implements
com.android.internal.R.bool.config_proximityCheckOnWake)) {
result.add(KEY_PROXIMITY_WAKE);
}
+ if (!isCameraGestureAvailable(context.getResources())) {
+ result.add(KEY_CAMERA_GESTURE);
+ }
+ if (!isCameraDoubleTapPowerGestureAvailable(context.getResources())) {
+ result.add(KEY_CAMERA_DOUBLE_TAP_POWER_GESTURE);
+ }
return result;
}
};
diff --git a/src/com/android/settings/HotspotOffReceiver.java b/src/com/android/settings/HotspotOffReceiver.java
index 06ced1f..f3c3fee 100644
--- a/src/com/android/settings/HotspotOffReceiver.java
+++ b/src/com/android/settings/HotspotOffReceiver.java
@@ -5,6 +5,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager;
+import android.util.Log;
import com.android.settingslib.TetherUtil;
@@ -14,11 +15,15 @@ import com.android.settingslib.TetherUtil;
*/
public class HotspotOffReceiver extends BroadcastReceiver {
+ private static final String TAG = "HotspotOffReceiver";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
@Override
public void onReceive(Context context, Intent intent) {
if (WifiManager.WIFI_AP_STATE_CHANGED_ACTION.equals(intent.getAction())) {
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
if (wifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_DISABLED) {
+ if (DEBUG) Log.d(TAG, "TetherService.cancelRecheckAlarmIfNecessary called");
// The hotspot has been turned off, we don't need to recheck tethering.
TetherService.cancelRecheckAlarmIfNecessary(context, TetherUtil.TETHERING_WIFI);
}
diff --git a/src/com/android/settings/InstrumentedActivity.java b/src/com/android/settings/InstrumentedActivity.java
new file mode 100644
index 0000000..4a0e03a
--- /dev/null
+++ b/src/com/android/settings/InstrumentedActivity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import com.android.internal.logging.MetricsLogger;
+
+import android.app.Activity;
+
+/**
+ * Instrumented activity that logs visibility state.
+ */
+public abstract class InstrumentedActivity extends Activity {
+ /**
+ * Declare the view of this category.
+ *
+ * Categories are defined in {@link com.android.internal.logging.MetricsLogger}
+ * or if there is no relevant existing category you may define one in
+ * {@link com.android.settings.InstrumentedFragment}.
+ */
+ protected abstract int getMetricsCategory();
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ MetricsLogger.visible(this, getMetricsCategory());
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ MetricsLogger.hidden(this, getMetricsCategory());
+ }
+}
diff --git a/src/com/android/settings/ManualDisplayActivity.java b/src/com/android/settings/ManualDisplayActivity.java
new file mode 100644
index 0000000..8be4fee
--- /dev/null
+++ b/src/com/android/settings/ManualDisplayActivity.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.io.File;
+
+/**
+ * The "dialog" that shows from "Manual" in the Settings app.
+ */
+public class ManualDisplayActivity extends Activity {
+ private static final String TAG = "SettingsManualActivity";
+
+ private static final String DEFAULT_MANUAL_PATH = "/system/etc/MANUAL.html.gz";
+ private static final String PROPERTY_MANUAL_PATH = "ro.config.manual_path";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Resources resources = getResources();
+
+ if (!resources.getBoolean(R.bool.config_show_manual)) {
+ finish(); // No manual to display for this device
+ }
+
+ final String path = SystemProperties.get(PROPERTY_MANUAL_PATH, DEFAULT_MANUAL_PATH);
+ if (TextUtils.isEmpty(path)) {
+ Log.e(TAG, "The system property for the manual is empty");
+ showErrorAndFinish();
+ return;
+ }
+
+ final File file = new File(path);
+ if (!file.exists() || file.length() == 0) {
+ Log.e(TAG, "Manual file " + path + " does not exist");
+ showErrorAndFinish();
+ return;
+ }
+
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(Uri.fromFile(file), "text/html");
+
+ intent.putExtra(Intent.EXTRA_TITLE, getString(R.string.settings_manual_activity_title));
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.setPackage("com.android.htmlviewer");
+
+ try {
+ startActivity(intent);
+ finish();
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "Failed to find viewer", e);
+ showErrorAndFinish();
+ }
+ }
+
+ private void showErrorAndFinish() {
+ Toast.makeText(this, R.string.settings_manual_activity_unavailable, Toast.LENGTH_LONG)
+ .show();
+ finish();
+ }
+}
diff --git a/src/com/android/settings/SaveChosenLockWorkerBase.java b/src/com/android/settings/SaveChosenLockWorkerBase.java
new file mode 100644
index 0000000..a40871a
--- /dev/null
+++ b/src/com/android/settings/SaveChosenLockWorkerBase.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings;
+
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.internal.widget.LockPatternUtils;
+
+/**
+ * An invisible retained worker fragment to track the AsyncWork that saves (and optionally
+ * verifies if a challenge is given) the chosen lock credential (pattern/pin/password).
+ */
+abstract class SaveChosenLockWorkerBase extends Fragment {
+
+ private Listener mListener;
+ private boolean mFinished;
+ private Intent mResultData;
+
+ protected LockPatternUtils mUtils;
+ protected boolean mHasChallenge;
+ protected long mChallenge;
+ protected boolean mWasSecureBefore;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setRetainInstance(true);
+ }
+
+ public void setListener(Listener listener) {
+ if (mListener == listener) {
+ return;
+ }
+
+ mListener = listener;
+ if (mFinished && mListener != null) {
+ mListener.onChosenLockSaveFinished(mWasSecureBefore, mResultData);
+ }
+ }
+
+ protected void prepare(LockPatternUtils utils, boolean credentialRequired,
+ boolean hasChallenge, long challenge) {
+ mUtils = utils;
+
+ mHasChallenge = hasChallenge;
+ mChallenge = challenge;
+ mWasSecureBefore = mUtils.isSecure(UserHandle.myUserId());
+
+ mUtils.setCredentialRequiredToDecrypt(credentialRequired);
+
+ mFinished = false;
+ mResultData = null;
+ }
+
+ protected void start() {
+ new Task().execute();
+ }
+
+ /**
+ * Executes the save and verify work in background.
+ * @return Intent with challenge token or null.
+ */
+ protected abstract Intent saveAndVerifyInBackground();
+
+ protected void finish(Intent resultData) {
+ mFinished = true;
+ mResultData = resultData;
+ if (mListener != null) {
+ mListener.onChosenLockSaveFinished(mWasSecureBefore, mResultData);
+ }
+ }
+
+ private class Task extends AsyncTask<Void, Void, Intent> {
+ @Override
+ protected Intent doInBackground(Void... params){
+ return saveAndVerifyInBackground();
+ }
+
+ @Override
+ protected void onPostExecute(Intent resultData) {
+ finish(resultData);
+ }
+ }
+
+ interface Listener {
+ public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData);
+ }
+}
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index 6e679b6..892d61f 100644
--- a/src/com/android/settings/SecuritySettings.java
+++ b/src/com/android/settings/SecuritySettings.java
@@ -361,6 +361,8 @@ public class SecuritySettings extends SettingsPreferenceFragment
fingerprintCount, fingerprintCount));
clazz = FingerprintSettings.class.getName();
} else {
+ fingerprintPreference.setSummary(
+ R.string.security_settings_fingerprint_preference_summary_none);
clazz = FingerprintEnrollIntroduction.class.getName();
}
intent.setClassName("com.android.settings", clazz);
@@ -795,10 +797,16 @@ public class SecuritySettings extends SettingsPreferenceFragment
FingerprintManager fpm =
(FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
if (fpm.isHardwareDetected()) {
+ // This catches the title which can be overloaded in an overlay
data = new SearchIndexableRaw(context);
data.title = res.getString(R.string.security_settings_fingerprint_preference_title);
data.screenTitle = screenTitle;
result.add(data);
+ // Fallback for when the above doesn't contain "fingerprint"
+ data = new SearchIndexableRaw(context);
+ data.title = res.getString(R.string.fingerprint_manage_category_title);
+ data.screenTitle = screenTitle;
+ result.add(data);
}
// Credential storage
diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java
index baf04d4..b957c38 100644
--- a/src/com/android/settings/SettingsPreferenceFragment.java
+++ b/src/com/android/settings/SettingsPreferenceFragment.java
@@ -505,7 +505,10 @@ public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceF
}
public void finish() {
- getActivity().onBackPressed();
+ Activity activity = getActivity();
+ if (activity != null) {
+ activity.onBackPressed();
+ }
}
public boolean startFragment(Fragment caller, String fragmentClass, int titleRes,
diff --git a/src/com/android/settings/TetherService.java b/src/com/android/settings/TetherService.java
index 459dc27..346a175 100644
--- a/src/com/android/settings/TetherService.java
+++ b/src/com/android/settings/TetherService.java
@@ -69,7 +69,7 @@ public class TetherService extends Service {
@Override
public void onCreate() {
super.onCreate();
- if (DEBUG) Log.d(TAG, "Creating WifiProvisionService");
+ if (DEBUG) Log.d(TAG, "Creating TetherService");
String provisionResponse = getResources().getString(
com.android.internal.R.string.config_mobile_hotspot_provision_response);
registerReceiver(mReceiver, new IntentFilter(provisionResponse),
@@ -89,21 +89,28 @@ public class TetherService extends Service {
mCurrentTethers.add(type);
}
}
+
if (intent.hasExtra(TetherUtil.EXTRA_REM_TETHER_TYPE)) {
- int type = intent.getIntExtra(TetherUtil.EXTRA_REM_TETHER_TYPE,
- TetherUtil.TETHERING_INVALID);
- if (DEBUG) Log.d(TAG, "Removing tether " + type);
- int index = mCurrentTethers.indexOf(type);
- if (index >= 0) {
- mCurrentTethers.remove(index);
- // If we are currently in the middle of a check, we may need to adjust the
- // index accordingly.
- if (index <= mCurrentTypeIndex && mCurrentTypeIndex > 0) {
- mCurrentTypeIndex--;
+ if (!mInProvisionCheck) {
+ int type = intent.getIntExtra(TetherUtil.EXTRA_REM_TETHER_TYPE,
+ TetherUtil.TETHERING_INVALID);
+ int index = mCurrentTethers.indexOf(type);
+ if (DEBUG) Log.d(TAG, "Removing tether " + type + ", index " + index);
+ if (index >= 0) {
+ mCurrentTethers.remove(index);
+ // If we are currently in the middle of a check, we may need to adjust the
+ // index accordingly.
+ if (DEBUG) Log.d(TAG, "mCurrentTypeIndex: " + mCurrentTypeIndex);
+ if (index <= mCurrentTypeIndex && mCurrentTypeIndex > 0) {
+ mCurrentTypeIndex--;
+ }
}
+ cancelAlarmIfNecessary();
+ } else {
+ if (DEBUG) Log.d(TAG, "Don't cancel alarm during provisioning");
}
- cancelAlarmIfNecessary();
}
+
// Only set the alarm if we have one tether, meaning the one just added,
// to avoid setting it when it was already set previously for another
// type.
@@ -125,7 +132,7 @@ public class TetherService extends Service {
}
// We want to be started if we are killed accidently, so that we can be sure we finish
// the check.
- return START_STICKY;
+ return START_REDELIVER_INTENT;
}
@Override
@@ -137,7 +144,7 @@ public class TetherService extends Service {
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
prefs.edit().putString(KEY_TETHERS, tethersToString(mCurrentTethers)).commit();
- if (DEBUG) Log.d(TAG, "Destroying WifiProvisionService");
+ if (DEBUG) Log.d(TAG, "Destroying TetherService");
unregisterReceiver(mReceiver);
super.onDestroy();
}
@@ -199,15 +206,17 @@ public class TetherService extends Service {
}
private void startProvisioning(int index) {
- String provisionAction = getResources().getString(
- com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui);
- if (DEBUG) Log.d(TAG, "Sending provisioning broadcast: " + provisionAction + " type: "
- + mCurrentTethers.get(index));
- Intent intent = new Intent(provisionAction);
- intent.putExtra(TETHER_CHOICE, mCurrentTethers.get(index));
- intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- sendBroadcast(intent);
- mInProvisionCheck = true;
+ if (index < mCurrentTethers.size()) {
+ String provisionAction = getResources().getString(
+ com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui);
+ if (DEBUG) Log.d(TAG, "Sending provisioning broadcast: " + provisionAction + " type: "
+ + mCurrentTethers.get(index));
+ Intent intent = new Intent(provisionAction);
+ intent.putExtra(TETHER_CHOICE, mCurrentTethers.get(index));
+ intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ sendBroadcast(intent);
+ mInProvisionCheck = true;
+ }
}
public static void scheduleRecheckAlarm(Context context, int type) {
@@ -261,9 +270,14 @@ public class TetherService extends Service {
if (DEBUG) Log.d(TAG, "Got provision result " + intent);
String provisionResponse = context.getResources().getString(
com.android.internal.R.string.config_mobile_hotspot_provision_response);
+
if (provisionResponse.equals(intent.getAction())) {
- mInProvisionCheck = false;
+ if (!mInProvisionCheck) {
+ Log.e(TAG, "Unexpected provision response " + intent);
+ return;
+ }
int checkType = mCurrentTethers.get(mCurrentTypeIndex);
+ mInProvisionCheck = false;
if (intent.getIntExtra(EXTRA_RESULT, RESULT_DEFAULT) == RESULT_OK) {
if (checkType == TetherUtil.TETHERING_WIFI && mEnableWifiAfterCheck) {
enableWifiTetheringIfNeeded();
@@ -282,7 +296,8 @@ public class TetherService extends Service {
break;
}
}
- if (++mCurrentTypeIndex == mCurrentTethers.size()) {
+
+ if (++mCurrentTypeIndex >= mCurrentTethers.size()) {
// We are done with all checks, time to die.
stopSelf();
} else {
diff --git a/src/com/android/settings/deviceinfo/UsbBackend.java b/src/com/android/settings/deviceinfo/UsbBackend.java
new file mode 100644
index 0000000..210e0a0
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/UsbBackend.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.deviceinfo;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
+import android.os.UserManager;
+
+public class UsbBackend {
+
+ private static final int MODE_POWER_MASK = 0x01;
+ public static final int MODE_POWER_SINK = 0x00;
+ public static final int MODE_POWER_SOURCE = 0x01;
+
+ private static final int MODE_DATA_MASK = 0x03 << 1;
+ public static final int MODE_DATA_NONE = 0x00 << 1;
+ public static final int MODE_DATA_MTP = 0x01 << 1;
+ public static final int MODE_DATA_PTP = 0x02 << 1;
+ public static final int MODE_DATA_MIDI = 0x03 << 1;
+
+ private final boolean mRestricted;
+
+ private UserManager mUserManager;
+ private UsbManager mUsbManager;
+ private UsbPort mPort;
+ private UsbPortStatus mPortStatus;
+
+ private boolean mIsUnlocked;
+
+ public UsbBackend(Context context) {
+ Intent intent = context.registerReceiver(null,
+ new IntentFilter(UsbManager.ACTION_USB_STATE));
+ mIsUnlocked = intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false);
+
+ mUserManager = UserManager.get(context);
+ mUsbManager = context.getSystemService(UsbManager.class);
+
+ mRestricted = mUserManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
+ UsbPort[] ports = mUsbManager.getPorts();
+ // For now look for a connected port, in the future we should identify port in the
+ // notification and pick based on that.
+ final int N = ports.length;
+ for (int i = 0; i < N; i++) {
+ UsbPortStatus status = mUsbManager.getPortStatus(ports[i]);
+ if (status.isConnected()) {
+ mPort = ports[i];
+ mPortStatus = status;
+ break;
+ }
+ }
+ }
+
+ public int getCurrentMode() {
+ if (mPort != null) {
+ int power = mPortStatus.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE
+ ? MODE_POWER_SOURCE : MODE_POWER_SINK;
+ return power | getUsbDataMode();
+ }
+ return MODE_POWER_SINK | getUsbDataMode();
+ }
+
+ public int getUsbDataMode() {
+ if (!mIsUnlocked) {
+ return MODE_DATA_NONE;
+ } else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MTP)) {
+ return MODE_DATA_MTP;
+ } else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP)) {
+ return MODE_DATA_PTP;
+ } else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MIDI)) {
+ return MODE_DATA_MIDI;
+ }
+ return MODE_DATA_NONE; // ...
+ }
+
+ private void setUsbFunction(int mode) {
+ switch (mode) {
+ case MODE_DATA_MTP:
+ mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP);
+ mUsbManager.setUsbDataUnlocked(true);
+ break;
+ case MODE_DATA_PTP:
+ mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_PTP);
+ mUsbManager.setUsbDataUnlocked(true);
+ break;
+ case MODE_DATA_MIDI:
+ mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MIDI);
+ mUsbManager.setUsbDataUnlocked(true);
+ break;
+ default:
+ mUsbManager.setCurrentFunction(null);
+ mUsbManager.setUsbDataUnlocked(false);
+ break;
+ }
+ }
+
+ public void setMode(int mode) {
+ if (mPort != null) {
+ int powerRole = modeToPower(mode);
+ // If we aren't using any data modes and we support host mode, then go to host mode
+ // so maybe? the other device can provide data if it wants, otherwise go into device
+ // mode because we have no choice.
+ int dataRole = (mode & MODE_DATA_MASK) == MODE_DATA_NONE
+ && mPortStatus.isRoleCombinationSupported(powerRole, UsbPort.DATA_ROLE_HOST)
+ ? UsbPort.DATA_ROLE_HOST : UsbPort.DATA_ROLE_DEVICE;
+ mUsbManager.setPortRoles(mPort, powerRole, dataRole);
+ }
+ setUsbFunction(mode & MODE_DATA_MASK);
+ }
+
+ private int modeToPower(int mode) {
+ return (mode & MODE_POWER_MASK) == MODE_POWER_SOURCE
+ ? UsbPort.POWER_ROLE_SOURCE : UsbPort.POWER_ROLE_SINK;
+ }
+
+ public boolean isModeSupported(int mode) {
+ if (mRestricted && (mode & MODE_DATA_MASK) != MODE_DATA_NONE
+ && (mode & MODE_DATA_MASK) != MODE_DATA_MIDI) {
+ // No USB data modes are supported.
+ return false;
+ }
+ if (mPort != null) {
+ int power = modeToPower(mode);
+ if ((mode & MODE_DATA_MASK) != 0) {
+ // We have a port and data, need to be in device mode.
+ return mPortStatus.isRoleCombinationSupported(power,
+ UsbPort.DATA_ROLE_DEVICE);
+ } else {
+ // No data needed, we can do this power mode in either device or host.
+ return mPortStatus.isRoleCombinationSupported(power, UsbPort.DATA_ROLE_DEVICE)
+ || mPortStatus.isRoleCombinationSupported(power, UsbPort.DATA_ROLE_HOST);
+ }
+ }
+ // No port, support sink modes only.
+ return (mode & MODE_POWER_MASK) != MODE_POWER_SOURCE;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/settings/deviceinfo/UsbModeChooserActivity.java b/src/com/android/settings/deviceinfo/UsbModeChooserActivity.java
index 4bdabd5..77fc388 100644
--- a/src/com/android/settings/deviceinfo/UsbModeChooserActivity.java
+++ b/src/com/android/settings/deviceinfo/UsbModeChooserActivity.java
@@ -20,13 +20,14 @@ import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
-import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.hardware.usb.UsbManager;
import android.os.Bundle;
-import android.os.UserManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Checkable;
+import android.widget.LinearLayout;
+import android.widget.TextView;
import com.android.settings.R;
@@ -36,83 +37,102 @@ import com.android.settings.R;
*/
public class UsbModeChooserActivity extends Activity {
- private UsbManager mUsbManager;
- private String[] mFunctions;
- private boolean mIsUnlocked;
+ public static final int[] DEFAULT_MODES = {
+ UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_NONE,
+ UsbBackend.MODE_POWER_SOURCE | UsbBackend.MODE_DATA_NONE,
+ UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_MTP,
+ UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_PTP,
+ UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_MIDI
+ };
+
+ private UsbBackend mBackend;
+ private AlertDialog mDialog;
+ private LayoutInflater mLayoutInflater;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
- Intent i = getBaseContext().registerReceiver(null, new IntentFilter(UsbManager.ACTION_USB_STATE));
- mIsUnlocked = i.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false);
super.onCreate(savedInstanceState);
- mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
- boolean isFileTransferRestricted = ((UserManager) getSystemService(Context.USER_SERVICE))
- .hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
- CharSequence[] items;
- if (isFileTransferRestricted) {
- items = new CharSequence[] { getText(R.string.usb_use_charging_only), getText(R.string.usb_use_MIDI)};
- mFunctions = new String[] { null, UsbManager.USB_FUNCTION_MIDI };
- } else {
- items = new CharSequence[] {
- getText(R.string.usb_use_charging_only), getText(R.string.usb_use_file_transfers),
- getText(R.string.usb_use_photo_transfers), getText(R.string.usb_use_MIDI)};
- mFunctions = new String[] { null, UsbManager.USB_FUNCTION_MTP,
- UsbManager.USB_FUNCTION_PTP, UsbManager.USB_FUNCTION_MIDI };
- }
- final AlertDialog levelDialog;
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.usb_use);
+ mLayoutInflater = LayoutInflater.from(this);
- builder.setSingleChoiceItems(items, getCurrentFunction(),
- new DialogInterface.OnClickListener() {
+ mDialog = new AlertDialog.Builder(this)
+ .setTitle(R.string.usb_use)
+ .setView(R.layout.usb_dialog_container)
+ .setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ finish();
+ }
+ })
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- if (!ActivityManager.isUserAMonkey()) {
- setCurrentFunction(which);
- }
- dialog.dismiss();
- UsbModeChooserActivity.this.finish();
+ finish();
}
- });
- builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- UsbModeChooserActivity.this.finish();
+ }).create();
+ mDialog.show();
+
+ LinearLayout container = (LinearLayout) mDialog.findViewById(R.id.container);
+
+ mBackend = new UsbBackend(this);
+ int current = mBackend.getCurrentMode();
+ for (int i = 0; i < DEFAULT_MODES.length; i++) {
+ if (mBackend.isModeSupported(DEFAULT_MODES[i])) {
+ inflateOption(DEFAULT_MODES[i], current == DEFAULT_MODES[i], container);
}
- });
- builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ }
+ }
+
+ private void inflateOption(final int mode, boolean selected, LinearLayout container) {
+ View v = mLayoutInflater.inflate(R.layout.radio_with_summary, container, false);
+
+ ((TextView) v.findViewById(android.R.id.title)).setText(getTitle(mode));
+ ((TextView) v.findViewById(android.R.id.summary)).setText(getSummary(mode));
+
+ v.setOnClickListener(new OnClickListener() {
@Override
- public void onClick(DialogInterface dialog, int which) {
- UsbModeChooserActivity.this.finish();
+ public void onClick(View v) {
+ if (!ActivityManager.isUserAMonkey()) {
+ mBackend.setMode(mode);
+ }
+ mDialog.dismiss();
+ finish();
}
});
- levelDialog = builder.create();
- levelDialog.show();
+ ((Checkable) v).setChecked(selected);
+ container.addView(v);
}
- private int getCurrentFunction() {
- if (!mIsUnlocked) {
- return 0;
- }
-
- for (int i = 1; i < mFunctions.length; i++) {
- if (mUsbManager.isFunctionEnabled(mFunctions[i])) {
- return i;
- }
+ private static int getSummary(int mode) {
+ switch (mode) {
+ case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_NONE:
+ return R.string.usb_use_charging_only_desc;
+ case UsbBackend.MODE_POWER_SOURCE | UsbBackend.MODE_DATA_NONE:
+ return R.string.usb_use_power_only_desc;
+ case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_MTP:
+ return R.string.usb_use_file_transfers_desc;
+ case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_PTP:
+ return R.string.usb_use_photo_transfers_desc;
+ case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_MIDI:
+ return R.string.usb_use_MIDI_desc;
}
return 0;
}
- private void setCurrentFunction(int which) {
- if (which == 0) {
- mUsbManager.setCurrentFunction(null);
- mUsbManager.setUsbDataUnlocked(false);
- return;
+ private static int getTitle(int mode) {
+ switch (mode) {
+ case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_NONE:
+ return R.string.usb_use_charging_only;
+ case UsbBackend.MODE_POWER_SOURCE | UsbBackend.MODE_DATA_NONE:
+ return R.string.usb_use_power_only;
+ case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_MTP:
+ return R.string.usb_use_file_transfers;
+ case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_PTP:
+ return R.string.usb_use_photo_transfers;
+ case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_MIDI:
+ return R.string.usb_use_MIDI;
}
-
- mUsbManager.setCurrentFunction(mFunctions[which]);
- mUsbManager.setUsbDataUnlocked(true);
+ return 0;
}
}
diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollBase.java b/src/com/android/settings/fingerprint/FingerprintEnrollBase.java
index 430f220..d164800 100644
--- a/src/com/android/settings/fingerprint/FingerprintEnrollBase.java
+++ b/src/com/android/settings/fingerprint/FingerprintEnrollBase.java
@@ -28,6 +28,7 @@ import android.widget.Button;
import android.widget.TextView;
import com.android.settings.ChooseLockSettingsHelper;
+import com.android.settings.InstrumentedActivity;
import com.android.settings.R;
import com.android.setupwizardlib.SetupWizardLayout;
import com.android.setupwizardlib.view.NavigationBar;
@@ -35,24 +36,11 @@ import com.android.setupwizardlib.view.NavigationBar;
/**
* Base activity for all fingerprint enrollment steps.
*/
-public class FingerprintEnrollBase extends Activity implements View.OnClickListener {
-
- /**
- * Used by the choose fingerprint wizard to indicate the wizard is
- * finished, and each activity in the wizard should finish.
- * <p>
- * Previously, each activity in the wizard would finish itself after
- * starting the next activity. However, this leads to broken 'Back'
- * behavior. So, now an activity does not finish itself until it gets this
- * result.
- */
- protected static final int RESULT_FINISHED = RESULT_FIRST_USER;
-
- /**
- * Used by the enrolling screen during setup wizard to skip over setting up fingerprint, which
- * will be useful if the user accidentally entered this flow.
- */
- protected static final int RESULT_SKIP = RESULT_FIRST_USER + 1;
+public abstract class FingerprintEnrollBase extends InstrumentedActivity
+ implements View.OnClickListener {
+ static final int RESULT_FINISHED = FingerprintSettings.RESULT_FINISHED;
+ static final int RESULT_SKIP = FingerprintSettings.RESULT_SKIP;
+ static final int RESULT_TIMEOUT = FingerprintSettings.RESULT_TIMEOUT;
protected byte[] mToken;
diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java
index 5d4edbd..fe0bb63 100644
--- a/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java
@@ -20,6 +20,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
+import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
@@ -29,6 +30,7 @@ import android.content.res.ColorStateList;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
@@ -38,6 +40,7 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.internal.logging.MetricsLogger;
import com.android.settings.ChooseLockSettingsHelper;
import com.android.settings.R;
@@ -244,9 +247,22 @@ public class FingerprintEnrollEnrolling extends FingerprintEnrollBase
}
@Override
- public void onEnrollmentError(CharSequence errString) {
- showError(errString);
+ public void onEnrollmentError(int errMsgId, CharSequence errString) {
+ int msgId;
+ switch (errMsgId) {
+ case FingerprintManager.FINGERPRINT_ERROR_TIMEOUT:
+ // This message happens when the underlying crypto layer decides to revoke the
+ // enrollment auth token.
+ msgId = R.string.security_settings_fingerprint_enroll_error_timeout_dialog_message;
+ break;
+ default:
+ // There's nothing specific to tell the user about. Ask them to try again.
+ msgId = R.string.security_settings_fingerprint_enroll_error_generic_dialog_message;
+ break;
+ }
+ showErrorDialog(getText(msgId), errMsgId);
stopIconAnimation();
+ mErrorText.removeCallbacks(mTouchAgainRunnable);
}
@Override
@@ -277,6 +293,11 @@ public class FingerprintEnrollEnrolling extends FingerprintEnrollBase
return PROGRESS_BAR_MAX * progress / (steps + 1);
}
+ private void showErrorDialog(CharSequence msg, int msgId) {
+ ErrorDialog dlg = ErrorDialog.newInstance(msg, msgId);
+ dlg.show(getFragmentManager(), ErrorDialog.class.getName());
+ }
+
private void showIconTouchDialog() {
mIconTouchCount = 0;
new IconTouchDialog().show(getFragmentManager(), null /* tag */);
@@ -380,6 +401,11 @@ public class FingerprintEnrollEnrolling extends FingerprintEnrollBase
}
};
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsLogger.FINGERPRINT_ENROLLING;
+ }
+
public static class IconTouchDialog extends DialogFragment {
@Override
@@ -397,4 +423,49 @@ public class FingerprintEnrollEnrolling extends FingerprintEnrollBase
return builder.create();
}
}
+
+ public static class ErrorDialog extends DialogFragment {
+
+ /**
+ * Create a new instance of ErrorDialog.
+ *
+ * @param msg the string to show for message text
+ * @param msgId the FingerprintManager error id so we know the cause
+ * @return a new ErrorDialog
+ */
+ static ErrorDialog newInstance(CharSequence msg, int msgId) {
+ ErrorDialog dlg = new ErrorDialog();
+ Bundle args = new Bundle();
+ args.putCharSequence("error_msg", msg);
+ args.putInt("error_id", msgId);
+ dlg.setArguments(args);
+ return dlg;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ CharSequence errorString = getArguments().getCharSequence("error_msg");
+ final int errMsgId = getArguments().getInt("error_id");
+ builder.setTitle(R.string.security_settings_fingerprint_enroll_error_dialog_title)
+ .setMessage(errorString)
+ .setCancelable(false)
+ .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ boolean wasTimeout =
+ errMsgId == FingerprintManager.FINGERPRINT_ERROR_TIMEOUT;
+ Activity activity = getActivity();
+ activity.setResult(wasTimeout ?
+ RESULT_TIMEOUT : RESULT_FINISHED);
+ activity.finish();
+ }
+ });
+ AlertDialog dialog = builder.create();
+ dialog.setCanceledOnTouchOutside(false);
+ return dialog;
+ }
+ }
}
diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java
index bbed42c..63d9335 100644
--- a/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java
@@ -16,11 +16,11 @@
package com.android.settings.fingerprint;
-import android.content.Context;
import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
+import com.android.internal.logging.MetricsLogger;
import com.android.settings.ChooseLockSettingsHelper;
import com.android.settings.R;
@@ -31,15 +31,19 @@ public class FingerprintEnrollFindSensor extends FingerprintEnrollBase {
private static final int CONFIRM_REQUEST = 1;
private static final int ENROLLING = 2;
+ public static final String EXTRA_KEY_LAUNCHED_CONFIRM = "launched_confirm_lock";
private FingerprintLocationAnimationView mAnimation;
+ private boolean mLaunchedConfirmLock;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fingerprint_enroll_find_sensor);
setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title);
- if (mToken == null) {
+ mLaunchedConfirmLock = savedInstanceState != null && savedInstanceState.getBoolean(
+ EXTRA_KEY_LAUNCHED_CONFIRM);
+ if (mToken == null && !mLaunchedConfirmLock) {
launchConfirmLock();
}
mAnimation = (FingerprintLocationAnimationView) findViewById(
@@ -59,6 +63,12 @@ public class FingerprintEnrollFindSensor extends FingerprintEnrollBase {
}
@Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(EXTRA_KEY_LAUNCHED_CONFIRM, mLaunchedConfirmLock);
+ }
+
+ @Override
protected void onNextButtonClick() {
startActivityForResult(getEnrollingIntent(), ENROLLING);
}
@@ -79,6 +89,9 @@ public class FingerprintEnrollFindSensor extends FingerprintEnrollBase {
} else if (resultCode == RESULT_SKIP) {
setResult(RESULT_SKIP);
finish();
+ } else if (resultCode == RESULT_TIMEOUT) {
+ setResult(RESULT_TIMEOUT);
+ finish();
} else {
FingerprintManager fpm = getSystemService(FingerprintManager.class);
int enrolled = fpm.getEnrolledFingerprints().size();
@@ -103,6 +116,13 @@ public class FingerprintEnrollFindSensor extends FingerprintEnrollBase {
// This shouldn't happen, as we should only end up at this step if a lock thingy is
// already set.
finish();
+ } else {
+ mLaunchedConfirmLock = true;
}
}
+
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsLogger.FINGERPRINT_FIND_SENSOR;
+ }
}
diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollFinish.java b/src/com/android/settings/fingerprint/FingerprintEnrollFinish.java
index 29f14d7..6691e20 100644
--- a/src/com/android/settings/fingerprint/FingerprintEnrollFinish.java
+++ b/src/com/android/settings/fingerprint/FingerprintEnrollFinish.java
@@ -25,6 +25,7 @@ import android.preference.Preference;
import android.view.View;
import android.widget.Button;
+import com.android.internal.logging.MetricsLogger;
import com.android.settings.R;
import com.android.settings.fingerprint.FingerprintSettings.FingerprintPreference;
@@ -70,4 +71,9 @@ public class FingerprintEnrollFinish extends FingerprintEnrollBase {
}
super.onClick(v);
}
+
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsLogger.FINGERPRINT_ENROLL_FINISH;
+ }
}
diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/fingerprint/FingerprintEnrollIntroduction.java
index de5bf24..beb1a8f 100644
--- a/src/com/android/settings/fingerprint/FingerprintEnrollIntroduction.java
+++ b/src/com/android/settings/fingerprint/FingerprintEnrollIntroduction.java
@@ -22,6 +22,7 @@ import android.os.Bundle;
import android.os.UserHandle;
import android.view.View;
+import com.android.internal.logging.MetricsLogger;
import com.android.settings.ChooseLockSettingsHelper;
import com.android.settings.HelpUtils;
import com.android.settings.R;
@@ -92,4 +93,9 @@ public class FingerprintEnrollIntroduction extends FingerprintEnrollBase {
getString(R.string.help_url_fingerprint), getClass().getName());
startActivity(helpIntent);
}
+
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsLogger.FINGERPRINT_ENROLL_INTRO;
+ }
}
diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollOnboard.java b/src/com/android/settings/fingerprint/FingerprintEnrollOnboard.java
index e81b0ff..0990459 100644
--- a/src/com/android/settings/fingerprint/FingerprintEnrollOnboard.java
+++ b/src/com/android/settings/fingerprint/FingerprintEnrollOnboard.java
@@ -21,6 +21,7 @@ import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
+import com.android.internal.logging.MetricsLogger;
import com.android.settings.ChooseLockGeneric;
import com.android.settings.ChooseLockSettingsHelper;
import com.android.settings.R;
@@ -82,4 +83,9 @@ public class FingerprintEnrollOnboard extends FingerprintEnrollBase {
protected Intent getFindSensorIntent() {
return new Intent(this, FingerprintEnrollFindSensor.class);
}
+
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsLogger.FINGERPRINT_ENROLL_ONBOARD;
+ }
}
diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollSidecar.java b/src/com/android/settings/fingerprint/FingerprintEnrollSidecar.java
index 6a47dc4..5feb08c 100644
--- a/src/com/android/settings/fingerprint/FingerprintEnrollSidecar.java
+++ b/src/com/android/settings/fingerprint/FingerprintEnrollSidecar.java
@@ -24,12 +24,14 @@ import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
+import com.android.internal.logging.MetricsLogger;
import com.android.settings.ChooseLockSettingsHelper;
+import com.android.settings.InstrumentedFragment;
/**
* Sidecar fragment to handle the state around fingerprint enrollment.
*/
-public class FingerprintEnrollSidecar extends Fragment {
+public class FingerprintEnrollSidecar extends InstrumentedFragment {
private int mEnrollmentSteps = -1;
private int mEnrollmentRemaining = 0;
@@ -128,7 +130,7 @@ public class FingerprintEnrollSidecar extends Fragment {
@Override
public void onEnrollmentError(int errMsgId, CharSequence errString) {
if (mListener != null) {
- mListener.onEnrollmentError(errString);
+ mListener.onEnrollmentError(errMsgId, errString);
}
}
};
@@ -140,9 +142,14 @@ public class FingerprintEnrollSidecar extends Fragment {
}
};
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsLogger.FINGERPRINT_ENROLL_SIDECAR;
+ }
+
public interface Listener {
void onEnrollmentHelp(CharSequence helpString);
- void onEnrollmentError(CharSequence errString);
+ void onEnrollmentError(int errMsgId, CharSequence errString);
void onEnrollmentProgressChange(int steps, int remaining);
}
}
diff --git a/src/com/android/settings/fingerprint/FingerprintSettings.java b/src/com/android/settings/fingerprint/FingerprintSettings.java
index a5c9963..cacd5dd 100644
--- a/src/com/android/settings/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/fingerprint/FingerprintSettings.java
@@ -70,8 +70,9 @@ import java.util.List;
* Settings screen for fingerprints
*/
public class FingerprintSettings extends SubSettings {
+
/**
- * Used by the FP settings wizard to indicate the wizard is
+ * Used by the choose fingerprint wizard to indicate the wizard is
* finished, and each activity in the wizard should finish.
* <p>
* Previously, each activity in the wizard would finish itself after
@@ -79,7 +80,21 @@ public class FingerprintSettings extends SubSettings {
* behavior. So, now an activity does not finish itself until it gets this
* result.
*/
- static final int RESULT_FINISHED = RESULT_FIRST_USER;
+ protected static final int RESULT_FINISHED = RESULT_FIRST_USER;
+
+ /**
+ * Used by the enrolling screen during setup wizard to skip over setting up fingerprint, which
+ * will be useful if the user accidentally entered this flow.
+ */
+ protected static final int RESULT_SKIP = RESULT_FIRST_USER + 1;
+
+ /**
+ * Like {@link #RESULT_FINISHED} except this one indicates enrollment failed because the
+ * device was left idle. This is used to clear the credential token to require the user to
+ * re-enter their pin/pattern/password before continuing.
+ */
+ protected static final int RESULT_TIMEOUT = RESULT_FIRST_USER + 2;
+
private static final long LOCKOUT_DURATION = 30000; // time we have to wait for fp to reset, ms
@Override
@@ -181,6 +196,7 @@ public class FingerprintSettings extends SubSettings {
case MSG_REFRESH_FINGERPRINT_TEMPLATES:
removeFingerprintPreference(msg.arg1);
updateAddPreference();
+ retryFingerprint();
break;
case MSG_FINGER_AUTH_SUCCESS:
mFingerprintCancel = null;
@@ -440,6 +456,12 @@ public class FingerprintSettings extends SubSettings {
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
}
}
+ } else if (requestCode == ADD_FINGERPRINT_REQUEST) {
+ if (resultCode == RESULT_TIMEOUT) {
+ Activity activity = getActivity();
+ activity.setResult(RESULT_TIMEOUT);
+ activity.finish();
+ }
}
if (mToken == null) {
@@ -480,10 +502,10 @@ public class FingerprintSettings extends SubSettings {
highlight.setHotspot(centerX, centerY);
view.setBackground(highlight);
view.setPressed(true);
+ view.setPressed(false);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
- view.setPressed(false);
view.setBackground(null);
}
}, RESET_HIGHLIGHT_DELAY_MS);
@@ -556,6 +578,9 @@ public class FingerprintSettings extends SubSettings {
if (DEBUG) {
Log.v(TAG, "rename " + name + " to " + newName);
}
+ MetricsLogger.action(getContext(),
+ MetricsLogger.ACTION_FINGERPRINT_RENAME,
+ mFp.getFingerId());
FingerprintSettingsFragment parent
= (FingerprintSettingsFragment)
getTargetFragment();
@@ -598,6 +623,8 @@ public class FingerprintSettings extends SubSettings {
private void onDeleteClick(DialogInterface dialog) {
if (DEBUG) Log.v(TAG, "Removing fpId=" + mFp.getFingerId());
+ MetricsLogger.action(getContext(), MetricsLogger.ACTION_FINGERPRINT_DELETE,
+ mFp.getFingerId());
FingerprintSettingsFragment parent
= (FingerprintSettingsFragment) getTargetFragment();
if (parent.mFingerprintManager.getEnrolledFingerprints().size() > 1) {
diff --git a/src/com/android/settings/fingerprint/SetupFingerprintEnrollEnrolling.java b/src/com/android/settings/fingerprint/SetupFingerprintEnrollEnrolling.java
index fc7b803..b57bb8d 100644
--- a/src/com/android/settings/fingerprint/SetupFingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/fingerprint/SetupFingerprintEnrollEnrolling.java
@@ -16,18 +16,29 @@
package com.android.settings.fingerprint;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
+import android.os.Bundle;
import android.view.View;
import android.widget.Button;
+import com.android.internal.logging.MetricsLogger;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
+import com.android.setupwizardlib.util.SystemBarHelper;
import com.android.setupwizardlib.view.NavigationBar;
public class SetupFingerprintEnrollEnrolling extends FingerprintEnrollEnrolling
implements NavigationBar.NavigationBarListener {
+ private static final String TAG_DIALOG = "dialog";
+
@Override
protected Intent getFinishIntent() {
final Intent intent = new Intent(this, SetupFingerprintEnrollFinish.class);
@@ -68,7 +79,53 @@ public class SetupFingerprintEnrollEnrolling extends FingerprintEnrollEnrolling
@Override
public void onNavigateNext() {
- setResult(RESULT_SKIP);
- finish();
+ new SkipDialog().show(getFragmentManager(), TAG_DIALOG);
+ }
+
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsLogger.FINGERPRINT_ENROLLING_SETUP;
+ }
+
+ public static class SkipDialog extends DialogFragment {
+
+ @Override
+ public void show(FragmentManager manager, String tag) {
+ if (manager.findFragmentByTag(tag) == null) {
+ super.show(manager, tag);
+ }
+ }
+
+ public SkipDialog() {
+ // no-arg constructor for fragment
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final AlertDialog dialog = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.setup_fingerprint_enroll_enrolling_skip_title)
+ .setMessage(R.string.setup_fingerprint_enroll_enrolling_skip_message)
+ .setCancelable(false)
+ .setPositiveButton(R.string.wifi_skip_anyway,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ Activity activity = getActivity();
+ if (activity != null) {
+ activity.setResult(RESULT_SKIP);
+ activity.finish();
+ }
+ }
+ })
+ .setNegativeButton(R.string.wifi_dont_skip,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ }
+ })
+ .create();
+ SystemBarHelper.hideSystemBars(dialog);
+ return dialog;
+ }
}
}
diff --git a/src/com/android/settings/fingerprint/SetupFingerprintEnrollFindSensor.java b/src/com/android/settings/fingerprint/SetupFingerprintEnrollFindSensor.java
index 17c0671..1483c83 100644
--- a/src/com/android/settings/fingerprint/SetupFingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/fingerprint/SetupFingerprintEnrollFindSensor.java
@@ -21,6 +21,7 @@ import android.content.res.Resources;
import android.view.View;
import android.widget.Button;
+import com.android.internal.logging.MetricsLogger;
import com.android.settings.ChooseLockSettingsHelper;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
@@ -70,4 +71,9 @@ public class SetupFingerprintEnrollFindSensor extends FingerprintEnrollFindSenso
public void onNavigateNext() {
onNextButtonClick();
}
+
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsLogger.FINGERPRINT_FIND_SENSOR_SETUP;
+ }
}
diff --git a/src/com/android/settings/fingerprint/SetupFingerprintEnrollFinish.java b/src/com/android/settings/fingerprint/SetupFingerprintEnrollFinish.java
index 351cd57..8f37a11 100644
--- a/src/com/android/settings/fingerprint/SetupFingerprintEnrollFinish.java
+++ b/src/com/android/settings/fingerprint/SetupFingerprintEnrollFinish.java
@@ -22,6 +22,7 @@ import android.view.View;
import android.widget.Button;
import android.widget.TextView;
+import com.android.internal.logging.MetricsLogger;
import com.android.settings.ChooseLockSettingsHelper;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
@@ -78,4 +79,9 @@ public class SetupFingerprintEnrollFinish extends FingerprintEnrollFinish
public void onNavigateNext() {
onNextButtonClick();
}
+
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsLogger.FINGERPRINT_ENROLL_FINISH_SETUP;
+ }
}
diff --git a/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroduction.java b/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroduction.java
index 416d53f..c7e39e5 100644
--- a/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroduction.java
+++ b/src/com/android/settings/fingerprint/SetupFingerprintEnrollIntroduction.java
@@ -21,6 +21,7 @@ import android.content.res.Resources;
import android.view.View;
import android.widget.Button;
+import com.android.internal.logging.MetricsLogger;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.setupwizardlib.view.NavigationBar;
@@ -74,4 +75,9 @@ public class SetupFingerprintEnrollIntroduction extends FingerprintEnrollIntrodu
public void onNavigateNext() {
onNextButtonClick();
}
+
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsLogger.FINGERPRINT_ENROLL_INTRO_SETUP;
+ }
}
diff --git a/src/com/android/settings/fingerprint/SetupFingerprintEnrollOnboard.java b/src/com/android/settings/fingerprint/SetupFingerprintEnrollOnboard.java
index bee0cde..7fca35a 100644
--- a/src/com/android/settings/fingerprint/SetupFingerprintEnrollOnboard.java
+++ b/src/com/android/settings/fingerprint/SetupFingerprintEnrollOnboard.java
@@ -21,6 +21,7 @@ import android.content.res.Resources;
import android.view.View;
import android.widget.Button;
+import com.android.internal.logging.MetricsLogger;
import com.android.settings.R;
import com.android.settings.SetupChooseLockGeneric;
import com.android.settings.SetupWizardUtils;
@@ -75,4 +76,9 @@ public class SetupFingerprintEnrollOnboard extends FingerprintEnrollOnboard
public void onNavigateNext() {
onNextButtonClick();
}
+
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsLogger.FINGERPRINT_ENROLL_ONBOARD_SETUP;
+ }
}
diff --git a/src/com/android/settings/fuelgauge/FakeUid.java b/src/com/android/settings/fuelgauge/FakeUid.java
index aaa30a2..7fd66c5 100644
--- a/src/com/android/settings/fuelgauge/FakeUid.java
+++ b/src/com/android/settings/fuelgauge/FakeUid.java
@@ -246,7 +246,7 @@ public class FakeUid extends Uid {
}
@Override
- public long getTimeAtCpuSpeed(int step, int which) {
+ public long getTimeAtCpuSpeed(int cluster, int step, int which) {
return 0;
}
diff --git a/src/com/android/settings/widget/ExploreByTouchHelper.java b/src/com/android/settings/widget/ExploreByTouchHelper.java
new file mode 100644
index 0000000..b64a74c
--- /dev/null
+++ b/src/com/android/settings/widget/ExploreByTouchHelper.java
@@ -0,0 +1,724 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Copied from setup wizard, which is in turn a modified copy of
+ * com.android.internal.ExploreByTouchHelper with the following modifications:
+ *
+ * - Make accessibility calls to the views, instead of to the accessibility delegate directly to
+ * make sure those methods for View subclasses are called.
+ *
+ * ExploreByTouchHelper is a utility class for implementing accessibility
+ * support in custom {@link android.view.View}s that represent a collection of View-like
+ * logical items. It extends {@link android.view.accessibility.AccessibilityNodeProvider} and
+ * simplifies many aspects of providing information to accessibility services
+ * and managing accessibility focus. This class does not currently support
+ * hierarchies of logical items.
+ * <p>
+ * This should be applied to the parent view using
+ * {@link android.view.View#setAccessibilityDelegate}:
+ *
+ * <pre>
+ * mAccessHelper = ExploreByTouchHelper.create(someView, mAccessHelperCallback);
+ * ViewCompat.setAccessibilityDelegate(someView, mAccessHelper);
+ * </pre>
+ */
+public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
+ /** Virtual node identifier value for invalid nodes. */
+ public static final int INVALID_ID = Integer.MIN_VALUE;
+
+ /** Default class name used for virtual views. */
+ private static final String DEFAULT_CLASS_NAME = View.class.getName();
+
+ // Temporary, reusable data structures.
+ private final Rect mTempScreenRect = new Rect();
+ private final Rect mTempParentRect = new Rect();
+ private final Rect mTempVisibleRect = new Rect();
+ private final int[] mTempGlobalRect = new int[2];
+
+ /** View's context **/
+ private Context mContext;
+
+ /** System accessibility manager, used to check state and send events. */
+ private final AccessibilityManager mManager;
+
+ /** View whose internal structure is exposed through this helper. */
+ private final View mView;
+
+ /** Node provider that handles creating nodes and performing actions. */
+ private ExploreByTouchNodeProvider mNodeProvider;
+
+ /** Virtual view id for the currently focused logical item. */
+ private int mFocusedVirtualViewId = INVALID_ID;
+
+ /** Virtual view id for the currently hovered logical item. */
+ private int mHoveredVirtualViewId = INVALID_ID;
+
+ /**
+ * Factory method to create a new {@link com.google.android.setupwizard.util.ExploreByTouchHelper}.
+ *
+ * @param forView View whose logical children are exposed by this helper.
+ */
+ public ExploreByTouchHelper(View forView) {
+ if (forView == null) {
+ throw new IllegalArgumentException("View may not be null");
+ }
+
+ mView = forView;
+ mContext = forView.getContext();
+ mManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ }
+
+ /**
+ * Returns the {@link android.view.accessibility.AccessibilityNodeProvider} for this helper.
+ *
+ * @param host View whose logical children are exposed by this helper.
+ * @return The accessibility node provider for this helper.
+ */
+ @Override
+ public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+ if (mNodeProvider == null) {
+ mNodeProvider = new ExploreByTouchNodeProvider();
+ }
+ return mNodeProvider;
+ }
+
+ /**
+ * Dispatches hover {@link android.view.MotionEvent}s to the virtual view hierarchy when
+ * the Explore by Touch feature is enabled.
+ * <p>
+ * This method should be called by overriding
+ * {@link android.view.View#dispatchHoverEvent}:
+ *
+ * <pre>&#64;Override
+ * public boolean dispatchHoverEvent(MotionEvent event) {
+ * if (mHelper.dispatchHoverEvent(this, event) {
+ * return true;
+ * }
+ * return super.dispatchHoverEvent(event);
+ * }
+ * </pre>
+ *
+ * @param event The hover event to dispatch to the virtual view hierarchy.
+ * @return Whether the hover event was handled.
+ */
+ public boolean dispatchHoverEvent(MotionEvent event) {
+ if (!mManager.isEnabled() || !mManager.isTouchExplorationEnabled()) {
+ return false;
+ }
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_HOVER_MOVE:
+ case MotionEvent.ACTION_HOVER_ENTER:
+ final int virtualViewId = getVirtualViewAt(event.getX(), event.getY());
+ updateHoveredVirtualView(virtualViewId);
+ return (virtualViewId != INVALID_ID);
+ case MotionEvent.ACTION_HOVER_EXIT:
+ if (mFocusedVirtualViewId != INVALID_ID) {
+ updateHoveredVirtualView(INVALID_ID);
+ return true;
+ }
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Populates an event of the specified type with information about an item
+ * and attempts to send it up through the view hierarchy.
+ * <p>
+ * You should call this method after performing a user action that normally
+ * fires an accessibility event, such as clicking on an item.
+ *
+ * <pre>public void performItemClick(T item) {
+ * ...
+ * sendEventForVirtualViewId(item.id, AccessibilityEvent.TYPE_VIEW_CLICKED);
+ * }
+ * </pre>
+ *
+ * @param virtualViewId The virtual view id for which to send an event.
+ * @param eventType The type of event to send.
+ * @return true if the event was sent successfully.
+ */
+ public boolean sendEventForVirtualView(int virtualViewId, int eventType) {
+ if ((virtualViewId == INVALID_ID) || !mManager.isEnabled()) {
+ return false;
+ }
+
+ final ViewParent parent = mView.getParent();
+ if (parent == null) {
+ return false;
+ }
+
+ final AccessibilityEvent event = createEvent(virtualViewId, eventType);
+ return parent.requestSendAccessibilityEvent(mView, event);
+ }
+
+ /**
+ * Notifies the accessibility framework that the properties of the parent
+ * view have changed.
+ * <p>
+ * You <b>must</b> call this method after adding or removing items from the
+ * parent view.
+ */
+ public void invalidateRoot() {
+ invalidateVirtualView(View.NO_ID);
+ }
+
+ /**
+ * Notifies the accessibility framework that the properties of a particular
+ * item have changed.
+ * <p>
+ * You <b>must</b> call this method after changing any of the properties set
+ * in {@link #onPopulateNodeForVirtualView}.
+ *
+ * @param virtualViewId The virtual view id to invalidate.
+ */
+ public void invalidateVirtualView(int virtualViewId) {
+ sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ }
+
+ /**
+ * Returns the virtual view id for the currently focused item,
+ *
+ * @return A virtual view id, or {@link #INVALID_ID} if no item is
+ * currently focused.
+ */
+ public int getFocusedVirtualView() {
+ return mFocusedVirtualViewId;
+ }
+
+ /**
+ * Sets the currently hovered item, sending hover accessibility events as
+ * necessary to maintain the correct state.
+ *
+ * @param virtualViewId The virtual view id for the item currently being
+ * hovered, or {@link #INVALID_ID} if no item is hovered within
+ * the parent view.
+ */
+ private void updateHoveredVirtualView(int virtualViewId) {
+ if (mHoveredVirtualViewId == virtualViewId) {
+ return;
+ }
+
+ final int previousVirtualViewId = mHoveredVirtualViewId;
+ mHoveredVirtualViewId = virtualViewId;
+
+ // Stay consistent with framework behavior by sending ENTER/EXIT pairs
+ // in reverse order. This is accurate as of API 18.
+ sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ sendEventForVirtualView(previousVirtualViewId, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ }
+
+ /**
+ * Constructs and returns an {@link android.view.accessibility.AccessibilityEvent} for the specified
+ * virtual view id, which includes the host view ({@link android.view.View#NO_ID}).
+ *
+ * @param virtualViewId The virtual view id for the item for which to
+ * construct an event.
+ * @param eventType The type of event to construct.
+ * @return An {@link android.view.accessibility.AccessibilityEvent} populated with information about
+ * the specified item.
+ */
+ private AccessibilityEvent createEvent(int virtualViewId, int eventType) {
+ switch (virtualViewId) {
+ case View.NO_ID:
+ return createEventForHost(eventType);
+ default:
+ return createEventForChild(virtualViewId, eventType);
+ }
+ }
+
+ /**
+ * Constructs and returns an {@link android.view.accessibility.AccessibilityEvent} for the host node.
+ *
+ * @param eventType The type of event to construct.
+ * @return An {@link android.view.accessibility.AccessibilityEvent} populated with information about
+ * the specified item.
+ */
+ private AccessibilityEvent createEventForHost(int eventType) {
+ final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+ mView.onInitializeAccessibilityEvent(event);
+ return event;
+ }
+
+ /**
+ * Constructs and returns an {@link android.view.accessibility.AccessibilityEvent} populated with
+ * information about the specified item.
+ *
+ * @param virtualViewId The virtual view id for the item for which to
+ * construct an event.
+ * @param eventType The type of event to construct.
+ * @return An {@link android.view.accessibility.AccessibilityEvent} populated with information about
+ * the specified item.
+ */
+ private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) {
+ final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+ event.setEnabled(true);
+ event.setClassName(DEFAULT_CLASS_NAME);
+
+ // Allow the client to populate the event.
+ onPopulateEventForVirtualView(virtualViewId, event);
+
+ // Make sure the developer is following the rules.
+ if (event.getText().isEmpty() && (event.getContentDescription() == null)) {
+ throw new RuntimeException("Callbacks must add text or a content description in "
+ + "populateEventForVirtualViewId()");
+ }
+
+ // Don't allow the client to override these properties.
+ event.setPackageName(mView.getContext().getPackageName());
+ event.setSource(mView, virtualViewId);
+
+ return event;
+ }
+
+ /**
+ * Constructs and returns an {@link android.view.accessibility.AccessibilityNodeInfo} for the
+ * specified virtual view id, which includes the host view
+ * ({@link android.view.View#NO_ID}).
+ *
+ * @param virtualViewId The virtual view id for the item for which to
+ * construct a node.
+ * @return An {@link android.view.accessibility.AccessibilityNodeInfo} populated with information
+ * about the specified item.
+ */
+ private AccessibilityNodeInfo createNode(int virtualViewId) {
+ switch (virtualViewId) {
+ case View.NO_ID:
+ return createNodeForHost();
+ default:
+ return createNodeForChild(virtualViewId);
+ }
+ }
+
+ /**
+ * Constructs and returns an {@link android.view.accessibility.AccessibilityNodeInfo} for the
+ * host view populated with its virtual descendants.
+ *
+ * @return An {@link android.view.accessibility.AccessibilityNodeInfo} for the parent node.
+ */
+ private AccessibilityNodeInfo createNodeForHost() {
+ final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(mView);
+ mView.onInitializeAccessibilityNodeInfo(node);
+
+ // Add the virtual descendants.
+ final LinkedList<Integer> virtualViewIds = new LinkedList<Integer>();
+ getVisibleVirtualViews(virtualViewIds);
+
+ for (Integer childVirtualViewId : virtualViewIds) {
+ node.addChild(mView, childVirtualViewId);
+ }
+
+ return node;
+ }
+
+ /**
+ * Constructs and returns an {@link android.view.accessibility.AccessibilityNodeInfo} for the
+ * specified item. Automatically manages accessibility focus actions.
+ * <p>
+ * Allows the implementing class to specify most node properties, but
+ * overrides the following:
+ * <ul>
+ * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setPackageName}
+ * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClassName}
+ * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setParent(android.view.View)}
+ * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setSource(android.view.View, int)}
+ * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setVisibleToUser}
+ * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInScreen(android.graphics.Rect)}
+ * </ul>
+ * <p>
+ * Uses the bounds of the parent view and the parent-relative bounding
+ * rectangle specified by
+ * {@link android.view.accessibility.AccessibilityNodeInfo#getBoundsInParent} to automatically
+ * update the following properties:
+ * <ul>
+ * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setVisibleToUser}
+ * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInParent}
+ * </ul>
+ *
+ * @param virtualViewId The virtual view id for item for which to construct
+ * a node.
+ * @return An {@link android.view.accessibility.AccessibilityNodeInfo} for the specified item.
+ */
+ private AccessibilityNodeInfo createNodeForChild(int virtualViewId) {
+ final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+
+ // Ensure the client has good defaults.
+ node.setEnabled(true);
+ node.setClassName(DEFAULT_CLASS_NAME);
+
+ // Allow the client to populate the node.
+ onPopulateNodeForVirtualView(virtualViewId, node);
+
+ // Make sure the developer is following the rules.
+ if ((node.getText() == null) && (node.getContentDescription() == null)) {
+ throw new RuntimeException("Callbacks must add text or a content description in "
+ + "populateNodeForVirtualViewId()");
+ }
+
+ node.getBoundsInParent(mTempParentRect);
+ if (mTempParentRect.isEmpty()) {
+ throw new RuntimeException("Callbacks must set parent bounds in "
+ + "populateNodeForVirtualViewId()");
+ }
+
+ final int actions = node.getActions();
+ if ((actions & AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) != 0) {
+ throw new RuntimeException("Callbacks must not add ACTION_ACCESSIBILITY_FOCUS in "
+ + "populateNodeForVirtualViewId()");
+ }
+ if ((actions & AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) != 0) {
+ throw new RuntimeException("Callbacks must not add ACTION_CLEAR_ACCESSIBILITY_FOCUS in "
+ + "populateNodeForVirtualViewId()");
+ }
+
+ // Don't allow the client to override these properties.
+ node.setPackageName(mView.getContext().getPackageName());
+ node.setSource(mView, virtualViewId);
+ node.setParent(mView);
+
+ // Manage internal accessibility focus state.
+ if (mFocusedVirtualViewId == virtualViewId) {
+ node.setAccessibilityFocused(true);
+ node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+ } else {
+ node.setAccessibilityFocused(false);
+ node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ }
+
+ // Set the visibility based on the parent bound.
+ if (intersectVisibleToUser(mTempParentRect)) {
+ node.setVisibleToUser(true);
+ node.setBoundsInParent(mTempParentRect);
+ }
+
+ // Calculate screen-relative bound.
+ mView.getLocationOnScreen(mTempGlobalRect);
+ final int offsetX = mTempGlobalRect[0];
+ final int offsetY = mTempGlobalRect[1];
+ mTempScreenRect.set(mTempParentRect);
+ mTempScreenRect.offset(offsetX, offsetY);
+ node.setBoundsInScreen(mTempScreenRect);
+
+ return node;
+ }
+
+ private boolean performAction(int virtualViewId, int action, Bundle arguments) {
+ switch (virtualViewId) {
+ case View.NO_ID:
+ return performActionForHost(action, arguments);
+ default:
+ return performActionForChild(virtualViewId, action, arguments);
+ }
+ }
+
+ private boolean performActionForHost(int action, Bundle arguments) {
+ return mView.performAccessibilityAction(action, arguments);
+ }
+
+ private boolean performActionForChild(int virtualViewId, int action, Bundle arguments) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
+ case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+ return manageFocusForChild(virtualViewId, action, arguments);
+ default:
+ return onPerformActionForVirtualView(virtualViewId, action, arguments);
+ }
+ }
+
+ private boolean manageFocusForChild(int virtualViewId, int action, Bundle arguments) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
+ return requestAccessibilityFocus(virtualViewId);
+ case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+ return clearAccessibilityFocus(virtualViewId);
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Computes whether the specified {@link android.graphics.Rect} intersects with the visible
+ * portion of its parent {@link android.view.View}. Modifies {@code localRect} to contain
+ * only the visible portion.
+ *
+ * @param localRect A rectangle in local (parent) coordinates.
+ * @return Whether the specified {@link android.graphics.Rect} is visible on the screen.
+ */
+ private boolean intersectVisibleToUser(Rect localRect) {
+ // Missing or empty bounds mean this view is not visible.
+ if ((localRect == null) || localRect.isEmpty()) {
+ return false;
+ }
+
+ // Attached to invisible window means this view is not visible.
+ if (mView.getWindowVisibility() != View.VISIBLE) {
+ return false;
+ }
+
+ // An invisible predecessor means that this view is not visible.
+ ViewParent viewParent = mView.getParent();
+ while (viewParent instanceof View) {
+ final View view = (View) viewParent;
+ if ((view.getAlpha() <= 0) || (view.getVisibility() != View.VISIBLE)) {
+ return false;
+ }
+ viewParent = view.getParent();
+ }
+
+ // A null parent implies the view is not visible.
+ if (viewParent == null) {
+ return false;
+ }
+
+ // If no portion of the parent is visible, this view is not visible.
+ if (!mView.getLocalVisibleRect(mTempVisibleRect)) {
+ return false;
+ }
+
+ // Check if the view intersects the visible portion of the parent.
+ return localRect.intersect(mTempVisibleRect);
+ }
+
+ /**
+ * Returns whether this virtual view is accessibility focused.
+ *
+ * @return True if the view is accessibility focused.
+ */
+ private boolean isAccessibilityFocused(int virtualViewId) {
+ return (mFocusedVirtualViewId == virtualViewId);
+ }
+
+ /**
+ * Attempts to give accessibility focus to a virtual view.
+ * <p>
+ * A virtual view will not actually take focus if
+ * {@link android.view.accessibility.AccessibilityManager#isEnabled()} returns false,
+ * {@link android.view.accessibility.AccessibilityManager#isTouchExplorationEnabled()} returns false,
+ * or the view already has accessibility focus.
+ *
+ * @param virtualViewId The id of the virtual view on which to place
+ * accessibility focus.
+ * @return Whether this virtual view actually took accessibility focus.
+ */
+ private boolean requestAccessibilityFocus(int virtualViewId) {
+ final AccessibilityManager accessibilityManager =
+ (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+
+ if (!mManager.isEnabled()
+ || !accessibilityManager.isTouchExplorationEnabled()) {
+ return false;
+ }
+ // TODO: Check virtual view visibility.
+ if (!isAccessibilityFocused(virtualViewId)) {
+ mFocusedVirtualViewId = virtualViewId;
+ // TODO: Only invalidate virtual view bounds.
+ mView.invalidate();
+ sendEventForVirtualView(virtualViewId,
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Attempts to clear accessibility focus from a virtual view.
+ *
+ * @param virtualViewId The id of the virtual view from which to clear
+ * accessibility focus.
+ * @return Whether this virtual view actually cleared accessibility focus.
+ */
+ private boolean clearAccessibilityFocus(int virtualViewId) {
+ if (isAccessibilityFocused(virtualViewId)) {
+ mFocusedVirtualViewId = INVALID_ID;
+ mView.invalidate();
+ sendEventForVirtualView(virtualViewId,
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Provides a mapping between view-relative coordinates and logical
+ * items.
+ *
+ * @param x The view-relative x coordinate
+ * @param y The view-relative y coordinate
+ * @return virtual view identifier for the logical item under
+ * coordinates (x,y)
+ */
+ protected abstract int getVirtualViewAt(float x, float y);
+
+ /**
+ * Populates a list with the view's visible items. The ordering of items
+ * within {@code virtualViewIds} specifies order of accessibility focus
+ * traversal.
+ *
+ * @param virtualViewIds The list to populate with visible items
+ */
+ protected abstract void getVisibleVirtualViews(List<Integer> virtualViewIds);
+
+ /**
+ * Populates an {@link android.view.accessibility.AccessibilityEvent} with information about the
+ * specified item.
+ * <p>
+ * Implementations <b>must</b> populate the following required fields:
+ * <ul>
+ * <li>event text, see {@link android.view.accessibility.AccessibilityEvent#getText} or
+ * {@link android.view.accessibility.AccessibilityEvent#setContentDescription}
+ * </ul>
+ * <p>
+ * The helper class automatically populates the following fields with
+ * default values, but implementations may optionally override them:
+ * <ul>
+ * <li>item class name, set to android.view.View, see
+ * {@link android.view.accessibility.AccessibilityEvent#setClassName}
+ * </ul>
+ * <p>
+ * The following required fields are automatically populated by the
+ * helper class and may not be overridden:
+ * <ul>
+ * <li>package name, set to the package of the host view's
+ * {@link android.content.Context}, see {@link android.view.accessibility.AccessibilityEvent#setPackageName}
+ * <li>event source, set to the host view and virtual view identifier,
+ * see {@link android.view.accessibility.AccessibilityRecord#setSource(android.view.View, int)}
+ * </ul>
+ *
+ * @param virtualViewId The virtual view id for the item for which to
+ * populate the event
+ * @param event The event to populate
+ */
+ protected abstract void onPopulateEventForVirtualView(
+ int virtualViewId, AccessibilityEvent event);
+
+ /**
+ * Populates an {@link android.view.accessibility.AccessibilityNodeInfo} with information
+ * about the specified item.
+ * <p>
+ * Implementations <b>must</b> populate the following required fields:
+ * <ul>
+ * <li>event text, see {@link android.view.accessibility.AccessibilityNodeInfo#setText} or
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setContentDescription}
+ * <li>bounds in parent coordinates, see
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInParent}
+ * </ul>
+ * <p>
+ * The helper class automatically populates the following fields with
+ * default values, but implementations may optionally override them:
+ * <ul>
+ * <li>enabled state, set to true, see
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setEnabled}
+ * <li>item class name, identical to the class name set by
+ * {@link #onPopulateEventForVirtualView}, see
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setClassName}
+ * </ul>
+ * <p>
+ * The following required fields are automatically populated by the
+ * helper class and may not be overridden:
+ * <ul>
+ * <li>package name, identical to the package name set by
+ * {@link #onPopulateEventForVirtualView}, see
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setPackageName}
+ * <li>node source, identical to the event source set in
+ * {@link #onPopulateEventForVirtualView}, see
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setSource(android.view.View, int)}
+ * <li>parent view, set to the host view, see
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setParent(android.view.View)}
+ * <li>visibility, computed based on parent-relative bounds, see
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setVisibleToUser}
+ * <li>accessibility focus, computed based on internal helper state, see
+ * {@link android.view.accessibility.AccessibilityNodeInfo#setAccessibilityFocused}
+ * <li>bounds in screen coordinates, computed based on host view bounds,
+ * see {@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInScreen}
+ * </ul>
+ * <p>
+ * Additionally, the helper class automatically handles accessibility
+ * focus management by adding the appropriate
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS} or
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
+ * action. Implementations must <b>never</b> manually add these actions.
+ * <p>
+ * The helper class also automatically modifies parent- and
+ * screen-relative bounds to reflect the portion of the item visible
+ * within its parent.
+ *
+ * @param virtualViewId The virtual view identifier of the item for
+ * which to populate the node
+ * @param node The node to populate
+ */
+ protected abstract void onPopulateNodeForVirtualView(
+ int virtualViewId, AccessibilityNodeInfo node);
+
+ /**
+ * Performs the specified accessibility action on the item associated
+ * with the virtual view identifier. See
+ * {@link android.view.accessibility.AccessibilityNodeInfo#performAction(int, android.os.Bundle)} for
+ * more information.
+ * <p>
+ * Implementations <b>must</b> handle any actions added manually in
+ * {@link #onPopulateNodeForVirtualView}.
+ * <p>
+ * The helper class automatically handles focus management resulting
+ * from {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS}
+ * and
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
+ * actions.
+ *
+ * @param virtualViewId The virtual view identifier of the item on which
+ * to perform the action
+ * @param action The accessibility action to perform
+ * @param arguments (Optional) A bundle with additional arguments, or
+ * null
+ * @return true if the action was performed
+ */
+ protected abstract boolean onPerformActionForVirtualView(
+ int virtualViewId, int action, Bundle arguments);
+
+ /**
+ * Exposes a virtual view hierarchy to the accessibility framework. Only
+ * used in API 16+.
+ */
+ private class ExploreByTouchNodeProvider extends AccessibilityNodeProvider {
+ @Override
+ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+ return ExploreByTouchHelper.this.createNode(virtualViewId);
+ }
+
+ @Override
+ public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+ return ExploreByTouchHelper.this.performAction(virtualViewId, action, arguments);
+ }
+ }
+}
diff --git a/src/com/android/settings/widget/LinkAccessibilityHelper.java b/src/com/android/settings/widget/LinkAccessibilityHelper.java
new file mode 100644
index 0000000..2d4d585
--- /dev/null
+++ b/src/com/android/settings/widget/LinkAccessibilityHelper.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.text.Layout;
+import android.text.Spanned;
+import android.text.style.ClickableSpan;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * Copied from setup wizard.
+ */
+public class LinkAccessibilityHelper extends ExploreByTouchHelper {
+
+ private static final String TAG = "LinkAccessibilityHelper";
+
+ private final TextView mView;
+ private final Rect mTempRect = new Rect();
+
+ public LinkAccessibilityHelper(TextView view) {
+ super(view);
+ mView = view;
+ }
+
+ @Override
+ protected int getVirtualViewAt(float x, float y) {
+ final CharSequence text = mView.getText();
+ if (text instanceof Spanned) {
+ final Spanned spannedText = (Spanned) text;
+ final int offset = mView.getOffsetForPosition(x, y);
+ ClickableSpan[] linkSpans = spannedText.getSpans(offset, offset, ClickableSpan.class);
+ if (linkSpans.length == 1) {
+ ClickableSpan linkSpan = linkSpans[0];
+ return spannedText.getSpanStart(linkSpan);
+ }
+ }
+ return INVALID_ID;
+ }
+
+ @Override
+ protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
+ final CharSequence text = mView.getText();
+ if (text instanceof Spanned) {
+ final Spanned spannedText = (Spanned) text;
+ ClickableSpan[] linkSpans = spannedText.getSpans(0, spannedText.length(),
+ ClickableSpan.class);
+ for (ClickableSpan span : linkSpans) {
+ virtualViewIds.add(spannedText.getSpanStart(span));
+ }
+ }
+ }
+
+ @Override
+ protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+ final ClickableSpan span = getSpanForOffset(virtualViewId);
+ if (span != null) {
+ event.setContentDescription(getTextForSpan(span));
+ } else {
+ Log.e(TAG, "ClickableSpan is null for offset: " + virtualViewId);
+ event.setContentDescription(mView.getText());
+ }
+ }
+
+ @Override
+ protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo info) {
+ final ClickableSpan span = getSpanForOffset(virtualViewId);
+ if (span != null) {
+ info.setContentDescription(getTextForSpan(span));
+ } else {
+ Log.e(TAG, "ClickableSpan is null for offset: " + virtualViewId);
+ info.setContentDescription(mView.getText());
+ }
+ info.setFocusable(true);
+ info.setClickable(true);
+ getBoundsForSpan(span, mTempRect);
+ if (!mTempRect.isEmpty()) {
+ info.setBoundsInParent(getBoundsForSpan(span, mTempRect));
+ } else {
+ Log.e(TAG, "LinkSpan bounds is empty for: " + virtualViewId);
+ mTempRect.set(0, 0, 1, 1);
+ info.setBoundsInParent(mTempRect);
+ }
+ info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+ }
+
+ @Override
+ protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
+ Bundle arguments) {
+ if (action == AccessibilityNodeInfo.ACTION_CLICK) {
+ ClickableSpan span = getSpanForOffset(virtualViewId);
+ if (span != null) {
+ span.onClick(mView);
+ return true;
+ } else {
+ Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+ }
+ }
+ return false;
+ }
+
+ private ClickableSpan getSpanForOffset(int offset) {
+ CharSequence text = mView.getText();
+ if (text instanceof Spanned) {
+ Spanned spannedText = (Spanned) text;
+ ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class);
+ if (spans.length == 1) {
+ return spans[0];
+ }
+ }
+ return null;
+ }
+
+ private CharSequence getTextForSpan(ClickableSpan span) {
+ CharSequence text = mView.getText();
+ if (text instanceof Spanned) {
+ Spanned spannedText = (Spanned) text;
+ return spannedText.subSequence(spannedText.getSpanStart(span),
+ spannedText.getSpanEnd(span));
+ }
+ return text;
+ }
+
+ // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for the
+ // section on the first line.
+ private Rect getBoundsForSpan(ClickableSpan span, Rect outRect) {
+ CharSequence text = mView.getText();
+ outRect.setEmpty();
+ if (text instanceof Spanned) {
+ Spanned spannedText = (Spanned) text;
+ final int spanStart = spannedText.getSpanStart(span);
+ final int spanEnd = spannedText.getSpanEnd(span);
+ final Layout layout = mView.getLayout();
+ final float xStart = layout.getPrimaryHorizontal(spanStart);
+ final float xEnd = layout.getPrimaryHorizontal(spanEnd);
+ final int lineStart = layout.getLineForOffset(spanStart);
+ final int lineEnd = layout.getLineForOffset(spanEnd);
+ layout.getLineBounds(lineStart, outRect);
+ outRect.left = (int) xStart;
+ if (lineEnd == lineStart) {
+ outRect.right = (int) xEnd;
+ } // otherwise just leave it at the end of the start line
+
+ // Offset for padding
+ outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop());
+ }
+ return outRect;
+ }
+}
diff --git a/src/com/android/settings/widget/LinkTextView.java b/src/com/android/settings/widget/LinkTextView.java
new file mode 100644
index 0000000..ab72fcf
--- /dev/null
+++ b/src/com/android/settings/widget/LinkTextView.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.widget;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.TextView;
+
+/**
+ * Copied from setup wizard.
+ */
+public class LinkTextView extends TextView {
+
+ private LinkAccessibilityHelper mAccessibilityHelper;
+
+ public LinkTextView(Context context) {
+ this(context, null);
+ }
+
+ public LinkTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mAccessibilityHelper = new LinkAccessibilityHelper(this);
+ setAccessibilityDelegate(mAccessibilityHelper);
+ }
+
+ @Override
+ protected boolean dispatchHoverEvent(@NonNull MotionEvent event) {
+ if (mAccessibilityHelper.dispatchHoverEvent(event)) {
+ return true;
+ }
+ return super.dispatchHoverEvent(event);
+ }
+}
diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java
index c74c56a..ab7999f 100644
--- a/src/com/android/settings/wifi/WifiConfigController.java
+++ b/src/com/android/settings/wifi/WifiConfigController.java
@@ -482,9 +482,17 @@ public class WifiConfigController implements TextWatcher,
String clientCert = (String) mEapUserCertSpinner.getSelectedItem();
if (clientCert.equals(unspecifiedCert)) clientCert = "";
config.enterpriseConfig.setClientCertificateAlias(clientCert);
- config.enterpriseConfig.setIdentity(mEapIdentityView.getText().toString());
- config.enterpriseConfig.setAnonymousIdentity(
- mEapAnonymousView.getText().toString());
+ if (eapMethod == Eap.SIM || eapMethod == Eap.AKA || eapMethod == Eap.AKA_PRIME) {
+ config.enterpriseConfig.setIdentity("");
+ config.enterpriseConfig.setAnonymousIdentity("");
+ } else if (eapMethod == Eap.PWD) {
+ config.enterpriseConfig.setIdentity(mEapIdentityView.getText().toString());
+ config.enterpriseConfig.setAnonymousIdentity("");
+ } else {
+ config.enterpriseConfig.setIdentity(mEapIdentityView.getText().toString());
+ config.enterpriseConfig.setAnonymousIdentity(
+ mEapAnonymousView.getText().toString());
+ }
if (mPasswordView.isShown()) {
// For security reasons, a previous password is not displayed to user.