summaryrefslogtreecommitdiff
path: root/packages/SystemUI/src
diff options
context:
space:
mode:
authorJason Monk <jmonk@google.com>2017-01-31 14:29:32 -0500
committerJason Monk <jmonk@google.com>2017-02-10 07:45:58 -0800
commit23f85ec14dab49b2c525dc266d2a1f74f7f9d07c (patch)
treef7543adafe98131df4e94785f26f3cb3dab1ba16 /packages/SystemUI/src
parentefdb4289597ad1594eb906aeafd2ebdf8854bdc7 (diff)
Move Keyguard to SystemUI
Test: make Change-Id: I3abb67e2b022737d2aa0226bb07f3966ad68fff7
Diffstat (limited to 'packages/SystemUI/src')
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AlphaOptimizedImageButton.java37
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AlphaOptimizedLinearLayout.java50
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AlphaOptimizedRelativeLayout.java37
-rw-r--r--packages/SystemUI/src/com/android/keyguard/CarrierText.java390
-rw-r--r--packages/SystemUI/src/com/android/keyguard/EmergencyButton.java246
-rw-r--r--packages/SystemUI/src/com/android/keyguard/EmergencyCarrierArea.java64
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java310
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java31
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java173
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java443
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java171
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java173
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java364
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java492
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java255
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java51
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java546
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java91
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java130
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java301
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java318
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java379
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java282
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java1798
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java259
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LatencyTracker.java150
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java71
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadKey.java180
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ObscureSpeechDelegate.java102
-rw-r--r--packages/SystemUI/src/com/android/keyguard/PasswordTextView.java703
-rw-r--r--packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java28
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java91
-rw-r--r--packages/SystemUI/src/com/android/systemui/EventLogTags.logtags68
33 files changed, 8784 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/AlphaOptimizedImageButton.java b/packages/SystemUI/src/com/android/keyguard/AlphaOptimizedImageButton.java
new file mode 100644
index 000000000000..58c79b41df3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/AlphaOptimizedImageButton.java
@@ -0,0 +1,37 @@
+/*
+ * 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.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+
+/**
+ * A frame layout which does not have overlapping renderings commands and therefore does not need a
+ * layer when alpha is changed.
+ */
+public class AlphaOptimizedImageButton extends ImageButton {
+
+ public AlphaOptimizedImageButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/AlphaOptimizedLinearLayout.java b/packages/SystemUI/src/com/android/keyguard/AlphaOptimizedLinearLayout.java
new file mode 100644
index 000000000000..2c6c4fa341ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/AlphaOptimizedLinearLayout.java
@@ -0,0 +1,50 @@
+/*
+ * 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.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+/**
+ * A linear layout which does not have overlapping renderings commands and therefore does not need a
+ * layer when alpha is changed.
+ */
+public class AlphaOptimizedLinearLayout extends LinearLayout
+{
+ public AlphaOptimizedLinearLayout(Context context) {
+ super(context);
+ }
+
+ public AlphaOptimizedLinearLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AlphaOptimizedLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public AlphaOptimizedLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/AlphaOptimizedRelativeLayout.java b/packages/SystemUI/src/com/android/keyguard/AlphaOptimizedRelativeLayout.java
new file mode 100644
index 000000000000..200b1162655e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/AlphaOptimizedRelativeLayout.java
@@ -0,0 +1,37 @@
+/*
+ * 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.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.RelativeLayout;
+
+/**
+ * A frame layout which does not have overlapping renderings commands and therefore does not need a
+ * layer when alpha is changed.
+ */
+public class AlphaOptimizedRelativeLayout extends RelativeLayout {
+
+ public AlphaOptimizedRelativeLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
new file mode 100644
index 000000000000..159ac4cc6cbd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.TypedArray;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.text.TextUtils;
+import android.text.method.SingleLineTransformationMethod;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.IccCardConstants.State;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.settingslib.WirelessUtils;
+
+public class CarrierText extends TextView {
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+ private static final String TAG = "CarrierText";
+
+ private static CharSequence mSeparator;
+
+ private final boolean mIsEmergencyCallCapable;
+
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+ private WifiManager mWifiManager;
+
+ private KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onRefreshCarrierInfo() {
+ updateCarrierText();
+ }
+
+ public void onFinishedGoingToSleep(int why) {
+ setSelected(false);
+ };
+
+ public void onStartedWakingUp() {
+ setSelected(true);
+ };
+ };
+ /**
+ * The status of this lock screen. Primarily used for widgets on LockScreen.
+ */
+ private static enum StatusMode {
+ Normal, // Normal case (sim card present, it's not locked)
+ NetworkLocked, // SIM card is 'network locked'.
+ SimMissing, // SIM card is missing.
+ SimMissingLocked, // SIM card is missing, and device isn't provisioned; don't allow access
+ SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
+ SimLocked, // SIM card is currently locked
+ SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
+ SimNotReady; // SIM is not ready yet. May never be on devices w/o a SIM.
+ }
+
+ public CarrierText(Context context) {
+ this(context, null);
+ }
+
+ public CarrierText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mIsEmergencyCallCapable = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_voice_capable);
+ boolean useAllCaps;
+ TypedArray a = context.getTheme().obtainStyledAttributes(
+ attrs, R.styleable.CarrierText, 0, 0);
+ try {
+ useAllCaps = a.getBoolean(R.styleable.CarrierText_allCaps, false);
+ } finally {
+ a.recycle();
+ }
+ setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps));
+
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ }
+
+ protected void updateCarrierText() {
+ boolean allSimsMissing = true;
+ boolean anySimReadyAndInService = false;
+ CharSequence displayText = null;
+
+ List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
+ final int N = subs.size();
+ if (DEBUG) Log.d(TAG, "updateCarrierText(): " + N);
+ for (int i = 0; i < N; i++) {
+ int subId = subs.get(i).getSubscriptionId();
+ State simState = mKeyguardUpdateMonitor.getSimState(subId);
+ CharSequence carrierName = subs.get(i).getCarrierName();
+ CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
+ if (DEBUG) {
+ Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
+ }
+ if (carrierTextForSimState != null) {
+ allSimsMissing = false;
+ displayText = concatenate(displayText, carrierTextForSimState);
+ }
+ if (simState == IccCardConstants.State.READY) {
+ ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
+ if (ss != null && ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
+ // hack for WFC (IWLAN) not turning off immediately once
+ // Wi-Fi is disassociated or disabled
+ if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+ || (mWifiManager.isWifiEnabled()
+ && mWifiManager.getConnectionInfo() != null
+ && mWifiManager.getConnectionInfo().getBSSID() != null)) {
+ if (DEBUG) {
+ Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
+ }
+ anySimReadyAndInService = true;
+ }
+ }
+ }
+ }
+ if (allSimsMissing) {
+ if (N != 0) {
+ // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
+ // This depends on mPlmn containing the text "Emergency calls only" when the radio
+ // has some connectivity. Otherwise, it should be null or empty and just show
+ // "No SIM card"
+ // Grab the first subscripton, because they all should contain the emergency text,
+ // described above.
+ displayText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.keyguard_missing_sim_message_short),
+ subs.get(0).getCarrierName());
+ } else {
+ // We don't have a SubscriptionInfo to get the emergency calls only from.
+ // Grab it from the old sticky broadcast if possible instead. We can use it
+ // here because no subscriptions are active, so we don't have
+ // to worry about MSIM clashing.
+ CharSequence text =
+ getContext().getText(com.android.internal.R.string.emergency_calls_only);
+ Intent i = getContext().registerReceiver(null,
+ new IntentFilter(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION));
+ if (i != null) {
+ String spn = "";
+ String plmn = "";
+ if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
+ spn = i.getStringExtra(TelephonyIntents.EXTRA_SPN);
+ }
+ if (i.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
+ plmn = i.getStringExtra(TelephonyIntents.EXTRA_PLMN);
+ }
+ if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
+ if (Objects.equals(plmn, spn)) {
+ text = plmn;
+ } else {
+ text = concatenate(plmn, spn);
+ }
+ }
+ displayText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.keyguard_missing_sim_message_short), text);
+ }
+ }
+
+ // APM (airplane mode) != no carrier state. There are carrier services
+ // (e.g. WFC = Wi-Fi calling) which may operate in APM.
+ if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
+ displayText = getContext().getString(R.string.airplane_mode);
+ }
+ setText(displayText);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mSeparator = getResources().getString(
+ com.android.internal.R.string.kg_text_message_separator);
+ boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
+ setSelected(shouldMarquee); // Allow marquee to work.
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (ConnectivityManager.from(mContext).isNetworkSupported(
+ ConnectivityManager.TYPE_MOBILE)) {
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ mKeyguardUpdateMonitor.registerCallback(mCallback);
+ } else {
+ // Don't listen and clear out the text when the device isn't a phone.
+ mKeyguardUpdateMonitor = null;
+ setText("");
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mKeyguardUpdateMonitor != null) {
+ mKeyguardUpdateMonitor.removeCallback(mCallback);
+ }
+ }
+
+ /**
+ * Top-level function for creating carrier text. Makes text based on simState, PLMN
+ * and SPN as well as device capabilities, such as being emergency call capable.
+ *
+ * @param simState
+ * @param text
+ * @param spn
+ * @return Carrier text if not in missing state, null otherwise.
+ */
+ private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
+ CharSequence text) {
+ CharSequence carrierText = null;
+ StatusMode status = getStatusForIccState(simState);
+ switch (status) {
+ case Normal:
+ carrierText = text;
+ break;
+
+ case SimNotReady:
+ // Null is reserved for denoting missing, in this case we have nothing to display.
+ carrierText = ""; // nothing to display yet.
+ break;
+
+ case NetworkLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ mContext.getText(R.string.keyguard_network_locked_message), text);
+ break;
+
+ case SimMissing:
+ carrierText = null;
+ break;
+
+ case SimPermDisabled:
+ carrierText = getContext().getText(
+ R.string.keyguard_permanent_disabled_sim_message_short);
+ break;
+
+ case SimMissingLocked:
+ carrierText = null;
+ break;
+
+ case SimLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.keyguard_sim_locked_message),
+ text);
+ break;
+
+ case SimPukLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.keyguard_sim_puk_locked_message),
+ text);
+ break;
+ }
+
+ return carrierText;
+ }
+
+ /*
+ * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
+ */
+ private CharSequence makeCarrierStringOnEmergencyCapable(
+ CharSequence simMessage, CharSequence emergencyCallMessage) {
+ if (mIsEmergencyCallCapable) {
+ return concatenate(simMessage, emergencyCallMessage);
+ }
+ return simMessage;
+ }
+
+ /**
+ * Determine the current status of the lock screen given the SIM state and other stuff.
+ */
+ private StatusMode getStatusForIccState(IccCardConstants.State simState) {
+ // Since reading the SIM may take a while, we assume it is present until told otherwise.
+ if (simState == null) {
+ return StatusMode.Normal;
+ }
+
+ final boolean missingAndNotProvisioned =
+ !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
+ && (simState == IccCardConstants.State.ABSENT ||
+ simState == IccCardConstants.State.PERM_DISABLED);
+
+ // Assume we're NETWORK_LOCKED if not provisioned
+ simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState;
+ switch (simState) {
+ case ABSENT:
+ return StatusMode.SimMissing;
+ case NETWORK_LOCKED:
+ return StatusMode.SimMissingLocked;
+ case NOT_READY:
+ return StatusMode.SimNotReady;
+ case PIN_REQUIRED:
+ return StatusMode.SimLocked;
+ case PUK_REQUIRED:
+ return StatusMode.SimPukLocked;
+ case READY:
+ return StatusMode.Normal;
+ case PERM_DISABLED:
+ return StatusMode.SimPermDisabled;
+ case UNKNOWN:
+ return StatusMode.SimMissing;
+ }
+ return StatusMode.SimMissing;
+ }
+
+ private static CharSequence concatenate(CharSequence plmn, CharSequence spn) {
+ final boolean plmnValid = !TextUtils.isEmpty(plmn);
+ final boolean spnValid = !TextUtils.isEmpty(spn);
+ if (plmnValid && spnValid) {
+ return new StringBuilder().append(plmn).append(mSeparator).append(spn).toString();
+ } else if (plmnValid) {
+ return plmn;
+ } else if (spnValid) {
+ return spn;
+ } else {
+ return "";
+ }
+ }
+
+ private CharSequence getCarrierHelpTextForSimState(IccCardConstants.State simState,
+ String plmn, String spn) {
+ int carrierHelpTextId = 0;
+ StatusMode status = getStatusForIccState(simState);
+ switch (status) {
+ case NetworkLocked:
+ carrierHelpTextId = R.string.keyguard_instructions_when_pattern_disabled;
+ break;
+
+ case SimMissing:
+ carrierHelpTextId = R.string.keyguard_missing_sim_instructions_long;
+ break;
+
+ case SimPermDisabled:
+ carrierHelpTextId = R.string.keyguard_permanent_disabled_sim_instructions;
+ break;
+
+ case SimMissingLocked:
+ carrierHelpTextId = R.string.keyguard_missing_sim_instructions;
+ break;
+
+ case Normal:
+ case SimLocked:
+ case SimPukLocked:
+ break;
+ }
+
+ return mContext.getText(carrierHelpTextId);
+ }
+
+ private class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
+ private final Locale mLocale;
+ private final boolean mAllCaps;
+
+ public CarrierTextTransformationMethod(Context context, boolean allCaps) {
+ mLocale = context.getResources().getConfiguration().locale;
+ mAllCaps = allCaps;
+ }
+
+ @Override
+ public CharSequence getTransformation(CharSequence source, View view) {
+ source = super.getTransformation(source, view);
+
+ if (mAllCaps && source != null) {
+ source = source.toString().toUpperCase(mLocale);
+ }
+
+ return source;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
new file mode 100644
index 000000000000..e3ab05d81de7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2008 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.keyguard;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.telecom.TelecomManager;
+import android.util.AttributeSet;
+import android.util.Slog;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.Button;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.telephony.IccCardConstants.State;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.policy.EmergencyAffordanceManager;
+
+/**
+ * This class implements a smart emergency button that updates itself based
+ * on telephony state. When the phone is idle, it is an emergency call button.
+ * When there's a call in progress, it presents an appropriate message and
+ * allows the user to return to the call.
+ */
+public class EmergencyButton extends Button {
+ private static final Intent INTENT_EMERGENCY_DIAL = new Intent()
+ .setAction("com.android.phone.EmergencyDialer.DIAL")
+ .setPackage("com.android.phone")
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+
+ private static final String LOG_TAG = "EmergencyButton";
+ private final EmergencyAffordanceManager mEmergencyAffordanceManager;
+
+ private int mDownX;
+ private int mDownY;
+ KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
+
+ @Override
+ public void onSimStateChanged(int subId, int slotId, State simState) {
+ updateEmergencyCallButton();
+ }
+
+ @Override
+ public void onPhoneStateChanged(int phoneState) {
+ updateEmergencyCallButton();
+ }
+ };
+ private boolean mLongPressWasDragged;
+
+ public interface EmergencyButtonCallback {
+ public void onEmergencyButtonClickedWhenInCall();
+ }
+
+ private LockPatternUtils mLockPatternUtils;
+ private PowerManager mPowerManager;
+ private EmergencyButtonCallback mEmergencyButtonCallback;
+
+ private final boolean mIsVoiceCapable;
+ private final boolean mEnableEmergencyCallWhileSimLocked;
+
+ public EmergencyButton(Context context) {
+ this(context, null);
+ }
+
+ public EmergencyButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mIsVoiceCapable = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_voice_capable);
+ mEnableEmergencyCallWhileSimLocked = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked);
+ mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mInfoCallback);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mInfoCallback);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mLockPatternUtils = new LockPatternUtils(mContext);
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ takeEmergencyCallAction();
+ }
+ });
+ setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ if (!mLongPressWasDragged
+ && mEmergencyAffordanceManager.needsEmergencyAffordance()) {
+ mEmergencyAffordanceManager.performEmergencyCall();
+ return true;
+ }
+ return false;
+ }
+ });
+ updateEmergencyCallButton();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ final int x = (int) event.getX();
+ final int y = (int) event.getY();
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mDownX = x;
+ mDownY = y;
+ mLongPressWasDragged = false;
+ } else {
+ final int xDiff = Math.abs(x - mDownX);
+ final int yDiff = Math.abs(y - mDownY);
+ int touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ if (Math.abs(yDiff) > touchSlop || Math.abs(xDiff) > touchSlop) {
+ mLongPressWasDragged = true;
+ }
+ }
+ return super.onTouchEvent(event);
+ }
+
+ @Override
+ public boolean performLongClick() {
+ return super.performLongClick();
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateEmergencyCallButton();
+ }
+
+ /**
+ * Shows the emergency dialer or returns the user to the existing call.
+ */
+ public void takeEmergencyCallAction() {
+ MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_CALL);
+ // TODO: implement a shorter timeout once new PowerManager API is ready.
+ // should be the equivalent to the old userActivity(EMERGENCY_CALL_TIMEOUT)
+ mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+ try {
+ ActivityManager.getService().stopSystemLockTaskMode();
+ } catch (RemoteException e) {
+ Slog.w(LOG_TAG, "Failed to stop app pinning");
+ }
+ if (isInCall()) {
+ resumeCall();
+ if (mEmergencyButtonCallback != null) {
+ mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
+ }
+ } else {
+ KeyguardUpdateMonitor.getInstance(mContext).reportEmergencyCallAction(
+ true /* bypassHandler */);
+ getContext().startActivityAsUser(INTENT_EMERGENCY_DIAL,
+ ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
+ new UserHandle(KeyguardUpdateMonitor.getCurrentUser()));
+ }
+ }
+
+ private void updateEmergencyCallButton() {
+ boolean visible = false;
+ if (mIsVoiceCapable) {
+ // Emergency calling requires voice capability.
+ if (isInCall()) {
+ visible = true; // always show "return to call" if phone is off-hook
+ } else {
+ final boolean simLocked = KeyguardUpdateMonitor.getInstance(mContext)
+ .isSimPinVoiceSecure();
+ if (simLocked) {
+ // Some countries can't handle emergency calls while SIM is locked.
+ visible = mEnableEmergencyCallWhileSimLocked;
+ } else {
+ // Only show if there is a secure screen (pin/pattern/SIM pin/SIM puk);
+ visible = mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser());
+ }
+ }
+ }
+ if (visible) {
+ setVisibility(View.VISIBLE);
+
+ int textId;
+ if (isInCall()) {
+ textId = com.android.internal.R.string.lockscreen_return_to_call;
+ } else {
+ textId = com.android.internal.R.string.lockscreen_emergency_call;
+ }
+ setText(textId);
+ } else {
+ setVisibility(View.GONE);
+ }
+ }
+
+ public void setCallback(EmergencyButtonCallback callback) {
+ mEmergencyButtonCallback = callback;
+ }
+
+ /**
+ * Resumes a call in progress.
+ */
+ private void resumeCall() {
+ getTelecommManager().showInCallScreen(false);
+ }
+
+ /**
+ * @return {@code true} if there is a call currently in progress.
+ */
+ private boolean isInCall() {
+ return getTelecommManager().isInCall();
+ }
+
+ private TelecomManager getTelecommManager() {
+ return (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyCarrierArea.java b/packages/SystemUI/src/com/android/keyguard/EmergencyCarrierArea.java
new file mode 100644
index 000000000000..0a89d9b15e26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyCarrierArea.java
@@ -0,0 +1,64 @@
+/*
+ * 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.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+public class EmergencyCarrierArea extends AlphaOptimizedLinearLayout {
+
+ private CarrierText mCarrierText;
+ private EmergencyButton mEmergencyButton;
+
+ public EmergencyCarrierArea(Context context) {
+ super(context);
+ }
+
+ public EmergencyCarrierArea(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mCarrierText = (CarrierText) findViewById(R.id.carrier_text);
+ mEmergencyButton = (EmergencyButton) findViewById(R.id.emergency_call_button);
+
+ // The emergency button overlaps the carrier text, only noticeable when highlighted.
+ // So temporarily hide the carrier text while the emergency button is pressed.
+ mEmergencyButton.setOnTouchListener(new OnTouchListener(){
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mCarrierText.getVisibility() != View.VISIBLE) return false;
+ switch(event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mCarrierText.animate().alpha(0);
+ break;
+ case MotionEvent.ACTION_UP:
+ mCarrierText.animate().alpha(1);
+ break;
+ }
+ return false;
+ }});
+ }
+
+ public void setCarrierTextVisible(boolean visible) {
+ mCarrierText.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
new file mode 100644
index 000000000000..5aa673b40124
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL;
+import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.HapticFeedbackConstants;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.internal.widget.LockPatternChecker;
+import com.android.internal.widget.LockPatternUtils;
+
+/**
+ * Base class for PIN and password unlock screens.
+ */
+public abstract class KeyguardAbsKeyInputView extends LinearLayout
+ implements KeyguardSecurityView, EmergencyButton.EmergencyButtonCallback {
+ protected KeyguardSecurityCallback mCallback;
+ protected LockPatternUtils mLockPatternUtils;
+ protected AsyncTask<?, ?, ?> mPendingLockCheck;
+ protected SecurityMessageDisplay mSecurityMessageDisplay;
+ protected View mEcaView;
+ protected boolean mEnableHaptics;
+ private boolean mDismissing;
+ private CountDownTimer mCountdownTimer = null;
+
+ // To avoid accidental lockout due to events while the device in in the pocket, ignore
+ // any passwords with length less than or equal to this length.
+ protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
+
+ public KeyguardAbsKeyInputView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ mEnableHaptics = mLockPatternUtils.isTactileFeedbackEnabled();
+ }
+
+ @Override
+ public void reset() {
+ // start fresh
+ mDismissing = false;
+ resetPasswordText(false /* animate */, false /* announce */);
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (shouldLockout(deadline)) {
+ handleAttemptLockout(deadline);
+ } else {
+ resetState();
+ }
+ }
+
+ // Allow subclasses to override this behavior
+ protected boolean shouldLockout(long deadline) {
+ return deadline != 0;
+ }
+
+ protected abstract int getPasswordTextViewId();
+ protected abstract void resetState();
+
+ @Override
+ protected void onFinishInflate() {
+ mLockPatternUtils = new LockPatternUtils(mContext);
+ mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(this);
+ mEcaView = findViewById(R.id.keyguard_selector_fade_container);
+
+ EmergencyButton button = (EmergencyButton) findViewById(R.id.emergency_call_button);
+ if (button != null) {
+ button.setCallback(this);
+ }
+ }
+
+ @Override
+ public void onEmergencyButtonClickedWhenInCall() {
+ mCallback.reset();
+ }
+
+ /*
+ * Override this if you have a different string for "wrong password"
+ *
+ * Note that PIN/PUK have their own implementation of verifyPasswordAndUnlock and so don't need this
+ */
+ protected int getWrongPasswordStringId() {
+ return R.string.kg_wrong_password;
+ }
+
+ protected void verifyPasswordAndUnlock() {
+ if (mDismissing) return; // already verified but haven't been dismissed; don't do it again.
+
+ final String entry = getPasswordText();
+ setPasswordEntryInputEnabled(false);
+ if (mPendingLockCheck != null) {
+ mPendingLockCheck.cancel(false);
+ }
+
+ final int userId = KeyguardUpdateMonitor.getCurrentUser();
+ if (entry.length() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
+ // to avoid accidental lockout, only count attempts that are long enough to be a
+ // real password. This may require some tweaking.
+ setPasswordEntryInputEnabled(true);
+ onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */);
+ return;
+ }
+
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
+ LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ }
+ mPendingLockCheck = LockPatternChecker.checkPassword(
+ mLockPatternUtils,
+ entry,
+ userId,
+ new LockPatternChecker.OnCheckCallback() {
+
+ @Override
+ public void onEarlyMatched() {
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionEnd(
+ ACTION_CHECK_CREDENTIAL);
+ }
+ onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
+ true /* isValidPassword */);
+ }
+
+ @Override
+ public void onChecked(boolean matched, int timeoutMs) {
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionEnd(
+ ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ }
+ setPasswordEntryInputEnabled(true);
+ mPendingLockCheck = null;
+ if (!matched) {
+ onPasswordChecked(userId, false /* matched */, timeoutMs,
+ true /* isValidPassword */);
+ }
+ }
+
+ @Override
+ public void onCancelled() {
+ // We already got dismissed with the early matched callback, so we cancelled
+ // the check. However, we still need to note down the latency.
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionEnd(
+ ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ }
+ }
+ });
+ }
+
+ private void onPasswordChecked(int userId, boolean matched, int timeoutMs,
+ boolean isValidPassword) {
+ boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
+ if (matched) {
+ mCallback.reportUnlockAttempt(userId, true, 0);
+ if (dismissKeyguard) {
+ mDismissing = true;
+ mCallback.dismiss(true, userId);
+ }
+ } else {
+ if (isValidPassword) {
+ mCallback.reportUnlockAttempt(userId, false, timeoutMs);
+ if (timeoutMs > 0) {
+ long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
+ userId, timeoutMs);
+ handleAttemptLockout(deadline);
+ }
+ }
+ if (timeoutMs == 0) {
+ mSecurityMessageDisplay.setMessage(getWrongPasswordStringId());
+ }
+ }
+ resetPasswordText(true /* animate */, !matched /* announce deletion if no match */);
+ }
+
+ protected abstract void resetPasswordText(boolean animate, boolean announce);
+ protected abstract String getPasswordText();
+ protected abstract void setPasswordEntryEnabled(boolean enabled);
+ protected abstract void setPasswordEntryInputEnabled(boolean enabled);
+
+ // Prevent user from using the PIN/Password entry until scheduled deadline.
+ protected void handleAttemptLockout(long elapsedRealtimeDeadline) {
+ setPasswordEntryEnabled(false);
+ long elapsedRealtime = SystemClock.elapsedRealtime();
+ long secondsInFuture = (long) Math.ceil(
+ (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
+ mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
+ mSecurityMessageDisplay.formatMessage(
+ R.string.kg_too_many_failed_attempts_countdown, secondsRemaining);
+ }
+
+ @Override
+ public void onFinish() {
+ mSecurityMessageDisplay.setMessage("");
+ resetState();
+ }
+ }.start();
+ }
+
+ protected void onUserInput() {
+ if (mCallback != null) {
+ mCallback.userActivity();
+ }
+ mSecurityMessageDisplay.setMessage("");
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ onUserInput();
+ return false;
+ }
+
+ @Override
+ public boolean needsInput() {
+ return false;
+ }
+
+ @Override
+ public void onPause() {
+ if (mCountdownTimer != null) {
+ mCountdownTimer.cancel();
+ mCountdownTimer = null;
+ }
+ if (mPendingLockCheck != null) {
+ mPendingLockCheck.cancel(false);
+ mPendingLockCheck = null;
+ }
+ }
+
+ @Override
+ public void onResume(int reason) {
+ reset();
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+ @Override
+ public void showPromptReason(int reason) {
+ if (reason != PROMPT_REASON_NONE) {
+ int promtReasonStringRes = getPromtReasonStringRes(reason);
+ if (promtReasonStringRes != 0) {
+ mSecurityMessageDisplay.setMessage(promtReasonStringRes);
+ }
+ }
+ }
+
+ @Override
+ public void showMessage(String message, int color) {
+ mSecurityMessageDisplay.setNextMessageColor(color);
+ mSecurityMessageDisplay.setMessage(message);
+ }
+
+ protected abstract int getPromtReasonStringRes(int reason);
+
+ // Cause a VIRTUAL_KEY vibration
+ public void doHapticKeyClick() {
+ if (mEnableHaptics) {
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
+ | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
+ }
+ }
+
+ @Override
+ public boolean startDisappearAnimation(Runnable finishRunnable) {
+ return false;
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
new file mode 100644
index 000000000000..39271224d943
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
@@ -0,0 +1,31 @@
+/*
+ * 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.keyguard;
+
+/**
+ * Defines constants for the Keyguard.
+ */
+public class KeyguardConstants {
+
+ /**
+ * Turns on debugging information for the whole Keyguard. This is very verbose and should only
+ * be used temporarily for debugging.
+ */
+ public static final boolean DEBUG = false;
+ public static final boolean DEBUG_SIM_STATES = false;
+ public static final boolean DEBUG_FP_WAKELOCK = true;
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
new file mode 100644
index 000000000000..8de1d317c5ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -0,0 +1,173 @@
+/*
+ * 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.keyguard;
+
+import android.app.Presentation;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.graphics.Point;
+import android.media.MediaRouter;
+import android.media.MediaRouter.RouteInfo;
+import android.os.Bundle;
+import android.util.Slog;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+
+public class KeyguardDisplayManager {
+ protected static final String TAG = "KeyguardDisplayManager";
+ private static boolean DEBUG = KeyguardConstants.DEBUG;
+ Presentation mPresentation;
+ private MediaRouter mMediaRouter;
+ private Context mContext;
+ private boolean mShowing;
+
+ public KeyguardDisplayManager(Context context) {
+ mContext = context;
+ mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+ }
+
+ public void show() {
+ if (!mShowing) {
+ if (DEBUG) Slog.v(TAG, "show");
+ mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
+ mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
+ updateDisplays(true);
+ }
+ mShowing = true;
+ }
+
+ public void hide() {
+ if (mShowing) {
+ if (DEBUG) Slog.v(TAG, "hide");
+ mMediaRouter.removeCallback(mMediaRouterCallback);
+ updateDisplays(false);
+ }
+ mShowing = false;
+ }
+
+ private final MediaRouter.SimpleCallback mMediaRouterCallback =
+ new MediaRouter.SimpleCallback() {
+ @Override
+ public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+ if (DEBUG) Slog.d(TAG, "onRouteSelected: type=" + type + ", info=" + info);
+ updateDisplays(mShowing);
+ }
+
+ @Override
+ public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
+ if (DEBUG) Slog.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info);
+ updateDisplays(mShowing);
+ }
+
+ @Override
+ public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) {
+ if (DEBUG) Slog.d(TAG, "onRoutePresentationDisplayChanged: info=" + info);
+ updateDisplays(mShowing);
+ }
+ };
+
+ private OnDismissListener mOnDismissListener = new OnDismissListener() {
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ mPresentation = null;
+ }
+ };
+
+ protected void updateDisplays(boolean showing) {
+ if (showing) {
+ MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(
+ MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
+ boolean useDisplay = route != null
+ && route.getPlaybackType() == MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
+ Display presentationDisplay = useDisplay ? route.getPresentationDisplay() : null;
+
+ if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) {
+ if (DEBUG) Slog.v(TAG, "Display gone: " + mPresentation.getDisplay());
+ mPresentation.dismiss();
+ mPresentation = null;
+ }
+
+ if (mPresentation == null && presentationDisplay != null) {
+ if (DEBUG) Slog.i(TAG, "Keyguard enabled on display: " + presentationDisplay);
+ mPresentation = new KeyguardPresentation(mContext, presentationDisplay,
+ R.style.keyguard_presentation_theme);
+ mPresentation.setOnDismissListener(mOnDismissListener);
+ try {
+ mPresentation.show();
+ } catch (WindowManager.InvalidDisplayException ex) {
+ Slog.w(TAG, "Invalid display:", ex);
+ mPresentation = null;
+ }
+ }
+ } else {
+ if (mPresentation != null) {
+ mPresentation.dismiss();
+ mPresentation = null;
+ }
+ }
+ }
+
+ private final static class KeyguardPresentation extends Presentation {
+ private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
+ private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
+ private View mClock;
+ private int mUsableWidth;
+ private int mUsableHeight;
+ private int mMarginTop;
+ private int mMarginLeft;
+ Runnable mMoveTextRunnable = new Runnable() {
+ @Override
+ public void run() {
+ int x = mMarginLeft + (int) (Math.random() * (mUsableWidth - mClock.getWidth()));
+ int y = mMarginTop + (int) (Math.random() * (mUsableHeight - mClock.getHeight()));
+ mClock.setTranslationX(x);
+ mClock.setTranslationY(y);
+ mClock.postDelayed(mMoveTextRunnable, MOVE_CLOCK_TIMEOUT);
+ }
+ };
+
+ public KeyguardPresentation(Context context, Display display, int theme) {
+ super(context, display, theme);
+ getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mClock.removeCallbacks(mMoveTextRunnable);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Point p = new Point();
+ getDisplay().getSize(p);
+ mUsableWidth = VIDEO_SAFE_REGION * p.x/100;
+ mUsableHeight = VIDEO_SAFE_REGION * p.y/100;
+ mMarginLeft = (100 - VIDEO_SAFE_REGION) * p.x / 200;
+ mMarginTop = (100 - VIDEO_SAFE_REGION) * p.y / 200;
+
+ setContentView(R.layout.keyguard_presentation);
+ mClock = findViewById(R.id.clock);
+
+ // Avoid screen burn in
+ mClock.post(mMoveTextRunnable);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
new file mode 100644
index 000000000000..dd5544d2cdad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2007 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.keyguard;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.media.AudioManager;
+import android.os.SystemClock;
+import android.service.trust.TrustAgentService;
+import android.telephony.TelephonyManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.FrameLayout;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+
+import java.io.File;
+
+/**
+ * Base class for keyguard view. {@link #reset} is where you should
+ * reset the state of your view. Use the {@link KeyguardViewCallback} via
+ * {@link #getCallback()} to send information back (such as poking the wake lock,
+ * or finishing the keyguard).
+ *
+ * Handles intercepting of media keys that still work when the keyguard is
+ * showing.
+ */
+public class KeyguardHostView extends FrameLayout implements SecurityCallback {
+
+ public interface OnDismissAction {
+ /**
+ * @return true if the dismiss should be deferred
+ */
+ boolean onDismiss();
+ }
+
+ private AudioManager mAudioManager;
+ private TelephonyManager mTelephonyManager = null;
+ protected ViewMediatorCallback mViewMediatorCallback;
+ protected LockPatternUtils mLockPatternUtils;
+ private OnDismissAction mDismissAction;
+ private Runnable mCancelAction;
+
+ private final KeyguardUpdateMonitorCallback mUpdateCallback =
+ new KeyguardUpdateMonitorCallback() {
+
+ @Override
+ public void onUserSwitchComplete(int userId) {
+ getSecurityContainer().showPrimarySecurityScreen(false /* turning off */);
+ }
+
+ @Override
+ public void onTrustGrantedWithFlags(int flags, int userId) {
+ if (userId != KeyguardUpdateMonitor.getCurrentUser()) return;
+ if (!isAttachedToWindow()) return;
+ boolean bouncerVisible = isVisibleToUser();
+ boolean initiatedByUser =
+ (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
+ boolean dismissKeyguard =
+ (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
+
+ if (initiatedByUser || dismissKeyguard) {
+ if (mViewMediatorCallback.isScreenOn() && (bouncerVisible || dismissKeyguard)) {
+ if (!bouncerVisible) {
+ // The trust agent dismissed the keyguard without the user proving
+ // that they are present (by swiping up to show the bouncer). That's fine if
+ // the user proved presence via some other way to the trust agent.
+ Log.i(TAG, "TrustAgent dismissed Keyguard.");
+ }
+ dismiss(false /* authenticated */, userId);
+ } else {
+ mViewMediatorCallback.playTrustedSound();
+ }
+ }
+ }
+ };
+
+ // Whether the volume keys should be handled by keyguard. If true, then
+ // they will be handled here for specific media types such as music, otherwise
+ // the audio service will bring up the volume dialog.
+ private static final boolean KEYGUARD_MANAGES_VOLUME = false;
+ public static final boolean DEBUG = KeyguardConstants.DEBUG;
+ private static final String TAG = "KeyguardViewBase";
+
+ private KeyguardSecurityContainer mSecurityContainer;
+
+ public KeyguardHostView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardHostView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateCallback);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ if (mViewMediatorCallback != null) {
+ mViewMediatorCallback.keyguardDoneDrawing();
+ }
+ }
+
+ /**
+ * Sets an action to run when keyguard finishes.
+ *
+ * @param action
+ */
+ public void setOnDismissAction(OnDismissAction action, Runnable cancelAction) {
+ if (mCancelAction != null) {
+ mCancelAction.run();
+ mCancelAction = null;
+ }
+ mDismissAction = action;
+ mCancelAction = cancelAction;
+ }
+
+ public void cancelDismissAction() {
+ setOnDismissAction(null, null);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mSecurityContainer =
+ (KeyguardSecurityContainer) findViewById(R.id.keyguard_security_container);
+ mLockPatternUtils = new LockPatternUtils(mContext);
+ mSecurityContainer.setLockPatternUtils(mLockPatternUtils);
+ mSecurityContainer.setSecurityCallback(this);
+ mSecurityContainer.showPrimarySecurityScreen(false);
+ // mSecurityContainer.updateSecurityViews(false /* not bouncing */);
+ }
+
+ /**
+ * Called when the view needs to be shown.
+ */
+ public void showPrimarySecurityScreen() {
+ if (DEBUG) Log.d(TAG, "show()");
+ mSecurityContainer.showPrimarySecurityScreen(false);
+ }
+
+ /**
+ * Show a string explaining why the security view needs to be solved.
+ *
+ * @param reason a flag indicating which string should be shown, see
+ * {@link KeyguardSecurityView#PROMPT_REASON_NONE},
+ * {@link KeyguardSecurityView#PROMPT_REASON_RESTART} and
+ * {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}.
+ */
+ public void showPromptReason(int reason) {
+ mSecurityContainer.showPromptReason(reason);
+ }
+
+ public void showMessage(String message, int color) {
+ mSecurityContainer.showMessage(message, color);
+ }
+
+ /**
+ * Dismisses the keyguard by going to the next screen or making it gone.
+ * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
+ * @return True if the keyguard is done.
+ */
+ public boolean dismiss(int targetUserId) {
+ return dismiss(false, targetUserId);
+ }
+
+ public boolean handleBackKey() {
+ if (mSecurityContainer.getCurrentSecuritySelection() != SecurityMode.None) {
+ mSecurityContainer.dismiss(false, KeyguardUpdateMonitor.getCurrentUser());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ event.getText().add(mSecurityContainer.getCurrentSecurityModeContentDescription());
+ return true;
+ } else {
+ return super.dispatchPopulateAccessibilityEvent(event);
+ }
+ }
+
+ protected KeyguardSecurityContainer getSecurityContainer() {
+ return mSecurityContainer;
+ }
+
+ @Override
+ public boolean dismiss(boolean authenticated, int targetUserId) {
+ return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId);
+ }
+
+ /**
+ * Authentication has happened and it's time to dismiss keyguard. This function
+ * should clean up and inform KeyguardViewMediator.
+ *
+ * @param strongAuth whether the user has authenticated with strong authentication like
+ * pattern, password or PIN but not by trust agents or fingerprint
+ * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
+ */
+ @Override
+ public void finish(boolean strongAuth, int targetUserId) {
+ // If there's a pending runnable because the user interacted with a widget
+ // and we're leaving keyguard, then run it.
+ boolean deferKeyguardDone = false;
+ if (mDismissAction != null) {
+ deferKeyguardDone = mDismissAction.onDismiss();
+ mDismissAction = null;
+ mCancelAction = null;
+ }
+ if (mViewMediatorCallback != null) {
+ if (deferKeyguardDone) {
+ mViewMediatorCallback.keyguardDonePending(strongAuth, targetUserId);
+ } else {
+ mViewMediatorCallback.keyguardDone(strongAuth, targetUserId);
+ }
+ }
+ }
+
+ @Override
+ public void reset() {
+ mViewMediatorCallback.resetKeyguard();
+ }
+
+ @Override
+ public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
+ if (mViewMediatorCallback != null) {
+ mViewMediatorCallback.setNeedsInput(needsInput);
+ }
+ }
+
+ public void userActivity() {
+ if (mViewMediatorCallback != null) {
+ mViewMediatorCallback.userActivity();
+ }
+ }
+
+ /**
+ * Called when the Keyguard is not actively shown anymore on the screen.
+ */
+ public void onPause() {
+ if (DEBUG) Log.d(TAG, String.format("screen off, instance %s at %s",
+ Integer.toHexString(hashCode()), SystemClock.uptimeMillis()));
+ mSecurityContainer.showPrimarySecurityScreen(true);
+ mSecurityContainer.onPause();
+ clearFocus();
+ }
+
+ /**
+ * Called when the Keyguard is actively shown on the screen.
+ */
+ public void onResume() {
+ if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
+ mSecurityContainer.onResume(KeyguardSecurityView.SCREEN_ON);
+ requestFocus();
+ }
+
+ /**
+ * Starts the animation when the Keyguard gets shown.
+ */
+ public void startAppearAnimation() {
+ mSecurityContainer.startAppearAnimation();
+ }
+
+ public void startDisappearAnimation(Runnable finishRunnable) {
+ if (!mSecurityContainer.startDisappearAnimation(finishRunnable) && finishRunnable != null) {
+ finishRunnable.run();
+ }
+ }
+
+ /**
+ * Called before this view is being removed.
+ */
+ public void cleanUp() {
+ getSecurityContainer().onPause();
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (interceptMediaKey(event)) {
+ return true;
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ /**
+ * Allows the media keys to work when the keyguard is showing.
+ * The media keys should be of no interest to the actual keyguard view(s),
+ * so intercepting them here should not be of any harm.
+ * @param event The key event
+ * @return whether the event was consumed as a media key.
+ */
+ public boolean interceptMediaKey(KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ /* Suppress PLAY/PAUSE toggle when phone is ringing or
+ * in-call to avoid music playback */
+ if (mTelephonyManager == null) {
+ mTelephonyManager = (TelephonyManager) getContext().getSystemService(
+ Context.TELEPHONY_SERVICE);
+ }
+ if (mTelephonyManager != null &&
+ mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
+ return true; // suppress key event
+ }
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
+ handleMediaKeyEvent(event);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE: {
+ if (KEYGUARD_MANAGES_VOLUME) {
+ synchronized (this) {
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager) getContext().getSystemService(
+ Context.AUDIO_SERVICE);
+ }
+ }
+ // Volume buttons should only function for music (local or remote).
+ // TODO: Actually handle MUTE.
+ mAudioManager.adjustSuggestedStreamVolume(
+ keyCode == KeyEvent.KEYCODE_VOLUME_UP
+ ? AudioManager.ADJUST_RAISE
+ : AudioManager.ADJUST_LOWER /* direction */,
+ AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */);
+ // Don't execute default volume behavior
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
+ handleMediaKeyEvent(event);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void handleMediaKeyEvent(KeyEvent keyEvent) {
+ synchronized (this) {
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager) getContext().getSystemService(
+ Context.AUDIO_SERVICE);
+ }
+ }
+ mAudioManager.dispatchMediaKeyEvent(keyEvent);
+ }
+
+ @Override
+ public void dispatchSystemUiVisibilityChanged(int visibility) {
+ super.dispatchSystemUiVisibilityChanged(visibility);
+
+ if (!(mContext instanceof Activity)) {
+ setSystemUiVisibility(STATUS_BAR_DISABLE_BACK);
+ }
+ }
+
+ /**
+ * In general, we enable unlocking the insecure keyguard with the menu key. However, there are
+ * some cases where we wish to disable it, notably when the menu button placement or technology
+ * is prone to false positives.
+ *
+ * @return true if the menu key should be enabled
+ */
+ private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
+ public boolean shouldEnableMenuKey() {
+ final Resources res = getResources();
+ final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
+ final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
+ final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
+ return !configDisabled || isTestHarness || fileOverride;
+ }
+
+ public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
+ mViewMediatorCallback = viewMediatorCallback;
+ // Update ViewMediator with the current input method requirements
+ mViewMediatorCallback.setNeedsInput(mSecurityContainer.needsInput());
+ }
+
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ mSecurityContainer.setLockPatternUtils(utils);
+ }
+
+ public SecurityMode getSecurityMode() {
+ return mSecurityContainer.getSecurityMode();
+ }
+
+ public SecurityMode getCurrentSecurityMode() {
+ return mSecurityContainer.getCurrentSecurityMode();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
new file mode 100644
index 000000000000..d19821fb9723
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2011 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.keyguard;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+
+import java.lang.ref.WeakReference;
+
+/***
+ * Manages a number of views inside of the given layout. See below for a list of widgets.
+ */
+class KeyguardMessageArea extends TextView implements SecurityMessageDisplay {
+ /** Handler token posted with accessibility announcement runnables. */
+ private static final Object ANNOUNCE_TOKEN = new Object();
+
+ /**
+ * Delay before speaking an accessibility announcement. Used to prevent
+ * lift-to-type from interrupting itself.
+ */
+ private static final long ANNOUNCEMENT_DELAY = 250;
+ private static final int DEFAULT_COLOR = -1;
+
+ private final Handler mHandler;
+ private final int mDefaultColor;
+
+ private CharSequence mMessage;
+ private int mNextMessageColor = DEFAULT_COLOR;
+
+ private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
+ public void onFinishedGoingToSleep(int why) {
+ setSelected(false);
+ };
+ public void onStartedWakingUp() {
+ setSelected(true);
+ };
+ };
+
+ public KeyguardMessageArea(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardMessageArea(Context context, AttributeSet attrs) {
+ this(context, attrs, KeyguardUpdateMonitor.getInstance(context));
+ }
+
+ public KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor) {
+ super(context, attrs);
+ setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
+
+ monitor.registerCallback(mInfoCallback);
+ mHandler = new Handler(Looper.myLooper());
+
+ mDefaultColor = getCurrentTextColor();
+ update();
+ }
+
+ @Override
+ public void setNextMessageColor(int color) {
+ mNextMessageColor = color;
+ }
+
+ @Override
+ public void setMessage(CharSequence msg) {
+ if (!TextUtils.isEmpty(msg)) {
+ securityMessageChanged(msg);
+ } else {
+ clearMessage();
+ }
+ }
+
+ @Override
+ public void setMessage(int resId) {
+ CharSequence message = null;
+ if (resId != 0) {
+ message = getContext().getResources().getText(resId);
+ }
+ setMessage(message);
+ }
+
+ @Override
+ public void formatMessage(int resId, Object... formatArgs) {
+ CharSequence message = null;
+ if (resId != 0) {
+ message = getContext().getString(resId, formatArgs);
+ }
+ setMessage(message);
+ }
+
+ public static SecurityMessageDisplay findSecurityMessageDisplay(View v) {
+ KeyguardMessageArea messageArea = (KeyguardMessageArea) v.findViewById(
+ R.id.keyguard_message_area);
+ if (messageArea == null) {
+ throw new RuntimeException("Can't find keyguard_message_area in " + v.getClass());
+ }
+ return messageArea;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
+ setSelected(shouldMarquee); // This is required to ensure marquee works
+ }
+
+ private void securityMessageChanged(CharSequence message) {
+ mMessage = message;
+ update();
+ mHandler.removeCallbacksAndMessages(ANNOUNCE_TOKEN);
+ mHandler.postAtTime(new AnnounceRunnable(this, getText()), ANNOUNCE_TOKEN,
+ (SystemClock.uptimeMillis() + ANNOUNCEMENT_DELAY));
+ }
+
+ private void clearMessage() {
+ mMessage = null;
+ update();
+ }
+
+ private void update() {
+ CharSequence status = mMessage;
+ setVisibility(TextUtils.isEmpty(status) ? INVISIBLE : VISIBLE);
+ setText(status);
+ int color = mDefaultColor;
+ if (mNextMessageColor != DEFAULT_COLOR) {
+ color = mNextMessageColor;
+ mNextMessageColor = DEFAULT_COLOR;
+ }
+ setTextColor(color);
+ }
+
+
+ /**
+ * Runnable used to delay accessibility announcements.
+ */
+ private static class AnnounceRunnable implements Runnable {
+ private final WeakReference<View> mHost;
+ private final CharSequence mTextToAnnounce;
+
+ AnnounceRunnable(View host, CharSequence textToAnnounce) {
+ mHost = new WeakReference<View>(host);
+ mTextToAnnounce = textToAnnounce;
+ }
+
+ @Override
+ public void run() {
+ final View host = mHost.get();
+ if (host != null) {
+ host.announceForAccessibility(mTextToAnnounce);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
new file mode 100644
index 000000000000..590d8d5de672
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+
+import com.android.settingslib.animation.AppearAnimationUtils;
+import com.android.settingslib.animation.DisappearAnimationUtils;
+
+/**
+ * Displays a PIN pad for unlocking.
+ */
+public class KeyguardPINView extends KeyguardPinBasedInputView {
+
+ private final AppearAnimationUtils mAppearAnimationUtils;
+ private final DisappearAnimationUtils mDisappearAnimationUtils;
+ private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
+ private ViewGroup mContainer;
+ private ViewGroup mRow0;
+ private ViewGroup mRow1;
+ private ViewGroup mRow2;
+ private ViewGroup mRow3;
+ private View mDivider;
+ private int mDisappearYTranslation;
+ private View[][] mViews;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+ public KeyguardPINView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardPINView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mAppearAnimationUtils = new AppearAnimationUtils(context);
+ mDisappearAnimationUtils = new DisappearAnimationUtils(context,
+ 125, 0.6f /* translationScale */,
+ 0.45f /* delayScale */, AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_linear_in));
+ mDisappearAnimationUtilsLocked = new DisappearAnimationUtils(context,
+ (long) (125 * KeyguardPatternView.DISAPPEAR_MULTIPLIER_LOCKED),
+ 0.6f /* translationScale */,
+ 0.45f /* delayScale */, AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_linear_in));
+ mDisappearYTranslation = getResources().getDimensionPixelSize(
+ R.dimen.disappear_y_translation);
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+ }
+
+ @Override
+ protected void resetState() {
+ super.resetState();
+ mSecurityMessageDisplay.setMessage("");
+ }
+
+ @Override
+ protected int getPasswordTextViewId() {
+ return R.id.pinEntry;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mContainer = (ViewGroup) findViewById(R.id.container);
+ mRow0 = (ViewGroup) findViewById(R.id.row0);
+ mRow1 = (ViewGroup) findViewById(R.id.row1);
+ mRow2 = (ViewGroup) findViewById(R.id.row2);
+ mRow3 = (ViewGroup) findViewById(R.id.row3);
+ mDivider = findViewById(R.id.divider);
+ mViews = new View[][]{
+ new View[]{
+ mRow0, null, null
+ },
+ new View[]{
+ findViewById(R.id.key1), findViewById(R.id.key2),
+ findViewById(R.id.key3)
+ },
+ new View[]{
+ findViewById(R.id.key4), findViewById(R.id.key5),
+ findViewById(R.id.key6)
+ },
+ new View[]{
+ findViewById(R.id.key7), findViewById(R.id.key8),
+ findViewById(R.id.key9)
+ },
+ new View[]{
+ null, findViewById(R.id.key0), findViewById(R.id.key_enter)
+ },
+ new View[]{
+ null, mEcaView, null
+ }};
+ }
+
+ @Override
+ public void showUsabilityHint() {
+ }
+
+ @Override
+ public int getWrongPasswordStringId() {
+ return R.string.kg_wrong_pin;
+ }
+
+ @Override
+ public void startAppearAnimation() {
+ enableClipping(false);
+ setAlpha(1f);
+ setTranslationY(mAppearAnimationUtils.getStartTranslation());
+ AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */,
+ 0, mAppearAnimationUtils.getInterpolator());
+ mAppearAnimationUtils.startAnimation2d(mViews,
+ new Runnable() {
+ @Override
+ public void run() {
+ enableClipping(true);
+ }
+ });
+ }
+
+ @Override
+ public boolean startDisappearAnimation(final Runnable finishRunnable) {
+ enableClipping(false);
+ setTranslationY(0);
+ AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 280 /* duration */,
+ mDisappearYTranslation, mDisappearAnimationUtils.getInterpolator());
+ DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor
+ .needsSlowUnlockTransition()
+ ? mDisappearAnimationUtilsLocked
+ : mDisappearAnimationUtils;
+ disappearAnimationUtils.startAnimation2d(mViews,
+ new Runnable() {
+ @Override
+ public void run() {
+ enableClipping(true);
+ if (finishRunnable != null) {
+ finishRunnable.run();
+ }
+ }
+ });
+ return true;
+ }
+
+ private void enableClipping(boolean enable) {
+ mContainer.setClipToPadding(enable);
+ mContainer.setClipChildren(enable);
+ mRow1.setClipToPadding(enable);
+ mRow2.setClipToPadding(enable);
+ mRow3.setClipToPadding(enable);
+ setClipChildren(enable);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
new file mode 100644
index 000000000000..d49ff975473d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.method.TextKeyListener;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import com.android.internal.widget.TextViewInputDisabler;
+
+import java.util.List;
+/**
+ * Displays an alphanumeric (latin-1) key entry for the user to enter
+ * an unlock password
+ */
+public class KeyguardPasswordView extends KeyguardAbsKeyInputView
+ implements KeyguardSecurityView, OnEditorActionListener, TextWatcher {
+
+ private final boolean mShowImeAtScreenOn;
+ private final int mDisappearYTranslation;
+
+ // A delay constant to be used in a workaround for the situation where InputMethodManagerService
+ // is not switched to the new user yet.
+ // TODO: Remove this by ensuring such a race condition never happens.
+ private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms
+
+ InputMethodManager mImm;
+ private TextView mPasswordEntry;
+ private TextViewInputDisabler mPasswordEntryDisabler;
+ private View mSwitchImeButton;
+
+ private Interpolator mLinearOutSlowInInterpolator;
+ private Interpolator mFastOutLinearInInterpolator;
+
+ public KeyguardPasswordView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardPasswordView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mShowImeAtScreenOn = context.getResources().
+ getBoolean(R.bool.kg_show_ime_at_screen_on);
+ mDisappearYTranslation = getResources().getDimensionPixelSize(
+ R.dimen.disappear_y_translation);
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
+ context, android.R.interpolator.linear_out_slow_in);
+ mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
+ context, android.R.interpolator.fast_out_linear_in);
+ }
+
+ @Override
+ protected void resetState() {
+ mSecurityMessageDisplay.setMessage("");
+ final boolean wasDisabled = mPasswordEntry.isEnabled();
+ setPasswordEntryEnabled(true);
+ setPasswordEntryInputEnabled(true);
+ if (wasDisabled) {
+ mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
+ }
+ }
+
+ @Override
+ protected int getPasswordTextViewId() {
+ return R.id.passwordEntry;
+ }
+
+ @Override
+ public boolean needsInput() {
+ return true;
+ }
+
+ @Override
+ public void onResume(final int reason) {
+ super.onResume(reason);
+
+ // Wait a bit to focus the field so the focusable flag on the window is already set then.
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (isShown() && mPasswordEntry.isEnabled()) {
+ mPasswordEntry.requestFocus();
+ if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
+ mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ protected int getPromtReasonStringRes(int reason) {
+ switch (reason) {
+ case PROMPT_REASON_RESTART:
+ return R.string.kg_prompt_reason_restart_password;
+ case PROMPT_REASON_TIMEOUT:
+ return R.string.kg_prompt_reason_timeout_password;
+ case PROMPT_REASON_DEVICE_ADMIN:
+ return R.string.kg_prompt_reason_device_admin;
+ case PROMPT_REASON_USER_REQUEST:
+ return R.string.kg_prompt_reason_user_request;
+ case PROMPT_REASON_NONE:
+ return 0;
+ default:
+ return R.string.kg_prompt_reason_timeout_password;
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mImm.hideSoftInputFromWindow(getWindowToken(), 0);
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ mPasswordEntry.requestFocus();
+ }
+
+ private void updateSwitchImeButton() {
+ // If there's more than one IME, enable the IME switcher button
+ final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE;
+ final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes(mImm, false);
+ if (wasVisible != shouldBeVisible) {
+ mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE);
+ }
+
+ // TODO: Check if we still need this hack.
+ // If no icon is visible, reset the start margin on the password field so the text is
+ // still centered.
+ if (mSwitchImeButton.getVisibility() != View.VISIBLE) {
+ android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams();
+ if (params instanceof MarginLayoutParams) {
+ final MarginLayoutParams mlp = (MarginLayoutParams) params;
+ mlp.setMarginStart(0);
+ mPasswordEntry.setLayoutParams(params);
+ }
+ }
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mImm = (InputMethodManager) getContext().getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+
+ mPasswordEntry = (TextView) findViewById(getPasswordTextViewId());
+ mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry);
+ mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
+ mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
+ | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ mPasswordEntry.setOnEditorActionListener(this);
+ mPasswordEntry.addTextChangedListener(this);
+
+ // Poke the wakelock any time the text is selected or modified
+ mPasswordEntry.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mCallback.userActivity();
+ }
+ });
+
+ // Set selected property on so the view can send accessibility events.
+ mPasswordEntry.setSelected(true);
+
+ mPasswordEntry.requestFocus();
+
+ mSwitchImeButton = findViewById(R.id.switch_ime_button);
+ mSwitchImeButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mCallback.userActivity(); // Leave the screen on a bit longer
+ // Do not show auxiliary subtypes in password lock screen.
+ mImm.showInputMethodPicker(false /* showAuxiliarySubtypes */);
+ }
+ });
+
+ // If there's more than one IME, enable the IME switcher button
+ updateSwitchImeButton();
+
+ // When we the current user is switching, InputMethodManagerService sometimes has not
+ // switched internal state yet here. As a quick workaround, we check the keyboard state
+ // again.
+ // TODO: Remove this workaround by ensuring such a race condition never happens.
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ updateSwitchImeButton();
+ }
+ }, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ // send focus to the password field
+ return mPasswordEntry.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ @Override
+ protected void resetPasswordText(boolean animate, boolean announce) {
+ mPasswordEntry.setText("");
+ }
+
+ @Override
+ protected String getPasswordText() {
+ return mPasswordEntry.getText().toString();
+ }
+
+ @Override
+ protected void setPasswordEntryEnabled(boolean enabled) {
+ mPasswordEntry.setEnabled(enabled);
+ }
+
+ @Override
+ protected void setPasswordEntryInputEnabled(boolean enabled) {
+ mPasswordEntryDisabler.setInputEnabled(enabled);
+ }
+
+ /**
+ * Method adapted from com.android.inputmethod.latin.Utils
+ *
+ * @param imm The input method manager
+ * @param shouldIncludeAuxiliarySubtypes
+ * @return true if we have multiple IMEs to choose from
+ */
+ private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
+ final boolean shouldIncludeAuxiliarySubtypes) {
+ final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList();
+
+ // Number of the filtered IMEs
+ int filteredImisCount = 0;
+
+ for (InputMethodInfo imi : enabledImis) {
+ // We can return true immediately after we find two or more filtered IMEs.
+ if (filteredImisCount > 1) return true;
+ final List<InputMethodSubtype> subtypes =
+ imm.getEnabledInputMethodSubtypeList(imi, true);
+ // IMEs that have no subtypes should be counted.
+ if (subtypes.isEmpty()) {
+ ++filteredImisCount;
+ continue;
+ }
+
+ int auxCount = 0;
+ for (InputMethodSubtype subtype : subtypes) {
+ if (subtype.isAuxiliary()) {
+ ++auxCount;
+ }
+ }
+ final int nonAuxCount = subtypes.size() - auxCount;
+
+ // IMEs that have one or more non-auxiliary subtypes should be counted.
+ // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
+ // subtypes should be counted as well.
+ if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
+ ++filteredImisCount;
+ continue;
+ }
+ }
+
+ return filteredImisCount > 1
+ // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
+ // input method subtype (The current IME should be LatinIME.)
+ || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
+ }
+
+ @Override
+ public void showUsabilityHint() {
+ }
+
+ @Override
+ public int getWrongPasswordStringId() {
+ return R.string.kg_wrong_password;
+ }
+
+ @Override
+ public void startAppearAnimation() {
+ setAlpha(0f);
+ setTranslationY(0f);
+ animate()
+ .alpha(1)
+ .withLayer()
+ .setDuration(300)
+ .setInterpolator(mLinearOutSlowInInterpolator);
+ }
+
+ @Override
+ public boolean startDisappearAnimation(Runnable finishRunnable) {
+ animate()
+ .alpha(0f)
+ .translationY(mDisappearYTranslation)
+ .setInterpolator(mFastOutLinearInInterpolator)
+ .setDuration(100)
+ .withEndAction(finishRunnable);
+ return true;
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ if (mCallback != null) {
+ mCallback.userActivity();
+ }
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ // Poor man's user edit detection, assuming empty text is programmatic and everything else
+ // is from the user.
+ if (!TextUtils.isEmpty(s)) {
+ onUserInput();
+ }
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ // Check if this was the result of hitting the enter key
+ final boolean isSoftImeEvent = event == null
+ && (actionId == EditorInfo.IME_NULL
+ || actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT);
+ final boolean isKeyboardEnterKey = event != null
+ && KeyEvent.isConfirmKey(event.getKeyCode())
+ && event.getAction() == KeyEvent.ACTION_DOWN;
+ if (isSoftImeEvent || isKeyboardEnterKey) {
+ verifyPasswordAndUnlock();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
new file mode 100644
index 000000000000..c2b57ffa6113
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL;
+import static com.android.keyguard.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.AsyncTask;
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.LinearLayout;
+
+import com.android.internal.widget.LockPatternChecker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.settingslib.animation.AppearAnimationCreator;
+import com.android.settingslib.animation.AppearAnimationUtils;
+import com.android.settingslib.animation.DisappearAnimationUtils;
+
+import java.util.List;
+
+public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView,
+ AppearAnimationCreator<LockPatternView.CellState>,
+ EmergencyButton.EmergencyButtonCallback {
+
+ private static final String TAG = "SecurityPatternView";
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+ // how long before we clear the wrong pattern
+ private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
+
+ // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
+ private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;
+
+ // how many cells the user has to cross before we poke the wakelock
+ private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
+
+ // How much we scale up the duration of the disappear animation when the current user is locked
+ public static final float DISAPPEAR_MULTIPLIER_LOCKED = 1.5f;
+
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final AppearAnimationUtils mAppearAnimationUtils;
+ private final DisappearAnimationUtils mDisappearAnimationUtils;
+ private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
+
+ private CountDownTimer mCountdownTimer = null;
+ private LockPatternUtils mLockPatternUtils;
+ private AsyncTask<?, ?, ?> mPendingLockCheck;
+ private LockPatternView mLockPatternView;
+ private KeyguardSecurityCallback mCallback;
+
+ /**
+ * Keeps track of the last time we poked the wake lock during dispatching of the touch event.
+ * Initialized to something guaranteed to make us poke the wakelock when the user starts
+ * drawing the pattern.
+ * @see #dispatchTouchEvent(android.view.MotionEvent)
+ */
+ private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
+
+ /**
+ * Useful for clearing out the wrong pattern after a delay
+ */
+ private Runnable mCancelPatternRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mLockPatternView.clearPattern();
+ }
+ };
+ private Rect mTempRect = new Rect();
+ private KeyguardMessageArea mSecurityMessageDisplay;
+ private View mEcaView;
+ private ViewGroup mContainer;
+ private int mDisappearYTranslation;
+
+ enum FooterMode {
+ Normal,
+ ForgotLockPattern,
+ VerifyUnlocked
+ }
+
+ public KeyguardPatternView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardPatternView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ mAppearAnimationUtils = new AppearAnimationUtils(context,
+ AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */,
+ 2.0f /* delayScale */, AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.linear_out_slow_in));
+ mDisappearAnimationUtils = new DisappearAnimationUtils(context,
+ 125, 1.2f /* translationScale */,
+ 0.6f /* delayScale */, AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_linear_in));
+ mDisappearAnimationUtilsLocked = new DisappearAnimationUtils(context,
+ (long) (125 * DISAPPEAR_MULTIPLIER_LOCKED), 1.2f /* translationScale */,
+ 0.6f /* delayScale */, AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_linear_in));
+ mDisappearYTranslation = getResources().getDimensionPixelSize(
+ R.dimen.disappear_y_translation);
+ }
+
+ @Override
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mLockPatternUtils = mLockPatternUtils == null
+ ? new LockPatternUtils(mContext) : mLockPatternUtils;
+
+ mLockPatternView = (LockPatternView) findViewById(R.id.lockPatternView);
+ mLockPatternView.setSaveEnabled(false);
+ mLockPatternView.setOnPatternListener(new UnlockPatternListener());
+
+ // vibrate mode will be the same for the life of this screen
+ mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
+
+ mSecurityMessageDisplay =
+ (KeyguardMessageArea) KeyguardMessageArea.findSecurityMessageDisplay(this);
+ mEcaView = findViewById(R.id.keyguard_selector_fade_container);
+ mContainer = (ViewGroup) findViewById(R.id.container);
+
+ EmergencyButton button = (EmergencyButton) findViewById(R.id.emergency_call_button);
+ if (button != null) {
+ button.setCallback(this);
+ }
+ }
+
+ @Override
+ public void onEmergencyButtonClickedWhenInCall() {
+ mCallback.reset();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ boolean result = super.onTouchEvent(ev);
+ // as long as the user is entering a pattern (i.e sending a touch event that was handled
+ // by this screen), keep poking the wake lock so that the screen will stay on.
+ final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime;
+ if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) {
+ mLastPokeTime = SystemClock.elapsedRealtime();
+ }
+ mTempRect.set(0, 0, 0, 0);
+ offsetRectIntoDescendantCoords(mLockPatternView, mTempRect);
+ ev.offsetLocation(mTempRect.left, mTempRect.top);
+ result = mLockPatternView.dispatchTouchEvent(ev) || result;
+ ev.offsetLocation(-mTempRect.left, -mTempRect.top);
+ return result;
+ }
+
+ @Override
+ public void reset() {
+ // reset lock pattern
+ mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
+ KeyguardUpdateMonitor.getCurrentUser()));
+ mLockPatternView.enableInput();
+ mLockPatternView.setEnabled(true);
+ mLockPatternView.clearPattern();
+
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (deadline != 0) {
+ handleAttemptLockout(deadline);
+ } else {
+ displayDefaultSecurityMessage();
+ }
+ }
+
+ private void displayDefaultSecurityMessage() {
+ mSecurityMessageDisplay.setMessage("");
+ }
+
+ @Override
+ public void showUsabilityHint() {
+ }
+
+ /** TODO: hook this up */
+ public void cleanUp() {
+ if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);
+ mLockPatternUtils = null;
+ mLockPatternView.setOnPatternListener(null);
+ }
+
+ private class UnlockPatternListener implements LockPatternView.OnPatternListener {
+
+ @Override
+ public void onPatternStart() {
+ mLockPatternView.removeCallbacks(mCancelPatternRunnable);
+ mSecurityMessageDisplay.setMessage("");
+ }
+
+ @Override
+ public void onPatternCleared() {
+ }
+
+ @Override
+ public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
+ mCallback.userActivity();
+ }
+
+ @Override
+ public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
+ mLockPatternView.disableInput();
+ if (mPendingLockCheck != null) {
+ mPendingLockCheck.cancel(false);
+ }
+
+ final int userId = KeyguardUpdateMonitor.getCurrentUser();
+ if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
+ mLockPatternView.enableInput();
+ onPatternChecked(userId, false, 0, false /* not valid - too short */);
+ return;
+ }
+
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
+ LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ }
+ mPendingLockCheck = LockPatternChecker.checkPattern(
+ mLockPatternUtils,
+ pattern,
+ userId,
+ new LockPatternChecker.OnCheckCallback() {
+
+ @Override
+ public void onEarlyMatched() {
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionEnd(
+ ACTION_CHECK_CREDENTIAL);
+ }
+ onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
+ true /* isValidPattern */);
+ }
+
+ @Override
+ public void onChecked(boolean matched, int timeoutMs) {
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionEnd(
+ ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ }
+ mLockPatternView.enableInput();
+ mPendingLockCheck = null;
+ if (!matched) {
+ onPatternChecked(userId, false /* matched */, timeoutMs,
+ true /* isValidPattern */);
+ }
+ }
+
+ @Override
+ public void onCancelled() {
+ // We already got dismissed with the early matched callback, so we
+ // cancelled the check. However, we still need to note down the latency.
+ if (LatencyTracker.isEnabled(mContext)) {
+ LatencyTracker.getInstance(mContext).onActionEnd(
+ ACTION_CHECK_CREDENTIAL_UNLOCKED);
+ }
+ }
+ });
+ if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
+ mCallback.userActivity();
+ }
+ }
+
+ private void onPatternChecked(int userId, boolean matched, int timeoutMs,
+ boolean isValidPattern) {
+ boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
+ if (matched) {
+ mCallback.reportUnlockAttempt(userId, true, 0);
+ if (dismissKeyguard) {
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
+ mCallback.dismiss(true, userId);
+ }
+ } else {
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
+ if (isValidPattern) {
+ mCallback.reportUnlockAttempt(userId, false, timeoutMs);
+ if (timeoutMs > 0) {
+ long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
+ userId, timeoutMs);
+ handleAttemptLockout(deadline);
+ }
+ }
+ if (timeoutMs == 0) {
+ mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern);
+ mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
+ }
+ }
+ }
+ }
+
+ private void handleAttemptLockout(long elapsedRealtimeDeadline) {
+ mLockPatternView.clearPattern();
+ mLockPatternView.setEnabled(false);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long secondsInFuture = (long) Math.ceil(
+ (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
+ mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ final int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
+ mSecurityMessageDisplay.formatMessage(
+ R.string.kg_too_many_failed_attempts_countdown, secondsRemaining);
+ }
+
+ @Override
+ public void onFinish() {
+ mLockPatternView.setEnabled(true);
+ displayDefaultSecurityMessage();
+ }
+
+ }.start();
+ }
+
+ @Override
+ public boolean needsInput() {
+ return false;
+ }
+
+ @Override
+ public void onPause() {
+ if (mCountdownTimer != null) {
+ mCountdownTimer.cancel();
+ mCountdownTimer = null;
+ }
+ if (mPendingLockCheck != null) {
+ mPendingLockCheck.cancel(false);
+ mPendingLockCheck = null;
+ }
+ }
+
+ @Override
+ public void onResume(int reason) {
+ reset();
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+ @Override
+ public void showPromptReason(int reason) {
+ switch (reason) {
+ case PROMPT_REASON_RESTART:
+ mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_restart_pattern);
+ break;
+ case PROMPT_REASON_TIMEOUT:
+ mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+ break;
+ case PROMPT_REASON_DEVICE_ADMIN:
+ mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_device_admin);
+ break;
+ case PROMPT_REASON_USER_REQUEST:
+ mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request);
+ break;
+ case PROMPT_REASON_NONE:
+ break;
+ default:
+ mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+ break;
+ }
+ }
+
+ @Override
+ public void showMessage(String message, int color) {
+ mSecurityMessageDisplay.setNextMessageColor(color);
+ mSecurityMessageDisplay.setMessage(message);
+ }
+
+ @Override
+ public void startAppearAnimation() {
+ enableClipping(false);
+ setAlpha(1f);
+ setTranslationY(mAppearAnimationUtils.getStartTranslation());
+ AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */,
+ 0, mAppearAnimationUtils.getInterpolator());
+ mAppearAnimationUtils.startAnimation2d(
+ mLockPatternView.getCellStates(),
+ new Runnable() {
+ @Override
+ public void run() {
+ enableClipping(true);
+ }
+ },
+ this);
+ if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
+ mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
+ AppearAnimationUtils.DEFAULT_APPEAR_DURATION,
+ mAppearAnimationUtils.getStartTranslation(),
+ true /* appearing */,
+ mAppearAnimationUtils.getInterpolator(),
+ null /* finishRunnable */);
+ }
+ }
+
+ @Override
+ public boolean startDisappearAnimation(final Runnable finishRunnable) {
+ float durationMultiplier = mKeyguardUpdateMonitor.needsSlowUnlockTransition()
+ ? DISAPPEAR_MULTIPLIER_LOCKED
+ : 1f;
+ mLockPatternView.clearPattern();
+ enableClipping(false);
+ setTranslationY(0);
+ AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */,
+ (long) (300 * durationMultiplier),
+ -mDisappearAnimationUtils.getStartTranslation(),
+ mDisappearAnimationUtils.getInterpolator());
+
+ DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor
+ .needsSlowUnlockTransition()
+ ? mDisappearAnimationUtilsLocked
+ : mDisappearAnimationUtils;
+ disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
+ () -> {
+ enableClipping(true);
+ if (finishRunnable != null) {
+ finishRunnable.run();
+ }
+ }, KeyguardPatternView.this);
+ if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
+ mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
+ (long) (200 * durationMultiplier),
+ - mDisappearAnimationUtils.getStartTranslation() * 3,
+ false /* appearing */,
+ mDisappearAnimationUtils.getInterpolator(),
+ null /* finishRunnable */);
+ }
+ return true;
+ }
+
+ private void enableClipping(boolean enable) {
+ setClipChildren(enable);
+ mContainer.setClipToPadding(enable);
+ mContainer.setClipChildren(enable);
+ }
+
+ @Override
+ public void createAnimation(final LockPatternView.CellState animatedCell, long delay,
+ long duration, float translationY, final boolean appearing,
+ Interpolator interpolator,
+ final Runnable finishListener) {
+ mLockPatternView.startCellStateAnimation(animatedCell,
+ 1f, appearing ? 1f : 0f, /* alpha */
+ appearing ? translationY : 0f, appearing ? 0f : translationY, /* translation */
+ appearing ? 0f : 1f, 1f /* scale */,
+ delay, duration, interpolator, finishListener);
+ if (finishListener != null) {
+ // Also animate the Emergency call
+ mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, translationY,
+ appearing, interpolator, null);
+ }
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
new file mode 100644
index 000000000000..108b466e44af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -0,0 +1,255 @@
+/*
+ * 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.keyguard;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * A Pin based Keyguard input view
+ */
+public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
+ implements View.OnKeyListener, View.OnTouchListener {
+
+ protected PasswordTextView mPasswordEntry;
+ private View mOkButton;
+ private View mDeleteButton;
+ private View mButton0;
+ private View mButton1;
+ private View mButton2;
+ private View mButton3;
+ private View mButton4;
+ private View mButton5;
+ private View mButton6;
+ private View mButton7;
+ private View mButton8;
+ private View mButton9;
+
+ public KeyguardPinBasedInputView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardPinBasedInputView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void reset() {
+ mPasswordEntry.requestFocus();
+ super.reset();
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ // send focus to the password field
+ return mPasswordEntry.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ @Override
+ protected void resetState() {
+ setPasswordEntryEnabled(true);
+ }
+
+ @Override
+ protected void setPasswordEntryEnabled(boolean enabled) {
+ mPasswordEntry.setEnabled(enabled);
+ mOkButton.setEnabled(enabled);
+ }
+
+ @Override
+ protected void setPasswordEntryInputEnabled(boolean enabled) {
+ mPasswordEntry.setEnabled(enabled);
+ mOkButton.setEnabled(enabled);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (KeyEvent.isConfirmKey(keyCode)) {
+ performClick(mOkButton);
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_DEL) {
+ performClick(mDeleteButton);
+ return true;
+ }
+ if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
+ int number = keyCode - KeyEvent.KEYCODE_0;
+ performNumberClick(number);
+ return true;
+ }
+ if (keyCode >= KeyEvent.KEYCODE_NUMPAD_0 && keyCode <= KeyEvent.KEYCODE_NUMPAD_9) {
+ int number = keyCode - KeyEvent.KEYCODE_NUMPAD_0;
+ performNumberClick(number);
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ protected int getPromtReasonStringRes(int reason) {
+ switch (reason) {
+ case PROMPT_REASON_RESTART:
+ return R.string.kg_prompt_reason_restart_pin;
+ case PROMPT_REASON_TIMEOUT:
+ return R.string.kg_prompt_reason_timeout_pin;
+ case PROMPT_REASON_DEVICE_ADMIN:
+ return R.string.kg_prompt_reason_device_admin;
+ case PROMPT_REASON_USER_REQUEST:
+ return R.string.kg_prompt_reason_user_request;
+ case PROMPT_REASON_NONE:
+ return 0;
+ default:
+ return R.string.kg_prompt_reason_timeout_pin;
+ }
+ }
+
+ private void performClick(View view) {
+ view.performClick();
+ }
+
+ private void performNumberClick(int number) {
+ switch (number) {
+ case 0:
+ performClick(mButton0);
+ break;
+ case 1:
+ performClick(mButton1);
+ break;
+ case 2:
+ performClick(mButton2);
+ break;
+ case 3:
+ performClick(mButton3);
+ break;
+ case 4:
+ performClick(mButton4);
+ break;
+ case 5:
+ performClick(mButton5);
+ break;
+ case 6:
+ performClick(mButton6);
+ break;
+ case 7:
+ performClick(mButton7);
+ break;
+ case 8:
+ performClick(mButton8);
+ break;
+ case 9:
+ performClick(mButton9);
+ break;
+ }
+ }
+
+ @Override
+ protected void resetPasswordText(boolean animate, boolean announce) {
+ mPasswordEntry.reset(animate, announce);
+ }
+
+ @Override
+ protected String getPasswordText() {
+ return mPasswordEntry.getText();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mPasswordEntry = (PasswordTextView) findViewById(getPasswordTextViewId());
+ mPasswordEntry.setOnKeyListener(this);
+
+ // Set selected property on so the view can send accessibility events.
+ mPasswordEntry.setSelected(true);
+
+ mPasswordEntry.setUserActivityListener(new PasswordTextView.UserActivityListener() {
+ @Override
+ public void onUserActivity() {
+ onUserInput();
+ }
+ });
+
+ mOkButton = findViewById(R.id.key_enter);
+ if (mOkButton != null) {
+ mOkButton.setOnTouchListener(this);
+ mOkButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mPasswordEntry.isEnabled()) {
+ verifyPasswordAndUnlock();
+ }
+ }
+ });
+ mOkButton.setOnHoverListener(new LiftToActivateListener(getContext()));
+ }
+
+ mDeleteButton = findViewById(R.id.delete_button);
+ mDeleteButton.setVisibility(View.VISIBLE);
+ mDeleteButton.setOnTouchListener(this);
+ mDeleteButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // check for time-based lockouts
+ if (mPasswordEntry.isEnabled()) {
+ mPasswordEntry.deleteLastChar();
+ }
+ }
+ });
+ mDeleteButton.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ // check for time-based lockouts
+ if (mPasswordEntry.isEnabled()) {
+ resetPasswordText(true /* animate */, true /* announce */);
+ }
+ doHapticKeyClick();
+ return true;
+ }
+ });
+
+ mButton0 = findViewById(R.id.key0);
+ mButton1 = findViewById(R.id.key1);
+ mButton2 = findViewById(R.id.key2);
+ mButton3 = findViewById(R.id.key3);
+ mButton4 = findViewById(R.id.key4);
+ mButton5 = findViewById(R.id.key5);
+ mButton6 = findViewById(R.id.key6);
+ mButton7 = findViewById(R.id.key7);
+ mButton8 = findViewById(R.id.key8);
+ mButton9 = findViewById(R.id.key9);
+
+ mPasswordEntry.requestFocus();
+ super.onFinishInflate();
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ doHapticKeyClick();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ return onKeyDown(keyCode, event);
+ }
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
new file mode 100644
index 000000000000..5b743c1a20c5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+public interface KeyguardSecurityCallback {
+
+ /**
+ * Dismiss the given security screen.
+ * @param securityVerified true if the user correctly entered credentials for the given screen.
+ * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
+ */
+ void dismiss(boolean securityVerified, int targetUserId);
+
+ /**
+ * Manually report user activity to keep the device awake.
+ */
+ void userActivity();
+
+ /**
+ * Checks if keyguard is in "verify credentials" mode.
+ * @return true if user has been asked to verify security.
+ */
+ boolean isVerifyUnlockOnly();
+
+ /**
+ * Call to report an unlock attempt.
+ * @param userId id of the user whose unlock attempt is recorded.
+ * @param success set to 'true' if user correctly entered security credentials.
+ * @param timeoutMs timeout in milliseconds to wait before reattempting an unlock.
+ * Only nonzero if 'success' is false
+ */
+ void reportUnlockAttempt(int userId, boolean success, int timeoutMs);
+
+ /**
+ * Resets the keyguard view.
+ */
+ void reset();
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
new file mode 100644
index 000000000000..8cdb906a0e56
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -0,0 +1,546 @@
+/*
+ * 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.keyguard;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserHandle;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+
+public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSecurityView {
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+ private static final String TAG = "KeyguardSecurityView";
+
+ private static final int USER_TYPE_PRIMARY = 1;
+ private static final int USER_TYPE_WORK_PROFILE = 2;
+ private static final int USER_TYPE_SECONDARY_USER = 3;
+
+ private KeyguardSecurityModel mSecurityModel;
+ private LockPatternUtils mLockPatternUtils;
+
+ private KeyguardSecurityViewFlipper mSecurityViewFlipper;
+ private boolean mIsVerifyUnlockOnly;
+ private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid;
+ private SecurityCallback mSecurityCallback;
+
+ private final KeyguardUpdateMonitor mUpdateMonitor;
+
+ // Used to notify the container when something interesting happens.
+ public interface SecurityCallback {
+ public boolean dismiss(boolean authenticated, int targetUserId);
+ public void userActivity();
+ public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
+
+ /**
+ * @param strongAuth wheher the user has authenticated with strong authentication like
+ * pattern, password or PIN but not by trust agents or fingerprint
+ * @param targetUserId a user that needs to be the foreground user at the finish completion.
+ */
+ public void finish(boolean strongAuth, int targetUserId);
+ public void reset();
+ }
+
+ public KeyguardSecurityContainer(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeyguardSecurityContainer(Context context) {
+ this(context, null, 0);
+ }
+
+ public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mSecurityModel = new KeyguardSecurityModel(context);
+ mLockPatternUtils = new LockPatternUtils(context);
+ mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ }
+
+ public void setSecurityCallback(SecurityCallback callback) {
+ mSecurityCallback = callback;
+ }
+
+ @Override
+ public void onResume(int reason) {
+ if (mCurrentSecuritySelection != SecurityMode.None) {
+ getSecurityView(mCurrentSecuritySelection).onResume(reason);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ if (mCurrentSecuritySelection != SecurityMode.None) {
+ getSecurityView(mCurrentSecuritySelection).onPause();
+ }
+ }
+
+ public void startAppearAnimation() {
+ if (mCurrentSecuritySelection != SecurityMode.None) {
+ getSecurityView(mCurrentSecuritySelection).startAppearAnimation();
+ }
+ }
+
+ public boolean startDisappearAnimation(Runnable onFinishRunnable) {
+ if (mCurrentSecuritySelection != SecurityMode.None) {
+ return getSecurityView(mCurrentSecuritySelection).startDisappearAnimation(
+ onFinishRunnable);
+ }
+ return false;
+ }
+
+ public void announceCurrentSecurityMethod() {
+ View v = (View) getSecurityView(mCurrentSecuritySelection);
+ if (v != null) {
+ v.announceForAccessibility(v.getContentDescription());
+ }
+ }
+
+ public CharSequence getCurrentSecurityModeContentDescription() {
+ View v = (View) getSecurityView(mCurrentSecuritySelection);
+ if (v != null) {
+ return v.getContentDescription();
+ }
+ return "";
+ }
+
+ private KeyguardSecurityView getSecurityView(SecurityMode securityMode) {
+ final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
+ KeyguardSecurityView view = null;
+ final int children = mSecurityViewFlipper.getChildCount();
+ for (int child = 0; child < children; child++) {
+ if (mSecurityViewFlipper.getChildAt(child).getId() == securityViewIdForMode) {
+ view = ((KeyguardSecurityView)mSecurityViewFlipper.getChildAt(child));
+ break;
+ }
+ }
+ int layoutId = getLayoutIdFor(securityMode);
+ if (view == null && layoutId != 0) {
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
+ View v = inflater.inflate(layoutId, mSecurityViewFlipper, false);
+ mSecurityViewFlipper.addView(v);
+ updateSecurityView(v);
+ view = (KeyguardSecurityView)v;
+ }
+
+ return view;
+ }
+
+ private void updateSecurityView(View view) {
+ if (view instanceof KeyguardSecurityView) {
+ KeyguardSecurityView ksv = (KeyguardSecurityView) view;
+ ksv.setKeyguardCallback(mCallback);
+ ksv.setLockPatternUtils(mLockPatternUtils);
+ } else {
+ Log.w(TAG, "View " + view + " is not a KeyguardSecurityView");
+ }
+ }
+
+ protected void onFinishInflate() {
+ mSecurityViewFlipper = (KeyguardSecurityViewFlipper) findViewById(R.id.view_flipper);
+ mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils);
+ }
+
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ mSecurityModel.setLockPatternUtils(utils);
+ mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils);
+ }
+
+ private void showDialog(String title, String message) {
+ final AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setTitle(title)
+ .setMessage(message)
+ .setCancelable(false)
+ .setNeutralButton(R.string.ok, null)
+ .create();
+ if (!(mContext instanceof Activity)) {
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ }
+ dialog.show();
+ }
+
+ private void showTimeoutDialog(int userId, int timeoutMs) {
+ int timeoutInSeconds = (int) timeoutMs / 1000;
+ int messageId = 0;
+
+ switch (mSecurityModel.getSecurityMode(userId)) {
+ case Pattern:
+ messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message;
+ break;
+ case PIN:
+ messageId = R.string.kg_too_many_failed_pin_attempts_dialog_message;
+ break;
+ case Password:
+ messageId = R.string.kg_too_many_failed_password_attempts_dialog_message;
+ break;
+ // These don't have timeout dialogs.
+ case Invalid:
+ case None:
+ case SimPin:
+ case SimPuk:
+ break;
+ }
+
+ if (messageId != 0) {
+ final String message = mContext.getString(messageId,
+ KeyguardUpdateMonitor.getInstance(mContext).getFailedUnlockAttempts(userId),
+ timeoutInSeconds);
+ showDialog(null, message);
+ }
+ }
+
+ private void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
+ String message = null;
+ switch (userType) {
+ case USER_TYPE_PRIMARY:
+ message = mContext.getString(R.string.kg_failed_attempts_almost_at_wipe,
+ attempts, remaining);
+ break;
+ case USER_TYPE_SECONDARY_USER:
+ message = mContext.getString(R.string.kg_failed_attempts_almost_at_erase_user,
+ attempts, remaining);
+ break;
+ case USER_TYPE_WORK_PROFILE:
+ message = mContext.getString(R.string.kg_failed_attempts_almost_at_erase_profile,
+ attempts, remaining);
+ break;
+ }
+ showDialog(null, message);
+ }
+
+ private void showWipeDialog(int attempts, int userType) {
+ String message = null;
+ switch (userType) {
+ case USER_TYPE_PRIMARY:
+ message = mContext.getString(R.string.kg_failed_attempts_now_wiping,
+ attempts);
+ break;
+ case USER_TYPE_SECONDARY_USER:
+ message = mContext.getString(R.string.kg_failed_attempts_now_erasing_user,
+ attempts);
+ break;
+ case USER_TYPE_WORK_PROFILE:
+ message = mContext.getString(R.string.kg_failed_attempts_now_erasing_profile,
+ attempts);
+ break;
+ }
+ showDialog(null, message);
+ }
+
+ private void reportFailedUnlockAttempt(int userId, int timeoutMs) {
+ final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+ final int failedAttempts = monitor.getFailedUnlockAttempts(userId) + 1; // +1 for this time
+
+ if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);
+
+ final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
+ final int failedAttemptsBeforeWipe =
+ dpm.getMaximumFailedPasswordsForWipe(null, userId);
+
+ final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ?
+ (failedAttemptsBeforeWipe - failedAttempts)
+ : Integer.MAX_VALUE; // because DPM returns 0 if no restriction
+ if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
+ // The user has installed a DevicePolicyManager that requests a user/profile to be wiped
+ // N attempts. Once we get below the grace period, we post this dialog every time as a
+ // clear warning until the deletion fires.
+ // Check which profile has the strictest policy for failed password attempts
+ final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId);
+ int userType = USER_TYPE_PRIMARY;
+ if (expiringUser == userId) {
+ // TODO: http://b/23522538
+ if (expiringUser != UserHandle.USER_SYSTEM) {
+ userType = USER_TYPE_SECONDARY_USER;
+ }
+ } else if (expiringUser != UserHandle.USER_NULL) {
+ userType = USER_TYPE_WORK_PROFILE;
+ } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
+ if (remainingBeforeWipe > 0) {
+ showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType);
+ } else {
+ // Too many attempts. The device will be wiped shortly.
+ Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!");
+ showWipeDialog(failedAttempts, userType);
+ }
+ }
+ monitor.reportFailedStrongAuthUnlockAttempt(userId);
+ mLockPatternUtils.reportFailedPasswordAttempt(userId);
+ if (timeoutMs > 0) {
+ mLockPatternUtils.reportPasswordLockout(timeoutMs, userId);
+ showTimeoutDialog(userId, timeoutMs);
+ }
+ }
+
+ /**
+ * Shows the primary security screen for the user. This will be either the multi-selector
+ * or the user's security method.
+ * @param turningOff true if the device is being turned off
+ */
+ void showPrimarySecurityScreen(boolean turningOff) {
+ SecurityMode securityMode = mSecurityModel.getSecurityMode(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")");
+ showSecurityScreen(securityMode);
+ }
+
+ /**
+ * Shows the next security screen if there is one.
+ * @param authenticated true if the user entered the correct authentication
+ * @param targetUserId a user that needs to be the foreground user at the finish (if called)
+ * completion.
+ * @return true if keyguard is done
+ */
+ boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId) {
+ if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")");
+ boolean finish = false;
+ boolean strongAuth = false;
+ if (mUpdateMonitor.getUserCanSkipBouncer(targetUserId)) {
+ finish = true;
+ } else if (SecurityMode.None == mCurrentSecuritySelection) {
+ SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
+ if (SecurityMode.None == securityMode) {
+ finish = true; // no security required
+ } else {
+ showSecurityScreen(securityMode); // switch to the alternate security view
+ }
+ } else if (authenticated) {
+ switch (mCurrentSecuritySelection) {
+ case Pattern:
+ case Password:
+ case PIN:
+ strongAuth = true;
+ finish = true;
+ break;
+
+ case SimPin:
+ case SimPuk:
+ // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
+ SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
+ if (securityMode != SecurityMode.None
+ || !mLockPatternUtils.isLockScreenDisabled(
+ KeyguardUpdateMonitor.getCurrentUser())) {
+ showSecurityScreen(securityMode);
+ } else {
+ finish = true;
+ }
+ break;
+
+ default:
+ Log.v(TAG, "Bad security screen " + mCurrentSecuritySelection + ", fail safe");
+ showPrimarySecurityScreen(false);
+ break;
+ }
+ }
+ if (finish) {
+ mSecurityCallback.finish(strongAuth, targetUserId);
+ }
+ return finish;
+ }
+
+ /**
+ * Switches to the given security view unless it's already being shown, in which case
+ * this is a no-op.
+ *
+ * @param securityMode
+ */
+ private void showSecurityScreen(SecurityMode securityMode) {
+ if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")");
+
+ if (securityMode == mCurrentSecuritySelection) return;
+
+ KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection);
+ KeyguardSecurityView newView = getSecurityView(securityMode);
+
+ // Emulate Activity life cycle
+ if (oldView != null) {
+ oldView.onPause();
+ oldView.setKeyguardCallback(mNullCallback); // ignore requests from old view
+ }
+ if (securityMode != SecurityMode.None) {
+ newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
+ newView.setKeyguardCallback(mCallback);
+ }
+
+ // Find and show this child.
+ final int childCount = mSecurityViewFlipper.getChildCount();
+
+ final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
+ for (int i = 0; i < childCount; i++) {
+ if (mSecurityViewFlipper.getChildAt(i).getId() == securityViewIdForMode) {
+ mSecurityViewFlipper.setDisplayedChild(i);
+ break;
+ }
+ }
+
+ mCurrentSecuritySelection = securityMode;
+ mSecurityCallback.onSecurityModeChanged(securityMode,
+ securityMode != SecurityMode.None && newView.needsInput());
+ }
+
+ private KeyguardSecurityViewFlipper getFlipper() {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child instanceof KeyguardSecurityViewFlipper) {
+ return (KeyguardSecurityViewFlipper) child;
+ }
+ }
+ return null;
+ }
+
+ private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() {
+ public void userActivity() {
+ if (mSecurityCallback != null) {
+ mSecurityCallback.userActivity();
+ }
+ }
+
+ public void dismiss(boolean authenticated, int targetId) {
+ mSecurityCallback.dismiss(authenticated, targetId);
+ }
+
+ public boolean isVerifyUnlockOnly() {
+ return mIsVerifyUnlockOnly;
+ }
+
+ public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
+ KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+ if (success) {
+ monitor.clearFailedUnlockAttempts();
+ mLockPatternUtils.reportSuccessfulPasswordAttempt(userId);
+ } else {
+ KeyguardSecurityContainer.this.reportFailedUnlockAttempt(userId, timeoutMs);
+ }
+ }
+
+ public void reset() {
+ mSecurityCallback.reset();
+ }
+ };
+
+ // The following is used to ignore callbacks from SecurityViews that are no longer current
+ // (e.g. face unlock). This avoids unwanted asynchronous events from messing with the
+ // state for the current security method.
+ private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {
+ @Override
+ public void userActivity() { }
+ @Override
+ public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { }
+ @Override
+ public boolean isVerifyUnlockOnly() { return false; }
+ @Override
+ public void dismiss(boolean securityVerified, int targetUserId) { }
+ @Override
+ public void reset() {}
+ };
+
+ private int getSecurityViewIdForMode(SecurityMode securityMode) {
+ switch (securityMode) {
+ case Pattern: return R.id.keyguard_pattern_view;
+ case PIN: return R.id.keyguard_pin_view;
+ case Password: return R.id.keyguard_password_view;
+ case SimPin: return R.id.keyguard_sim_pin_view;
+ case SimPuk: return R.id.keyguard_sim_puk_view;
+ }
+ return 0;
+ }
+
+ protected int getLayoutIdFor(SecurityMode securityMode) {
+ switch (securityMode) {
+ case Pattern: return R.layout.keyguard_pattern_view;
+ case PIN: return R.layout.keyguard_pin_view;
+ case Password: return R.layout.keyguard_password_view;
+ case SimPin: return R.layout.keyguard_sim_pin_view;
+ case SimPuk: return R.layout.keyguard_sim_puk_view;
+ default:
+ return 0;
+ }
+ }
+
+ public SecurityMode getSecurityMode() {
+ return mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser());
+ }
+
+ public SecurityMode getCurrentSecurityMode() {
+ return mCurrentSecuritySelection;
+ }
+
+ public void verifyUnlock() {
+ mIsVerifyUnlockOnly = true;
+ showSecurityScreen(getSecurityMode());
+ }
+
+ public SecurityMode getCurrentSecuritySelection() {
+ return mCurrentSecuritySelection;
+ }
+
+ public void dismiss(boolean authenticated, int targetUserId) {
+ mCallback.dismiss(authenticated, targetUserId);
+ }
+
+ public boolean needsInput() {
+ return mSecurityViewFlipper.needsInput();
+ }
+
+ @Override
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mSecurityViewFlipper.setKeyguardCallback(callback);
+ }
+
+ @Override
+ public void reset() {
+ mSecurityViewFlipper.reset();
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ return mSecurityViewFlipper.getCallback();
+ }
+
+ @Override
+ public void showPromptReason(int reason) {
+ if (mCurrentSecuritySelection != SecurityMode.None) {
+ if (reason != PROMPT_REASON_NONE) {
+ Log.i(TAG, "Strong auth required, reason: " + reason);
+ }
+ getSecurityView(mCurrentSecuritySelection).showPromptReason(reason);
+ }
+ }
+
+
+ public void showMessage(String message, int color) {
+ if (mCurrentSecuritySelection != SecurityMode.None) {
+ getSecurityView(mCurrentSecuritySelection).showMessage(message, color);
+ }
+ }
+
+ @Override
+ public void showUsabilityHint() {
+ mSecurityViewFlipper.showUsabilityHint();
+ }
+
+}
+
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
new file mode 100644
index 000000000000..7baa57e7dae7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.widget.LockPatternUtils;
+
+public class KeyguardSecurityModel {
+
+ /**
+ * The different types of security available.
+ * @see KeyguardSecurityContainer#showSecurityScreen
+ */
+ public enum SecurityMode {
+ Invalid, // NULL state
+ None, // No security enabled
+ Pattern, // Unlock by drawing a pattern.
+ Password, // Unlock by entering an alphanumeric password
+ PIN, // Strictly numeric password
+ SimPin, // Unlock by entering a sim pin.
+ SimPuk // Unlock by entering a sim puk
+ }
+
+ private final Context mContext;
+ private final boolean mIsPukScreenAvailable;
+
+ private LockPatternUtils mLockPatternUtils;
+
+ KeyguardSecurityModel(Context context) {
+ mContext = context;
+ mLockPatternUtils = new LockPatternUtils(context);
+ mIsPukScreenAvailable = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enable_puk_unlock_screen);
+ }
+
+ void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ SecurityMode getSecurityMode(int userId) {
+ KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+
+ if (SubscriptionManager.isValidSubscriptionId(
+ monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) {
+ return SecurityMode.SimPin;
+ }
+
+ if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
+ monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))) {
+ return SecurityMode.SimPuk;
+ }
+
+ final int security = mLockPatternUtils.getActivePasswordQuality(userId);
+ switch (security) {
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
+ return SecurityMode.PIN;
+
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
+ case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
+ return SecurityMode.Password;
+
+ case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
+ return SecurityMode.Pattern;
+ case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
+ return SecurityMode.None;
+
+ default:
+ throw new IllegalStateException("Unknown security quality:" + security);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
new file mode 100644
index 000000000000..829084202f5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+import com.android.internal.widget.LockPatternUtils;
+
+public interface KeyguardSecurityView {
+ static public final int SCREEN_ON = 1;
+ static public final int VIEW_REVEALED = 2;
+
+ int PROMPT_REASON_NONE = 0;
+
+ /**
+ * Strong auth is required because the device has just booted.
+ */
+ int PROMPT_REASON_RESTART = 1;
+
+ /**
+ * Strong auth is required because the user hasn't used strong auth since a while.
+ */
+ int PROMPT_REASON_TIMEOUT = 2;
+
+ /**
+ * Strong auth is required because a device admin requested it.
+ */
+ int PROMPT_REASON_DEVICE_ADMIN = 3;
+
+ /**
+ * Some auth is required because the user force locked.
+ */
+ int PROMPT_REASON_USER_REQUEST = 4;
+
+ /**
+ * Some auth is required because too many wrong credentials led to a lockout.
+ */
+ int PROMPT_REASON_AFTER_LOCKOUT = 5;
+
+ /**
+ * Interface back to keyguard to tell it when security
+ * @param callback
+ */
+ void setKeyguardCallback(KeyguardSecurityCallback callback);
+
+ /**
+ * Set {@link LockPatternUtils} object. Useful for providing a mock interface.
+ * @param utils
+ */
+ void setLockPatternUtils(LockPatternUtils utils);
+
+ /**
+ * Reset the view and prepare to take input. This should do things like clearing the
+ * password or pattern and clear error messages.
+ */
+ void reset();
+
+ /**
+ * Emulate activity life cycle within the view. When called, the view should clean up
+ * and prepare to be removed.
+ */
+ void onPause();
+
+ /**
+ * Emulate activity life cycle within this view. When called, the view should prepare itself
+ * to be shown.
+ * @param reason the root cause of the event.
+ */
+ void onResume(int reason);
+
+ /**
+ * Inquire whether this view requires IME (keyboard) interaction.
+ *
+ * @return true if IME interaction is required.
+ */
+ boolean needsInput();
+
+ /**
+ * Get {@link KeyguardSecurityCallback} for the given object
+ * @return KeyguardSecurityCallback
+ */
+ KeyguardSecurityCallback getCallback();
+
+ /**
+ * Show a string explaining why the security view needs to be solved.
+ *
+ * @param reason a flag indicating which string should be shown, see {@link #PROMPT_REASON_NONE}
+ * and {@link #PROMPT_REASON_RESTART}
+ */
+ void showPromptReason(int reason);
+
+ /**
+ * Show a message on the security view with a specified color
+ *
+ * @param message the message to show
+ * @param color the color to use
+ */
+ void showMessage(String message, int color);
+
+ /**
+ * Instruct the view to show usability hints, if any.
+ *
+ */
+ void showUsabilityHint();
+
+ /**
+ * Starts the animation which should run when the security view appears.
+ */
+ void startAppearAnimation();
+
+ /**
+ * Starts the animation which should run when the security view disappears.
+ *
+ * @param finishRunnable the runnable to be run when the animation ended
+ * @return true if an animation started and {@code finishRunnable} will be run, false if no
+ * animation started and {@code finishRunnable} will not be run
+ */
+ boolean startDisappearAnimation(Runnable finishRunnable);
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
new file mode 100644
index 000000000000..6012c4501412
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ViewFlipper;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import java.lang.Override;
+
+/**
+ * Subclass of the current view flipper that allows us to overload dispatchTouchEvent() so
+ * we can emulate {@link WindowManager.LayoutParams#FLAG_SLIPPERY} within a view hierarchy.
+ *
+ */
+public class KeyguardSecurityViewFlipper extends ViewFlipper implements KeyguardSecurityView {
+ private static final String TAG = "KeyguardSecurityViewFlipper";
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+
+ private Rect mTempRect = new Rect();
+
+ public KeyguardSecurityViewFlipper(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardSecurityViewFlipper(Context context, AttributeSet attr) {
+ super(context, attr);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ boolean result = super.onTouchEvent(ev);
+ mTempRect.set(0, 0, 0, 0);
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() == View.VISIBLE) {
+ offsetRectIntoDescendantCoords(child, mTempRect);
+ ev.offsetLocation(mTempRect.left, mTempRect.top);
+ result = child.dispatchTouchEvent(ev) || result;
+ ev.offsetLocation(-mTempRect.left, -mTempRect.top);
+ }
+ }
+ return result;
+ }
+
+ KeyguardSecurityView getSecurityView() {
+ View child = getChildAt(getDisplayedChild());
+ if (child instanceof KeyguardSecurityView) {
+ return (KeyguardSecurityView) child;
+ }
+ return null;
+ }
+
+ @Override
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.setKeyguardCallback(callback);
+ }
+ }
+
+ @Override
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.setLockPatternUtils(utils);
+ }
+ }
+
+ @Override
+ public void reset() {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.reset();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.onPause();
+ }
+ }
+
+ @Override
+ public void onResume(int reason) {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.onResume(reason);
+ }
+ }
+
+ @Override
+ public boolean needsInput() {
+ KeyguardSecurityView ksv = getSecurityView();
+ return (ksv != null) ? ksv.needsInput() : false;
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ KeyguardSecurityView ksv = getSecurityView();
+ return (ksv != null) ? ksv.getCallback() : null;
+ }
+
+ @Override
+ public void showPromptReason(int reason) {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.showPromptReason(reason);
+ }
+ }
+
+ @Override
+ public void showMessage(String message, int color) {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.showMessage(message, color);
+ }
+ }
+
+ @Override
+ public void showUsabilityHint() {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.showUsabilityHint();
+ }
+ }
+
+ @Override
+ public void startAppearAnimation() {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.startAppearAnimation();
+ }
+ }
+
+ @Override
+ public boolean startDisappearAnimation(Runnable finishRunnable) {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ return ksv.startDisappearAnimation(finishRunnable);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams;
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams ? new LayoutParams((LayoutParams) p) : new LayoutParams(p);
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected void onMeasure(int widthSpec, int heightSpec) {
+ final int widthMode = MeasureSpec.getMode(widthSpec);
+ final int heightMode = MeasureSpec.getMode(heightSpec);
+ if (DEBUG && widthMode != MeasureSpec.AT_MOST) {
+ Log.w(TAG, "onMeasure: widthSpec " + MeasureSpec.toString(widthSpec) +
+ " should be AT_MOST");
+ }
+ if (DEBUG && heightMode != MeasureSpec.AT_MOST) {
+ Log.w(TAG, "onMeasure: heightSpec " + MeasureSpec.toString(heightSpec) +
+ " should be AT_MOST");
+ }
+
+ final int widthSize = MeasureSpec.getSize(widthSpec);
+ final int heightSize = MeasureSpec.getSize(heightSpec);
+ int maxWidth = widthSize;
+ int maxHeight = heightSize;
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ if (lp.maxWidth > 0 && lp.maxWidth < maxWidth) {
+ maxWidth = lp.maxWidth;
+ }
+ if (lp.maxHeight > 0 && lp.maxHeight < maxHeight) {
+ maxHeight = lp.maxHeight;
+ }
+ }
+
+ final int wPadding = getPaddingLeft() + getPaddingRight();
+ final int hPadding = getPaddingTop() + getPaddingBottom();
+ maxWidth = Math.max(0, maxWidth - wPadding);
+ maxHeight = Math.max(0, maxHeight - hPadding);
+
+ int width = widthMode == MeasureSpec.EXACTLY ? widthSize : 0;
+ int height = heightMode == MeasureSpec.EXACTLY ? heightSize : 0;
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ final int childWidthSpec = makeChildMeasureSpec(maxWidth, lp.width);
+ final int childHeightSpec = makeChildMeasureSpec(maxHeight, lp.height);
+
+ child.measure(childWidthSpec, childHeightSpec);
+
+ width = Math.max(width, Math.min(child.getMeasuredWidth(), widthSize - wPadding));
+ height = Math.max(height, Math.min(child.getMeasuredHeight(), heightSize - hPadding));
+ }
+ setMeasuredDimension(width + wPadding, height + hPadding);
+ }
+
+ private int makeChildMeasureSpec(int maxSize, int childDimen) {
+ final int mode;
+ final int size;
+ switch (childDimen) {
+ case LayoutParams.WRAP_CONTENT:
+ mode = MeasureSpec.AT_MOST;
+ size = maxSize;
+ break;
+ case LayoutParams.MATCH_PARENT:
+ mode = MeasureSpec.EXACTLY;
+ size = maxSize;
+ break;
+ default:
+ mode = MeasureSpec.EXACTLY;
+ size = Math.min(maxSize, childDimen);
+ break;
+ }
+ return MeasureSpec.makeMeasureSpec(size, mode);
+ }
+
+ public static class LayoutParams extends FrameLayout.LayoutParams {
+ @ViewDebug.ExportedProperty(category = "layout")
+ public int maxWidth;
+
+ @ViewDebug.ExportedProperty(category = "layout")
+ public int maxHeight;
+
+ public LayoutParams(ViewGroup.LayoutParams other) {
+ super(other);
+ }
+
+ public LayoutParams(LayoutParams other) {
+ super(other);
+
+ maxWidth = other.maxWidth;
+ maxHeight = other.maxHeight;
+ }
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+
+ final TypedArray a = c.obtainStyledAttributes(attrs,
+ R.styleable.KeyguardSecurityViewFlipper_Layout, 0, 0);
+ maxWidth = a.getDimensionPixelSize(
+ R.styleable.KeyguardSecurityViewFlipper_Layout_layout_maxWidth, 0);
+ maxHeight = a.getDimensionPixelSize(
+ R.styleable.KeyguardSecurityViewFlipper_Layout_layout_maxHeight, 0);
+ a.recycle();
+ }
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("layout:maxWidth", maxWidth);
+ encoder.addProperty("layout:maxHeight", maxHeight);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
new file mode 100644
index 000000000000..839d3cec5e96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.IccCardConstants.State;
+import com.android.internal.telephony.PhoneConstants;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.graphics.Color;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+/**
+ * Displays a PIN pad for unlocking.
+ */
+public class KeyguardSimPinView extends KeyguardPinBasedInputView {
+ private static final String LOG_TAG = "KeyguardSimPinView";
+ private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES;
+ public static final String TAG = "KeyguardSimPinView";
+
+ private ProgressDialog mSimUnlockProgressDialog = null;
+ private CheckSimPin mCheckSimPinThread;
+
+ private AlertDialog mRemainingAttemptsDialog;
+ private int mSubId;
+ private ImageView mSimImageView;
+
+ KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onSimStateChanged(int subId, int slotId, State simState) {
+ if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
+ resetState();
+ };
+ };
+
+ public KeyguardSimPinView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardSimPinView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void resetState() {
+ super.resetState();
+ if (DEBUG) Log.v(TAG, "Resetting state");
+ KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+ mSubId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED);
+ if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
+ int count = TelephonyManager.getDefault().getSimCount();
+ Resources rez = getResources();
+ final String msg;
+ int color = Color.WHITE;
+ if (count < 2) {
+ msg = rez.getString(R.string.kg_sim_pin_instructions);
+ } else {
+ SubscriptionInfo info = monitor.getSubscriptionInfoForSubId(mSubId);
+ CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
+ msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
+ if (info != null) {
+ color = info.getIconTint();
+ }
+ }
+ mSecurityMessageDisplay.setMessage(msg);
+ mSimImageView.setImageTintList(ColorStateList.valueOf(color));
+ }
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ resetState();
+ }
+
+ @Override
+ protected int getPromtReasonStringRes(int reason) {
+ // No message on SIM Pin
+ return 0;
+ }
+
+ private String getPinPasswordErrorMessage(int attemptsRemaining) {
+ String displayMessage;
+
+ if (attemptsRemaining == 0) {
+ displayMessage = getContext().getString(R.string.kg_password_wrong_pin_code_pukked);
+ } else if (attemptsRemaining > 0) {
+ displayMessage = getContext().getResources()
+ .getQuantityString(R.plurals.kg_password_wrong_pin_code, attemptsRemaining,
+ attemptsRemaining);
+ } else {
+ displayMessage = getContext().getString(R.string.kg_password_pin_failed);
+ }
+ if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:"
+ + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
+ return displayMessage;
+ }
+
+ @Override
+ protected boolean shouldLockout(long deadline) {
+ // SIM PIN doesn't have a timed lockout
+ return false;
+ }
+
+ @Override
+ protected int getPasswordTextViewId() {
+ return R.id.simPinEntry;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ if (mEcaView instanceof EmergencyCarrierArea) {
+ ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true);
+ }
+ mSimImageView = (ImageView) findViewById(R.id.keyguard_sim);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
+ }
+
+ @Override
+ public void showUsabilityHint() {
+ }
+
+ @Override
+ public void onPause() {
+ // dismiss the dialog.
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.dismiss();
+ mSimUnlockProgressDialog = null;
+ }
+ }
+
+ /**
+ * Since the IPC can block, we want to run the request in a separate thread
+ * with a callback.
+ */
+ private abstract class CheckSimPin extends Thread {
+ private final String mPin;
+ private int mSubId;
+
+ protected CheckSimPin(String pin, int subId) {
+ mPin = pin;
+ mSubId = subId;
+ }
+
+ abstract void onSimCheckResponse(final int result, final int attemptsRemaining);
+
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")");
+ }
+ final int[] result = ITelephony.Stub.asInterface(ServiceManager
+ .checkService("phone")).supplyPinReportResultForSubscriber(mSubId, mPin);
+ if (DEBUG) {
+ Log.v(TAG, "supplyPinReportResult returned: " + result[0] + " " + result[1]);
+ }
+ post(new Runnable() {
+ @Override
+ public void run() {
+ onSimCheckResponse(result[0], result[1]);
+ }
+ });
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException for supplyPinReportResult:", e);
+ post(new Runnable() {
+ @Override
+ public void run() {
+ onSimCheckResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1);
+ }
+ });
+ }
+ }
+ }
+
+ private Dialog getSimUnlockProgressDialog() {
+ if (mSimUnlockProgressDialog == null) {
+ mSimUnlockProgressDialog = new ProgressDialog(mContext);
+ mSimUnlockProgressDialog.setMessage(
+ mContext.getString(R.string.kg_sim_unlock_progress_dialog_message));
+ mSimUnlockProgressDialog.setIndeterminate(true);
+ mSimUnlockProgressDialog.setCancelable(false);
+ mSimUnlockProgressDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ }
+ return mSimUnlockProgressDialog;
+ }
+
+ private Dialog getSimRemainingAttemptsDialog(int remaining) {
+ String msg = getPinPasswordErrorMessage(remaining);
+ if (mRemainingAttemptsDialog == null) {
+ Builder builder = new AlertDialog.Builder(mContext);
+ builder.setMessage(msg);
+ builder.setCancelable(false);
+ builder.setNeutralButton(R.string.ok, null);
+ mRemainingAttemptsDialog = builder.create();
+ mRemainingAttemptsDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ } else {
+ mRemainingAttemptsDialog.setMessage(msg);
+ }
+ return mRemainingAttemptsDialog;
+ }
+
+ @Override
+ protected void verifyPasswordAndUnlock() {
+ String entry = mPasswordEntry.getText();
+
+ if (entry.length() < 4) {
+ // otherwise, display a message to the user, and don't submit.
+ mSecurityMessageDisplay.setMessage(R.string.kg_invalid_sim_pin_hint);
+ resetPasswordText(true /* animate */, true /* announce */);
+ mCallback.userActivity();
+ return;
+ }
+
+ getSimUnlockProgressDialog().show();
+
+ if (mCheckSimPinThread == null) {
+ mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) {
+ @Override
+ void onSimCheckResponse(final int result, final int attemptsRemaining) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.hide();
+ }
+ resetPasswordText(true /* animate */,
+ result != PhoneConstants.PIN_RESULT_SUCCESS /* announce */);
+ if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
+ KeyguardUpdateMonitor.getInstance(getContext())
+ .reportSimUnlocked(mSubId);
+ mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+ } else {
+ if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) {
+ if (attemptsRemaining <= 2) {
+ // this is getting critical - show dialog
+ getSimRemainingAttemptsDialog(attemptsRemaining).show();
+ } else {
+ // show message
+ mSecurityMessageDisplay.setMessage(
+ getPinPasswordErrorMessage(attemptsRemaining));
+ }
+ } else {
+ // "PIN operation failed!" - no idea what this was and no way to
+ // find out. :/
+ mSecurityMessageDisplay.setMessage(getContext().getString(
+ R.string.kg_password_pin_failed));
+ }
+ if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
+ + " CheckSimPin.onSimCheckResponse: " + result
+ + " attemptsRemaining=" + attemptsRemaining);
+ }
+ mCallback.userActivity();
+ mCheckSimPinThread = null;
+ }
+ });
+ }
+ };
+ mCheckSimPinThread.start();
+ }
+ }
+
+ @Override
+ public void startAppearAnimation() {
+ // noop.
+ }
+
+ @Override
+ public boolean startDisappearAnimation(Runnable finishRunnable) {
+ return false;
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
new file mode 100644
index 000000000000..3871448cb617
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.graphics.Color;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.IccCardConstants.State;
+
+
+/**
+ * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier.
+ */
+public class KeyguardSimPukView extends KeyguardPinBasedInputView {
+ private static final String LOG_TAG = "KeyguardSimPukView";
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+ public static final String TAG = "KeyguardSimPukView";
+
+ private ProgressDialog mSimUnlockProgressDialog = null;
+ private CheckSimPuk mCheckSimPukThread;
+ private String mPukText;
+ private String mPinText;
+ private StateMachine mStateMachine = new StateMachine();
+ private AlertDialog mRemainingAttemptsDialog;
+ private int mSubId;
+ private ImageView mSimImageView;
+
+ KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onSimStateChanged(int subId, int slotId, State simState) {
+ if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
+ resetState();
+ };
+ };
+
+ public KeyguardSimPukView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardSimPukView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ private class StateMachine {
+ final int ENTER_PUK = 0;
+ final int ENTER_PIN = 1;
+ final int CONFIRM_PIN = 2;
+ final int DONE = 3;
+ private int state = ENTER_PUK;
+
+ public void next() {
+ int msg = 0;
+ if (state == ENTER_PUK) {
+ if (checkPuk()) {
+ state = ENTER_PIN;
+ msg = R.string.kg_puk_enter_pin_hint;
+ } else {
+ msg = R.string.kg_invalid_sim_puk_hint;
+ }
+ } else if (state == ENTER_PIN) {
+ if (checkPin()) {
+ state = CONFIRM_PIN;
+ msg = R.string.kg_enter_confirm_pin_hint;
+ } else {
+ msg = R.string.kg_invalid_sim_pin_hint;
+ }
+ } else if (state == CONFIRM_PIN) {
+ if (confirmPin()) {
+ state = DONE;
+ msg = R.string.keyguard_sim_unlock_progress_dialog_message;
+ updateSim();
+ } else {
+ state = ENTER_PIN; // try again?
+ msg = R.string.kg_invalid_confirm_pin_hint;
+ }
+ }
+ resetPasswordText(true /* animate */, true /* announce */);
+ if (msg != 0) {
+ mSecurityMessageDisplay.setMessage(msg);
+ }
+ }
+
+ void reset() {
+ mPinText="";
+ mPukText="";
+ state = ENTER_PUK;
+ KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+ mSubId = monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED);
+ if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
+ int count = TelephonyManager.getDefault().getSimCount();
+ Resources rez = getResources();
+ final String msg;
+ int color = Color.WHITE;
+ if (count < 2) {
+ msg = rez.getString(R.string.kg_puk_enter_puk_hint);
+ } else {
+ SubscriptionInfo info = monitor.getSubscriptionInfoForSubId(mSubId);
+ CharSequence displayName = info != null ? info.getDisplayName() : "";
+ msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
+ if (info != null) {
+ color = info.getIconTint();
+ }
+ }
+ mSecurityMessageDisplay.setMessage(msg);
+ mSimImageView.setImageTintList(ColorStateList.valueOf(color));
+ }
+ mPasswordEntry.requestFocus();
+ }
+ }
+
+ @Override
+ protected int getPromtReasonStringRes(int reason) {
+ // No message on SIM Puk
+ return 0;
+ }
+
+ private String getPukPasswordErrorMessage(int attemptsRemaining) {
+ String displayMessage;
+
+ if (attemptsRemaining == 0) {
+ displayMessage = getContext().getString(R.string.kg_password_wrong_puk_code_dead);
+ } else if (attemptsRemaining > 0) {
+ displayMessage = getContext().getResources()
+ .getQuantityString(R.plurals.kg_password_wrong_puk_code, attemptsRemaining,
+ attemptsRemaining);
+ } else {
+ displayMessage = getContext().getString(R.string.kg_password_puk_failed);
+ }
+ if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:"
+ + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
+ return displayMessage;
+ }
+
+ @Override
+ public void resetState() {
+ super.resetState();
+ mStateMachine.reset();
+ }
+
+ @Override
+ protected boolean shouldLockout(long deadline) {
+ // SIM PUK doesn't have a timed lockout
+ return false;
+ }
+
+ @Override
+ protected int getPasswordTextViewId() {
+ return R.id.pukEntry;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ if (mEcaView instanceof EmergencyCarrierArea) {
+ ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true);
+ }
+ mSimImageView = (ImageView) findViewById(R.id.keyguard_sim);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
+ }
+
+ @Override
+ public void showUsabilityHint() {
+ }
+
+ @Override
+ public void onPause() {
+ // dismiss the dialog.
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.dismiss();
+ mSimUnlockProgressDialog = null;
+ }
+ }
+
+ /**
+ * Since the IPC can block, we want to run the request in a separate thread
+ * with a callback.
+ */
+ private abstract class CheckSimPuk extends Thread {
+
+ private final String mPin, mPuk;
+ private final int mSubId;
+
+ protected CheckSimPuk(String puk, String pin, int subId) {
+ mPuk = puk;
+ mPin = pin;
+ mSubId = subId;
+ }
+
+ abstract void onSimLockChangedResponse(final int result, final int attemptsRemaining);
+
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.v(TAG, "call supplyPukReportResult()");
+ final int[] result = ITelephony.Stub.asInterface(ServiceManager
+ .checkService("phone")).supplyPukReportResultForSubscriber(mSubId, mPuk, mPin);
+ if (DEBUG) {
+ Log.v(TAG, "supplyPukReportResult returned: " + result[0] + " " + result[1]);
+ }
+ post(new Runnable() {
+ @Override
+ public void run() {
+ onSimLockChangedResponse(result[0], result[1]);
+ }
+ });
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException for supplyPukReportResult:", e);
+ post(new Runnable() {
+ @Override
+ public void run() {
+ onSimLockChangedResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1);
+ }
+ });
+ }
+ }
+ }
+
+ private Dialog getSimUnlockProgressDialog() {
+ if (mSimUnlockProgressDialog == null) {
+ mSimUnlockProgressDialog = new ProgressDialog(mContext);
+ mSimUnlockProgressDialog.setMessage(
+ mContext.getString(R.string.kg_sim_unlock_progress_dialog_message));
+ mSimUnlockProgressDialog.setIndeterminate(true);
+ mSimUnlockProgressDialog.setCancelable(false);
+ if (!(mContext instanceof Activity)) {
+ mSimUnlockProgressDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ }
+ }
+ return mSimUnlockProgressDialog;
+ }
+
+ private Dialog getPukRemainingAttemptsDialog(int remaining) {
+ String msg = getPukPasswordErrorMessage(remaining);
+ if (mRemainingAttemptsDialog == null) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ builder.setMessage(msg);
+ builder.setCancelable(false);
+ builder.setNeutralButton(R.string.ok, null);
+ mRemainingAttemptsDialog = builder.create();
+ mRemainingAttemptsDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ } else {
+ mRemainingAttemptsDialog.setMessage(msg);
+ }
+ return mRemainingAttemptsDialog;
+ }
+
+ private boolean checkPuk() {
+ // make sure the puk is at least 8 digits long.
+ if (mPasswordEntry.getText().length() == 8) {
+ mPukText = mPasswordEntry.getText();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean checkPin() {
+ // make sure the PIN is between 4 and 8 digits
+ int length = mPasswordEntry.getText().length();
+ if (length >= 4 && length <= 8) {
+ mPinText = mPasswordEntry.getText();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean confirmPin() {
+ return mPinText.equals(mPasswordEntry.getText());
+ }
+
+ private void updateSim() {
+ getSimUnlockProgressDialog().show();
+
+ if (mCheckSimPukThread == null) {
+ mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) {
+ @Override
+ void onSimLockChangedResponse(final int result, final int attemptsRemaining) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.hide();
+ }
+ resetPasswordText(true /* animate */,
+ result != PhoneConstants.PIN_RESULT_SUCCESS /* announce */);
+ if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
+ KeyguardUpdateMonitor.getInstance(getContext())
+ .reportSimUnlocked(mSubId);
+ mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+ } else {
+ if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) {
+ if (attemptsRemaining <= 2) {
+ // this is getting critical - show dialog
+ getPukRemainingAttemptsDialog(attemptsRemaining).show();
+ } else {
+ // show message
+ mSecurityMessageDisplay.setMessage(
+ getPukPasswordErrorMessage(attemptsRemaining));
+ }
+ } else {
+ mSecurityMessageDisplay.setMessage(getContext().getString(
+ R.string.kg_password_puk_failed));
+ }
+ if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock "
+ + " UpdateSim.onSimCheckResponse: "
+ + " attemptsRemaining=" + attemptsRemaining);
+ mStateMachine.reset();
+ }
+ mCheckSimPukThread = null;
+ }
+ });
+ }
+ };
+ mCheckSimPukThread.start();
+ }
+ }
+
+ @Override
+ protected void verifyPasswordAndUnlock() {
+ mStateMachine.next();
+ }
+
+ @Override
+ public void startAppearAnimation() {
+ // noop.
+ }
+
+ @Override
+ public boolean startDisappearAnimation(Runnable finishRunnable) {
+ return false;
+ }
+}
+
+
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
new file mode 100644
index 000000000000..f8f4f2a8b8fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.GridLayout;
+import android.widget.TextClock;
+import android.widget.TextView;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import java.util.Locale;
+
+public class KeyguardStatusView extends GridLayout {
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+ private static final String TAG = "KeyguardStatusView";
+
+ private final LockPatternUtils mLockPatternUtils;
+ private final AlarmManager mAlarmManager;
+
+ private TextView mAlarmStatusView;
+ private TextClock mDateView;
+ private TextClock mClockView;
+ private TextView mOwnerInfo;
+ private ViewGroup mClockContainer;
+
+ private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
+
+ @Override
+ public void onTimeChanged() {
+ refresh();
+ }
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ if (showing) {
+ if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
+ refresh();
+ updateOwnerInfo();
+ }
+ }
+
+ @Override
+ public void onStartedWakingUp() {
+ setEnableMarquee(true);
+ }
+
+ @Override
+ public void onFinishedGoingToSleep(int why) {
+ setEnableMarquee(false);
+ }
+
+ @Override
+ public void onUserSwitchComplete(int userId) {
+ refresh();
+ updateOwnerInfo();
+ }
+ };
+
+ public KeyguardStatusView(Context context) {
+ this(context, null, 0);
+ }
+
+ public KeyguardStatusView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeyguardStatusView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ mLockPatternUtils = new LockPatternUtils(getContext());
+ }
+
+ private void setEnableMarquee(boolean enabled) {
+ if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee");
+ if (mAlarmStatusView != null) mAlarmStatusView.setSelected(enabled);
+ if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mClockContainer = (ViewGroup) findViewById(R.id.keyguard_clock_container);
+ mAlarmStatusView = (TextView) findViewById(R.id.alarm_status);
+ mDateView = (TextClock) findViewById(R.id.date_view);
+ mClockView = (TextClock) findViewById(R.id.clock_view);
+ mDateView.setShowCurrentUserTime(true);
+ mClockView.setShowCurrentUserTime(true);
+ mOwnerInfo = (TextView) findViewById(R.id.owner_info);
+
+ boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
+ setEnableMarquee(shouldMarquee);
+ refresh();
+ updateOwnerInfo();
+
+ // Disable elegant text height because our fancy colon makes the ymin value huge for no
+ // reason.
+ mClockView.setElegantTextHeight(false);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ getResources().getDimensionPixelSize(R.dimen.widget_big_font_size));
+ // Some layouts like burmese have a different margin for the clock
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mClockView.getLayoutParams();
+ layoutParams.bottomMargin = getResources().getDimensionPixelSize(
+ R.dimen.bottom_text_spacing_digital);
+ mClockView.setLayoutParams(layoutParams);
+ mDateView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
+ mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
+ }
+
+ public void refreshTime() {
+ mDateView.setFormat24Hour(Patterns.dateView);
+ mDateView.setFormat12Hour(Patterns.dateView);
+
+ mClockView.setFormat12Hour(Patterns.clockView12);
+ mClockView.setFormat24Hour(Patterns.clockView24);
+ }
+
+ private void refresh() {
+ AlarmManager.AlarmClockInfo nextAlarm =
+ mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
+ Patterns.update(mContext, nextAlarm != null);
+
+ refreshTime();
+ refreshAlarmStatus(nextAlarm);
+ }
+
+ void refreshAlarmStatus(AlarmManager.AlarmClockInfo nextAlarm) {
+ if (nextAlarm != null) {
+ String alarm = formatNextAlarm(mContext, nextAlarm);
+ mAlarmStatusView.setText(alarm);
+ mAlarmStatusView.setContentDescription(
+ getResources().getString(R.string.keyguard_accessibility_next_alarm, alarm));
+ mAlarmStatusView.setVisibility(View.VISIBLE);
+ } else {
+ mAlarmStatusView.setVisibility(View.GONE);
+ }
+ }
+
+ public int getClockBottom() {
+ return mClockView.getBottom() +
+ ((MarginLayoutParams) mClockView.getLayoutParams()).bottomMargin;
+ }
+
+ public static String formatNextAlarm(Context context, AlarmManager.AlarmClockInfo info) {
+ if (info == null) {
+ return "";
+ }
+ String skeleton = DateFormat.is24HourFormat(context, ActivityManager.getCurrentUser())
+ ? "EHm"
+ : "Ehma";
+ String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
+ return DateFormat.format(pattern, info.getTriggerTime()).toString();
+ }
+
+ private void updateOwnerInfo() {
+ if (mOwnerInfo == null) return;
+ String ownerInfo = getOwnerInfo();
+ if (!TextUtils.isEmpty(ownerInfo)) {
+ mOwnerInfo.setVisibility(View.VISIBLE);
+ mOwnerInfo.setText(ownerInfo);
+ } else {
+ mOwnerInfo.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mInfoCallback);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mInfoCallback);
+ }
+
+ private String getOwnerInfo() {
+ String info = null;
+ if (mLockPatternUtils.isDeviceOwnerInfoEnabled()) {
+ // Use the device owner information set by device policy client via
+ // device policy manager.
+ info = mLockPatternUtils.getDeviceOwnerInfo();
+ } else {
+ // Use the current user owner information if enabled.
+ final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (ownerInfoEnabled) {
+ info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
+ }
+ }
+ return info;
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
+ // This is an optimization to ensure we only recompute the patterns when the inputs change.
+ private static final class Patterns {
+ static String dateView;
+ static String clockView12;
+ static String clockView24;
+ static String cacheKey;
+
+ static void update(Context context, boolean hasAlarm) {
+ final Locale locale = Locale.getDefault();
+ final Resources res = context.getResources();
+ final String dateViewSkel = res.getString(hasAlarm
+ ? R.string.abbrev_wday_month_day_no_year_alarm
+ : R.string.abbrev_wday_month_day_no_year);
+ final String clockView12Skel = res.getString(R.string.clock_12hr_format);
+ final String clockView24Skel = res.getString(R.string.clock_24hr_format);
+ final String key = locale.toString() + dateViewSkel + clockView12Skel + clockView24Skel;
+ if (key.equals(cacheKey)) return;
+
+ dateView = DateFormat.getBestDateTimePattern(locale, dateViewSkel);
+
+ clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel);
+ // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
+ // format. The following code removes the AM/PM indicator if we didn't want it.
+ if (!clockView12Skel.contains("a")) {
+ clockView12 = clockView12.replaceAll("a", "").trim();
+ }
+
+ clockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel);
+
+ // Use fancy colon.
+ clockView24 = clockView24.replace(':', '\uee01');
+ clockView12 = clockView12.replace(':', '\uee01');
+
+ cacheKey = key;
+ }
+ }
+
+ public void setDark(boolean dark) {
+ final int N = mClockContainer.getChildCount();
+ for (int i = 0; i < N; i++) {
+ View child = mClockContainer.getChildAt(i);
+ if (child == mClockView) {
+ continue;
+ }
+ child.setAlpha(dark ? 0 : 1);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
new file mode 100644
index 000000000000..80d4a264cac8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -0,0 +1,1798 @@
+/*
+ * Copyright (C) 2008 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.keyguard;
+
+import static android.content.Intent.ACTION_USER_UNLOCKED;
+import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
+import static android.os.BatteryManager.BATTERY_STATUS_FULL;
+import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
+import static android.os.BatteryManager.EXTRA_HEALTH;
+import static android.os.BatteryManager.EXTRA_LEVEL;
+import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT;
+import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
+import static android.os.BatteryManager.EXTRA_PLUGGED;
+import static android.os.BatteryManager.EXTRA_STATUS;
+
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.UserSwitchObserver;
+import android.app.admin.DevicePolicyManager;
+import android.app.trust.TrustManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
+import android.media.AudioManager;
+import android.os.BatteryManager;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import com.google.android.collect.Lists;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.IccCardConstants.State;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.widget.LockPatternUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+
+/**
+ * Watches for updates that may be interesting to the keyguard, and provides
+ * the up to date information as well as a registration for callbacks that care
+ * to be updated.
+ *
+ * Note: under time crunch, this has been extended to include some stuff that
+ * doesn't really belong here. see {@link #handleBatteryUpdate} where it shutdowns
+ * the device, and {@link #getFailedUnlockAttempts()}, {@link #reportFailedAttempt()}
+ * and {@link #clearFailedUnlockAttempts()}. Maybe we should rename this 'KeyguardContext'...
+ */
+public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
+
+ private static final String TAG = "KeyguardUpdateMonitor";
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+ private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES;
+ private static final int LOW_BATTERY_THRESHOLD = 20;
+
+ private static final String ACTION_FACE_UNLOCK_STARTED
+ = "com.android.facelock.FACE_UNLOCK_STARTED";
+ private static final String ACTION_FACE_UNLOCK_STOPPED
+ = "com.android.facelock.FACE_UNLOCK_STOPPED";
+
+ // Callback messages
+ private static final int MSG_TIME_UPDATE = 301;
+ private static final int MSG_BATTERY_UPDATE = 302;
+ private static final int MSG_SIM_STATE_CHANGE = 304;
+ private static final int MSG_RINGER_MODE_CHANGED = 305;
+ private static final int MSG_PHONE_STATE_CHANGED = 306;
+ private static final int MSG_DEVICE_PROVISIONED = 308;
+ private static final int MSG_DPM_STATE_CHANGED = 309;
+ private static final int MSG_USER_SWITCHING = 310;
+ private static final int MSG_KEYGUARD_RESET = 312;
+ private static final int MSG_BOOT_COMPLETED = 313;
+ private static final int MSG_USER_SWITCH_COMPLETE = 314;
+ private static final int MSG_USER_INFO_CHANGED = 317;
+ private static final int MSG_REPORT_EMERGENCY_CALL_ACTION = 318;
+ private static final int MSG_STARTED_WAKING_UP = 319;
+ private static final int MSG_FINISHED_GOING_TO_SLEEP = 320;
+ private static final int MSG_STARTED_GOING_TO_SLEEP = 321;
+ private static final int MSG_KEYGUARD_BOUNCER_CHANGED = 322;
+ private static final int MSG_FACE_UNLOCK_STATE_CHANGED = 327;
+ private static final int MSG_SIM_SUBSCRIPTION_INFO_CHANGED = 328;
+ private static final int MSG_AIRPLANE_MODE_CHANGED = 329;
+ private static final int MSG_SERVICE_STATE_CHANGE = 330;
+ private static final int MSG_SCREEN_TURNED_ON = 331;
+ private static final int MSG_SCREEN_TURNED_OFF = 332;
+ private static final int MSG_DREAMING_STATE_CHANGED = 333;
+ private static final int MSG_USER_UNLOCKED = 334;
+
+ /** Fingerprint state: Not listening to fingerprint. */
+ private static final int FINGERPRINT_STATE_STOPPED = 0;
+
+ /** Fingerprint state: Listening. */
+ private static final int FINGERPRINT_STATE_RUNNING = 1;
+
+ /**
+ * Fingerprint state: Cancelling and waiting for the confirmation from FingerprintService to
+ * send us the confirmation that cancellation has happened.
+ */
+ private static final int FINGERPRINT_STATE_CANCELLING = 2;
+
+ /**
+ * Fingerprint state: During cancelling we got another request to start listening, so when we
+ * receive the cancellation done signal, we should start listening again.
+ */
+ private static final int FINGERPRINT_STATE_CANCELLING_RESTARTING = 3;
+
+ private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
+
+ private static final ComponentName FALLBACK_HOME_COMPONENT = new ComponentName(
+ "com.android.settings", "com.android.settings.FallbackHome");
+
+ private static KeyguardUpdateMonitor sInstance;
+
+ private final Context mContext;
+ HashMap<Integer, SimData> mSimDatas = new HashMap<Integer, SimData>();
+ HashMap<Integer, ServiceState> mServiceStates = new HashMap<Integer, ServiceState>();
+
+ private int mRingMode;
+ private int mPhoneState;
+ private boolean mKeyguardIsVisible;
+
+ /**
+ * If true, fingerprint was already authenticated and we don't need to start listening again
+ * until the Keyguard has been dismissed.
+ */
+ private boolean mFingerprintAlreadyAuthenticated;
+ private boolean mGoingToSleep;
+ private boolean mBouncer;
+ private boolean mBootCompleted;
+ private boolean mNeedsSlowUnlockTransition;
+ private boolean mHasLockscreenWallpaper;
+
+ // Device provisioning state
+ private boolean mDeviceProvisioned;
+
+ // Battery status
+ private BatteryStatus mBatteryStatus;
+
+ // Password attempts
+ private SparseIntArray mFailedAttempts = new SparseIntArray();
+
+ private final StrongAuthTracker mStrongAuthTracker;
+
+ private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
+ mCallbacks = Lists.newArrayList();
+ private ContentObserver mDeviceProvisionedObserver;
+
+ private boolean mSwitchingUser;
+
+ private boolean mDeviceInteractive;
+ private boolean mScreenOn;
+ private SubscriptionManager mSubscriptionManager;
+ private List<SubscriptionInfo> mSubscriptionInfo;
+ private TrustManager mTrustManager;
+ private UserManager mUserManager;
+ private int mFingerprintRunningState = FINGERPRINT_STATE_STOPPED;
+ private LockPatternUtils mLockPatternUtils;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_TIME_UPDATE:
+ handleTimeUpdate();
+ break;
+ case MSG_BATTERY_UPDATE:
+ handleBatteryUpdate((BatteryStatus) msg.obj);
+ break;
+ case MSG_SIM_STATE_CHANGE:
+ handleSimStateChange(msg.arg1, msg.arg2, (State) msg.obj);
+ break;
+ case MSG_RINGER_MODE_CHANGED:
+ handleRingerModeChange(msg.arg1);
+ break;
+ case MSG_PHONE_STATE_CHANGED:
+ handlePhoneStateChanged((String) msg.obj);
+ break;
+ case MSG_DEVICE_PROVISIONED:
+ handleDeviceProvisioned();
+ break;
+ case MSG_DPM_STATE_CHANGED:
+ handleDevicePolicyManagerStateChanged();
+ break;
+ case MSG_USER_SWITCHING:
+ handleUserSwitching(msg.arg1, (IRemoteCallback) msg.obj);
+ break;
+ case MSG_USER_SWITCH_COMPLETE:
+ handleUserSwitchComplete(msg.arg1);
+ break;
+ case MSG_KEYGUARD_RESET:
+ handleKeyguardReset();
+ break;
+ case MSG_KEYGUARD_BOUNCER_CHANGED:
+ handleKeyguardBouncerChanged(msg.arg1);
+ break;
+ case MSG_BOOT_COMPLETED:
+ handleBootCompleted();
+ break;
+ case MSG_USER_INFO_CHANGED:
+ handleUserInfoChanged(msg.arg1);
+ break;
+ case MSG_REPORT_EMERGENCY_CALL_ACTION:
+ handleReportEmergencyCallAction();
+ break;
+ case MSG_STARTED_GOING_TO_SLEEP:
+ handleStartedGoingToSleep(msg.arg1);
+ break;
+ case MSG_FINISHED_GOING_TO_SLEEP:
+ handleFinishedGoingToSleep(msg.arg1);
+ break;
+ case MSG_STARTED_WAKING_UP:
+ Trace.beginSection("KeyguardUpdateMonitor#handler MSG_STARTED_WAKING_UP");
+ handleStartedWakingUp();
+ Trace.endSection();
+ break;
+ case MSG_FACE_UNLOCK_STATE_CHANGED:
+ Trace.beginSection("KeyguardUpdateMonitor#handler MSG_FACE_UNLOCK_STATE_CHANGED");
+ handleFaceUnlockStateChanged(msg.arg1 != 0, msg.arg2);
+ Trace.endSection();
+ break;
+ case MSG_SIM_SUBSCRIPTION_INFO_CHANGED:
+ handleSimSubscriptionInfoChanged();
+ break;
+ case MSG_AIRPLANE_MODE_CHANGED:
+ handleAirplaneModeChanged();
+ break;
+ case MSG_SERVICE_STATE_CHANGE:
+ handleServiceStateChange(msg.arg1, (ServiceState) msg.obj);
+ break;
+ case MSG_SCREEN_TURNED_ON:
+ handleScreenTurnedOn();
+ break;
+ case MSG_SCREEN_TURNED_OFF:
+ Trace.beginSection("KeyguardUpdateMonitor#handler MSG_SCREEN_TURNED_ON");
+ handleScreenTurnedOff();
+ Trace.endSection();
+ break;
+ case MSG_DREAMING_STATE_CHANGED:
+ handleDreamingStateChanged(msg.arg1);
+ break;
+ case MSG_USER_UNLOCKED:
+ handleUserUnlocked();
+ break;
+ }
+ }
+ };
+
+ private OnSubscriptionsChangedListener mSubscriptionListener =
+ new OnSubscriptionsChangedListener() {
+ @Override
+ public void onSubscriptionsChanged() {
+ mHandler.sendEmptyMessage(MSG_SIM_SUBSCRIPTION_INFO_CHANGED);
+ }
+ };
+
+ private SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
+ private SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
+ private SparseBooleanArray mUserFingerprintAuthenticated = new SparseBooleanArray();
+ private SparseBooleanArray mUserFaceUnlockRunning = new SparseBooleanArray();
+
+ private static int sCurrentUser;
+
+ public synchronized static void setCurrentUser(int currentUser) {
+ sCurrentUser = currentUser;
+ }
+
+ public synchronized static int getCurrentUser() {
+ return sCurrentUser;
+ }
+
+ @Override
+ public void onTrustChanged(boolean enabled, int userId, int flags) {
+ mUserHasTrust.put(userId, enabled);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onTrustChanged(userId);
+ if (enabled && flags != 0) {
+ cb.onTrustGrantedWithFlags(flags, userId);
+ }
+ }
+ }
+ }
+
+ protected void handleSimSubscriptionInfoChanged() {
+ if (DEBUG_SIM_STATES) {
+ Log.v(TAG, "onSubscriptionInfoChanged()");
+ List<SubscriptionInfo> sil = mSubscriptionManager.getActiveSubscriptionInfoList();
+ if (sil != null) {
+ for (SubscriptionInfo subInfo : sil) {
+ Log.v(TAG, "SubInfo:" + subInfo);
+ }
+ } else {
+ Log.v(TAG, "onSubscriptionInfoChanged: list is null");
+ }
+ }
+ List<SubscriptionInfo> subscriptionInfos = getSubscriptionInfo(true /* forceReload */);
+
+ // Hack level over 9000: Because the subscription id is not yet valid when we see the
+ // first update in handleSimStateChange, we need to force refresh all all SIM states
+ // so the subscription id for them is consistent.
+ ArrayList<SubscriptionInfo> changedSubscriptions = new ArrayList<>();
+ for (int i = 0; i < subscriptionInfos.size(); i++) {
+ SubscriptionInfo info = subscriptionInfos.get(i);
+ boolean changed = refreshSimState(info.getSubscriptionId(), info.getSimSlotIndex());
+ if (changed) {
+ changedSubscriptions.add(info);
+ }
+ }
+ for (int i = 0; i < changedSubscriptions.size(); i++) {
+ SimData data = mSimDatas.get(changedSubscriptions.get(i).getSubscriptionId());
+ for (int j = 0; j < mCallbacks.size(); j++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(j).get();
+ if (cb != null) {
+ cb.onSimStateChanged(data.subId, data.slotId, data.simState);
+ }
+ }
+ }
+ for (int j = 0; j < mCallbacks.size(); j++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(j).get();
+ if (cb != null) {
+ cb.onRefreshCarrierInfo();
+ }
+ }
+ }
+
+ private void handleAirplaneModeChanged() {
+ for (int j = 0; j < mCallbacks.size(); j++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(j).get();
+ if (cb != null) {
+ cb.onRefreshCarrierInfo();
+ }
+ }
+ }
+
+ /** @return List of SubscriptionInfo records, maybe empty but never null */
+ public List<SubscriptionInfo> getSubscriptionInfo(boolean forceReload) {
+ List<SubscriptionInfo> sil = mSubscriptionInfo;
+ if (sil == null || forceReload) {
+ sil = mSubscriptionManager.getActiveSubscriptionInfoList();
+ }
+ if (sil == null) {
+ // getActiveSubscriptionInfoList was null callers expect an empty list.
+ mSubscriptionInfo = new ArrayList<SubscriptionInfo>();
+ } else {
+ mSubscriptionInfo = sil;
+ }
+ return mSubscriptionInfo;
+ }
+
+ @Override
+ public void onTrustManagedChanged(boolean managed, int userId) {
+ mUserTrustIsManaged.put(userId, managed);
+
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onTrustManagedChanged(userId);
+ }
+ }
+ }
+
+ private void onFingerprintAuthenticated(int userId) {
+ Trace.beginSection("KeyGuardUpdateMonitor#onFingerPrintAuthenticated");
+ mUserFingerprintAuthenticated.put(userId, true);
+
+ // If fingerprint unlocking is allowed, this event will lead to a Keyguard dismiss or to a
+ // wake-up (if Keyguard is not showing), so we don't need to listen until Keyguard is
+ // fully gone.
+ mFingerprintAlreadyAuthenticated = isUnlockingWithFingerprintAllowed();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onFingerprintAuthenticated(userId);
+ }
+ }
+ Trace.endSection();
+ }
+
+ private void handleFingerprintAuthFailed() {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onFingerprintAuthFailed();
+ }
+ }
+ handleFingerprintHelp(-1, mContext.getString(R.string.fingerprint_not_recognized));
+ }
+
+ private void handleFingerprintAcquired(int acquireInfo) {
+ if (acquireInfo != FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
+ return;
+ }
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onFingerprintAcquired();
+ }
+ }
+ }
+
+ private void handleFingerprintAuthenticated(int authUserId) {
+ Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");
+ try {
+ final int userId;
+ try {
+ userId = ActivityManager.getService().getCurrentUser().id;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get current user id: ", e);
+ return;
+ }
+ if (userId != authUserId) {
+ Log.d(TAG, "Fingerprint authenticated for wrong user: " + authUserId);
+ return;
+ }
+ if (isFingerprintDisabled(userId)) {
+ Log.d(TAG, "Fingerprint disabled by DPM for userId: " + userId);
+ return;
+ }
+ onFingerprintAuthenticated(userId);
+ } finally {
+ setFingerprintRunningState(FINGERPRINT_STATE_STOPPED);
+ }
+ Trace.endSection();
+ }
+
+ private void handleFingerprintHelp(int msgId, String helpString) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onFingerprintHelp(msgId, helpString);
+ }
+ }
+ }
+
+ private void handleFingerprintError(int msgId, String errString) {
+ if (msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
+ && mFingerprintRunningState == FINGERPRINT_STATE_CANCELLING_RESTARTING) {
+ setFingerprintRunningState(FINGERPRINT_STATE_STOPPED);
+ startListeningForFingerprint();
+ } else {
+ setFingerprintRunningState(FINGERPRINT_STATE_STOPPED);
+ }
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onFingerprintError(msgId, errString);
+ }
+ }
+ }
+
+ private void handleFingerprintLockoutReset() {
+ updateFingerprintListeningState();
+ }
+
+ private void setFingerprintRunningState(int fingerprintRunningState) {
+ boolean wasRunning = mFingerprintRunningState == FINGERPRINT_STATE_RUNNING;
+ boolean isRunning = fingerprintRunningState == FINGERPRINT_STATE_RUNNING;
+ mFingerprintRunningState = fingerprintRunningState;
+
+ // Clients of KeyguardUpdateMonitor don't care about the internal state about the
+ // asynchronousness of the cancel cycle. So only notify them if the actualy running state
+ // has changed.
+ if (wasRunning != isRunning) {
+ notifyFingerprintRunningStateChanged();
+ }
+ }
+
+ private void notifyFingerprintRunningStateChanged() {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onFingerprintRunningStateChanged(isFingerprintDetectionRunning());
+ }
+ }
+ }
+ private void handleFaceUnlockStateChanged(boolean running, int userId) {
+ mUserFaceUnlockRunning.put(userId, running);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onFaceUnlockStateChanged(running, userId);
+ }
+ }
+ }
+
+ public boolean isFaceUnlockRunning(int userId) {
+ return mUserFaceUnlockRunning.get(userId);
+ }
+
+ public boolean isFingerprintDetectionRunning() {
+ return mFingerprintRunningState == FINGERPRINT_STATE_RUNNING;
+ }
+
+ private boolean isTrustDisabled(int userId) {
+ // Don't allow trust agent if device is secured with a SIM PIN. This is here
+ // mainly because there's no other way to prompt the user to enter their SIM PIN
+ // once they get past the keyguard screen.
+ final boolean disabledBySimPin = isSimPinSecure();
+ return disabledBySimPin;
+ }
+
+ private boolean isFingerprintDisabled(int userId) {
+ final DevicePolicyManager dpm =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ return dpm != null && (dpm.getKeyguardDisabledFeatures(null, userId)
+ & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) != 0
+ || isSimPinSecure();
+ }
+
+ public boolean getUserCanSkipBouncer(int userId) {
+ return getUserHasTrust(userId) || (mUserFingerprintAuthenticated.get(userId)
+ && isUnlockingWithFingerprintAllowed());
+ }
+
+ public boolean getUserHasTrust(int userId) {
+ return !isTrustDisabled(userId) && mUserHasTrust.get(userId);
+ }
+
+ public boolean getUserTrustIsManaged(int userId) {
+ return mUserTrustIsManaged.get(userId) && !isTrustDisabled(userId);
+ }
+
+ public boolean isUnlockingWithFingerprintAllowed() {
+ return mStrongAuthTracker.isUnlockingWithFingerprintAllowed();
+ }
+
+ public boolean needsSlowUnlockTransition() {
+ return mNeedsSlowUnlockTransition;
+ }
+
+ public StrongAuthTracker getStrongAuthTracker() {
+ return mStrongAuthTracker;
+ }
+
+ public void reportSuccessfulStrongAuthUnlockAttempt() {
+ if (mFpm != null) {
+ byte[] token = null; /* TODO: pass real auth token once fp HAL supports it */
+ mFpm.resetTimeout(token);
+ }
+ }
+
+ private void notifyStrongAuthStateChanged(int userId) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onStrongAuthStateChanged(userId);
+ }
+ }
+ }
+
+ static class DisplayClientState {
+ public int clientGeneration;
+ public boolean clearing;
+ public PendingIntent intent;
+ public int playbackState;
+ public long playbackEventTime;
+ }
+
+ private DisplayClientState mDisplayClientState = new DisplayClientState();
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (DEBUG) Log.d(TAG, "received broadcast " + action);
+
+ if (Intent.ACTION_TIME_TICK.equals(action)
+ || Intent.ACTION_TIME_CHANGED.equals(action)
+ || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
+ mHandler.sendEmptyMessage(MSG_TIME_UPDATE);
+ } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+ final int status = intent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
+ final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0);
+ final int level = intent.getIntExtra(EXTRA_LEVEL, 0);
+ final int health = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
+
+ final int maxChargingMicroAmp = intent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
+ int maxChargingMicroVolt = intent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
+ final int maxChargingMicroWatt;
+
+ if (maxChargingMicroVolt <= 0) {
+ maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
+ }
+ if (maxChargingMicroAmp > 0) {
+ // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor
+ // to maintain precision equally on both factors.
+ maxChargingMicroWatt = (maxChargingMicroAmp / 1000)
+ * (maxChargingMicroVolt / 1000);
+ } else {
+ maxChargingMicroWatt = -1;
+ }
+ final Message msg = mHandler.obtainMessage(
+ MSG_BATTERY_UPDATE, new BatteryStatus(status, level, plugged, health,
+ maxChargingMicroWatt));
+ mHandler.sendMessage(msg);
+ } else if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
+ SimData args = SimData.fromIntent(intent);
+ if (DEBUG_SIM_STATES) {
+ Log.v(TAG, "action " + action
+ + " state: " + intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE)
+ + " slotId: " + args.slotId + " subid: " + args.subId);
+ }
+ mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, args.subId, args.slotId, args.simState)
+ .sendToTarget();
+ } else if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED,
+ intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1), 0));
+ } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
+ String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_PHONE_STATE_CHANGED, state));
+ } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
+ mHandler.sendEmptyMessage(MSG_AIRPLANE_MODE_CHANGED);
+ } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ dispatchBootCompleted();
+ } else if (TelephonyIntents.ACTION_SERVICE_STATE_CHANGED.equals(action)) {
+ ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras());
+ int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ if (DEBUG) {
+ Log.v(TAG, "action " + action + " serviceState=" + serviceState + " subId="
+ + subId);
+ }
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
+ }
+ }
+ };
+
+ private final BroadcastReceiver mBroadcastAllReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED.equals(action)) {
+ mHandler.sendEmptyMessage(MSG_TIME_UPDATE);
+ } else if (Intent.ACTION_USER_INFO_CHANGED.equals(action)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_INFO_CHANGED,
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId()), 0));
+ } else if (ACTION_FACE_UNLOCK_STARTED.equals(action)) {
+ Trace.beginSection("KeyguardUpdateMonitor.mBroadcastAllReceiver#onReceive ACTION_FACE_UNLOCK_STARTED");
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_FACE_UNLOCK_STATE_CHANGED, 1,
+ getSendingUserId()));
+ Trace.endSection();
+ } else if (ACTION_FACE_UNLOCK_STOPPED.equals(action)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_FACE_UNLOCK_STATE_CHANGED, 0,
+ getSendingUserId()));
+ } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
+ .equals(action)) {
+ mHandler.sendEmptyMessage(MSG_DPM_STATE_CHANGED);
+ } else if (ACTION_USER_UNLOCKED.equals(action)) {
+ mHandler.sendEmptyMessage(MSG_USER_UNLOCKED);
+ }
+ }
+ };
+
+ private final FingerprintManager.LockoutResetCallback mLockoutResetCallback
+ = new FingerprintManager.LockoutResetCallback() {
+ @Override
+ public void onLockoutReset() {
+ handleFingerprintLockoutReset();
+ }
+ };
+
+ private FingerprintManager.AuthenticationCallback mAuthenticationCallback
+ = new AuthenticationCallback() {
+
+ @Override
+ public void onAuthenticationFailed() {
+ handleFingerprintAuthFailed();
+ };
+
+ @Override
+ public void onAuthenticationSucceeded(AuthenticationResult result) {
+ Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
+ handleFingerprintAuthenticated(result.getUserId());
+ Trace.endSection();
+ }
+
+ @Override
+ public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+ handleFingerprintHelp(helpMsgId, helpString.toString());
+ }
+
+ @Override
+ public void onAuthenticationError(int errMsgId, CharSequence errString) {
+ handleFingerprintError(errMsgId, errString.toString());
+ }
+
+ @Override
+ public void onAuthenticationAcquired(int acquireInfo) {
+ handleFingerprintAcquired(acquireInfo);
+ }
+ };
+ private CancellationSignal mFingerprintCancelSignal;
+ private FingerprintManager mFpm;
+
+ /**
+ * When we receive a
+ * {@link com.android.internal.telephony.TelephonyIntents#ACTION_SIM_STATE_CHANGED} broadcast,
+ * and then pass a result via our handler to {@link KeyguardUpdateMonitor#handleSimStateChange},
+ * we need a single object to pass to the handler. This class helps decode
+ * the intent and provide a {@link SimCard.State} result.
+ */
+ private static class SimData {
+ public State simState;
+ public int slotId;
+ public int subId;
+
+ SimData(State state, int slot, int id) {
+ simState = state;
+ slotId = slot;
+ subId = id;
+ }
+
+ static SimData fromIntent(Intent intent) {
+ State state;
+ if (!TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
+ throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED");
+ }
+ String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
+ int slotId = intent.getIntExtra(PhoneConstants.SLOT_KEY, 0);
+ int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
+ final String absentReason = intent
+ .getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
+
+ if (IccCardConstants.INTENT_VALUE_ABSENT_ON_PERM_DISABLED.equals(
+ absentReason)) {
+ state = IccCardConstants.State.PERM_DISABLED;
+ } else {
+ state = IccCardConstants.State.ABSENT;
+ }
+ } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
+ state = IccCardConstants.State.READY;
+ } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
+ final String lockedReason = intent
+ .getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
+ if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
+ state = IccCardConstants.State.PIN_REQUIRED;
+ } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
+ state = IccCardConstants.State.PUK_REQUIRED;
+ } else {
+ state = IccCardConstants.State.UNKNOWN;
+ }
+ } else if (IccCardConstants.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) {
+ state = IccCardConstants.State.NETWORK_LOCKED;
+ } else if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(stateExtra)
+ || IccCardConstants.INTENT_VALUE_ICC_IMSI.equals(stateExtra)) {
+ // This is required because telephony doesn't return to "READY" after
+ // these state transitions. See bug 7197471.
+ state = IccCardConstants.State.READY;
+ } else {
+ state = IccCardConstants.State.UNKNOWN;
+ }
+ return new SimData(state, slotId, subId);
+ }
+
+ @Override
+ public String toString() {
+ return "SimData{state=" + simState + ",slotId=" + slotId + ",subId=" + subId + "}";
+ }
+ }
+
+ public static class BatteryStatus {
+ public static final int CHARGING_UNKNOWN = -1;
+ public static final int CHARGING_SLOWLY = 0;
+ public static final int CHARGING_REGULAR = 1;
+ public static final int CHARGING_FAST = 2;
+
+ public final int status;
+ public final int level;
+ public final int plugged;
+ public final int health;
+ public final int maxChargingWattage;
+ public BatteryStatus(int status, int level, int plugged, int health,
+ int maxChargingWattage) {
+ this.status = status;
+ this.level = level;
+ this.plugged = plugged;
+ this.health = health;
+ this.maxChargingWattage = maxChargingWattage;
+ }
+
+ /**
+ * Determine whether the device is plugged in (USB, power, or wireless).
+ * @return true if the device is plugged in.
+ */
+ public boolean isPluggedIn() {
+ return plugged == BatteryManager.BATTERY_PLUGGED_AC
+ || plugged == BatteryManager.BATTERY_PLUGGED_USB
+ || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
+ }
+
+ /**
+ * Whether or not the device is charged. Note that some devices never return 100% for
+ * battery level, so this allows either battery level or status to determine if the
+ * battery is charged.
+ * @return true if the device is charged
+ */
+ public boolean isCharged() {
+ return status == BATTERY_STATUS_FULL || level >= 100;
+ }
+
+ /**
+ * Whether battery is low and needs to be charged.
+ * @return true if battery is low
+ */
+ public boolean isBatteryLow() {
+ return level < LOW_BATTERY_THRESHOLD;
+ }
+
+ public final int getChargingSpeed(int slowThreshold, int fastThreshold) {
+ return maxChargingWattage <= 0 ? CHARGING_UNKNOWN :
+ maxChargingWattage < slowThreshold ? CHARGING_SLOWLY :
+ maxChargingWattage > fastThreshold ? CHARGING_FAST :
+ CHARGING_REGULAR;
+ }
+ }
+
+ public class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+ public StrongAuthTracker(Context context) {
+ super(context);
+ }
+
+ public boolean isUnlockingWithFingerprintAllowed() {
+ int userId = getCurrentUser();
+ return isFingerprintAllowedForUser(userId);
+ }
+
+ public boolean hasUserAuthenticatedSinceBoot() {
+ int userId = getCurrentUser();
+ return (getStrongAuthForUser(userId)
+ & STRONG_AUTH_REQUIRED_AFTER_BOOT) == 0;
+ }
+
+ @Override
+ public void onStrongAuthRequiredChanged(int userId) {
+ notifyStrongAuthStateChanged(userId);
+ }
+ }
+
+ public static KeyguardUpdateMonitor getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new KeyguardUpdateMonitor(context);
+ }
+ return sInstance;
+ }
+
+ protected void handleStartedWakingUp() {
+ Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
+ updateFingerprintListeningState();
+ final int count = mCallbacks.size();
+ for (int i = 0; i < count; i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onStartedWakingUp();
+ }
+ }
+ Trace.endSection();
+ }
+
+ protected void handleStartedGoingToSleep(int arg1) {
+ clearFingerprintRecognized();
+ final int count = mCallbacks.size();
+ for (int i = 0; i < count; i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onStartedGoingToSleep(arg1);
+ }
+ }
+ mGoingToSleep = true;
+ mFingerprintAlreadyAuthenticated = false;
+ updateFingerprintListeningState();
+ }
+
+ protected void handleFinishedGoingToSleep(int arg1) {
+ mGoingToSleep = false;
+ final int count = mCallbacks.size();
+ for (int i = 0; i < count; i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onFinishedGoingToSleep(arg1);
+ }
+ }
+ updateFingerprintListeningState();
+ }
+
+ private void handleScreenTurnedOn() {
+ final int count = mCallbacks.size();
+ for (int i = 0; i < count; i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onScreenTurnedOn();
+ }
+ }
+ }
+
+ private void handleScreenTurnedOff() {
+ final int count = mCallbacks.size();
+ for (int i = 0; i < count; i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onScreenTurnedOff();
+ }
+ }
+ }
+
+ private void handleDreamingStateChanged(int dreamStart) {
+ final int count = mCallbacks.size();
+ boolean showingDream = dreamStart == 1;
+ for (int i = 0; i < count; i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onDreamingStateChanged(showingDream);
+ }
+ }
+ }
+
+ /**
+ * IMPORTANT: Must be called from UI thread.
+ */
+ public void dispatchSetBackground(Bitmap bmp) {
+ if (DEBUG) Log.d(TAG, "dispatchSetBackground");
+ final int count = mCallbacks.size();
+ for (int i = 0; i < count; i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onSetBackground(bmp);
+ }
+ }
+ }
+
+ private void handleUserInfoChanged(int userId) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onUserInfoChanged(userId);
+ }
+ }
+ }
+
+ private void handleUserUnlocked() {
+ mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onUserUnlocked();
+ }
+ }
+ }
+
+ private KeyguardUpdateMonitor(Context context) {
+ mContext = context;
+ mSubscriptionManager = SubscriptionManager.from(context);
+ mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
+ mStrongAuthTracker = new StrongAuthTracker(context);
+
+ // Since device can't be un-provisioned, we only need to register a content observer
+ // to update mDeviceProvisioned when we are...
+ if (!mDeviceProvisioned) {
+ watchForDeviceProvisioning();
+ }
+
+ // Take a guess at initial SIM state, battery status and PLMN until we get an update
+ mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0, 0);
+
+ // Watch for interesting updates
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_TIME_TICK);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+ filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+ filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
+ filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ context.registerReceiver(mBroadcastReceiver, filter);
+
+ final IntentFilter bootCompleteFilter = new IntentFilter();
+ bootCompleteFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ bootCompleteFilter.addAction(Intent.ACTION_BOOT_COMPLETED);
+ context.registerReceiver(mBroadcastReceiver, bootCompleteFilter);
+
+ final IntentFilter allUserFilter = new IntentFilter();
+ allUserFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
+ allUserFilter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+ allUserFilter.addAction(ACTION_FACE_UNLOCK_STARTED);
+ allUserFilter.addAction(ACTION_FACE_UNLOCK_STOPPED);
+ allUserFilter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+ allUserFilter.addAction(ACTION_USER_UNLOCKED);
+ context.registerReceiverAsUser(mBroadcastAllReceiver, UserHandle.ALL, allUserFilter,
+ null, null);
+
+ mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
+ try {
+ ActivityManager.getService().registerUserSwitchObserver(
+ new UserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId, IRemoteCallback reply) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHING,
+ newUserId, 0, reply));
+ }
+ @Override
+ public void onUserSwitchComplete(int newUserId) throws RemoteException {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCH_COMPLETE,
+ newUserId, 0));
+ }
+ }, TAG);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+
+ mTrustManager = (TrustManager) context.getSystemService(Context.TRUST_SERVICE);
+ mTrustManager.registerTrustListener(this);
+ mLockPatternUtils = new LockPatternUtils(context);
+ mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
+
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
+ }
+ updateFingerprintListeningState();
+ if (mFpm != null) {
+ mFpm.addLockoutResetCallback(mLockoutResetCallback);
+ }
+
+ mUserManager = context.getSystemService(UserManager.class);
+ }
+
+ private void updateFingerprintListeningState() {
+ boolean shouldListenForFingerprint = shouldListenForFingerprint();
+ if (mFingerprintRunningState == FINGERPRINT_STATE_RUNNING && !shouldListenForFingerprint) {
+ stopListeningForFingerprint();
+ } else if (mFingerprintRunningState != FINGERPRINT_STATE_RUNNING
+ && shouldListenForFingerprint) {
+ startListeningForFingerprint();
+ }
+ }
+
+ private boolean shouldListenForFingerprint() {
+ return (mKeyguardIsVisible || !mDeviceInteractive || mBouncer || mGoingToSleep)
+ && !mSwitchingUser && !mFingerprintAlreadyAuthenticated
+ && !isFingerprintDisabled(getCurrentUser());
+ }
+
+ private void startListeningForFingerprint() {
+ if (mFingerprintRunningState == FINGERPRINT_STATE_CANCELLING) {
+ setFingerprintRunningState(FINGERPRINT_STATE_CANCELLING_RESTARTING);
+ return;
+ }
+ if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");
+ int userId = ActivityManager.getCurrentUser();
+ if (isUnlockWithFingerprintPossible(userId)) {
+ if (mFingerprintCancelSignal != null) {
+ mFingerprintCancelSignal.cancel();
+ }
+ mFingerprintCancelSignal = new CancellationSignal();
+ mFpm.authenticate(null, mFingerprintCancelSignal, 0, mAuthenticationCallback, null, userId);
+ setFingerprintRunningState(FINGERPRINT_STATE_RUNNING);
+ }
+ }
+
+ public boolean isUnlockWithFingerprintPossible(int userId) {
+ return mFpm != null && mFpm.isHardwareDetected() && !isFingerprintDisabled(userId)
+ && mFpm.getEnrolledFingerprints(userId).size() > 0;
+ }
+
+ private void stopListeningForFingerprint() {
+ if (DEBUG) Log.v(TAG, "stopListeningForFingerprint()");
+ if (mFingerprintRunningState == FINGERPRINT_STATE_RUNNING) {
+ mFingerprintCancelSignal.cancel();
+ mFingerprintCancelSignal = null;
+ setFingerprintRunningState(FINGERPRINT_STATE_CANCELLING);
+ }
+ if (mFingerprintRunningState == FINGERPRINT_STATE_CANCELLING_RESTARTING) {
+ setFingerprintRunningState(FINGERPRINT_STATE_CANCELLING);
+ }
+ }
+
+ private boolean isDeviceProvisionedInSettingsDb() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+
+ private void watchForDeviceProvisioning() {
+ mDeviceProvisionedObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
+ if (mDeviceProvisioned) {
+ mHandler.sendEmptyMessage(MSG_DEVICE_PROVISIONED);
+ }
+ if (DEBUG) Log.d(TAG, "DEVICE_PROVISIONED state = " + mDeviceProvisioned);
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+ false, mDeviceProvisionedObserver);
+
+ // prevent a race condition between where we check the flag and where we register the
+ // observer by grabbing the value once again...
+ boolean provisioned = isDeviceProvisionedInSettingsDb();
+ if (provisioned != mDeviceProvisioned) {
+ mDeviceProvisioned = provisioned;
+ if (mDeviceProvisioned) {
+ mHandler.sendEmptyMessage(MSG_DEVICE_PROVISIONED);
+ }
+ }
+ }
+
+ /**
+ * Update the state whether Keyguard currently has a lockscreen wallpaper.
+ *
+ * @param hasLockscreenWallpaper Whether Keyguard has a lockscreen wallpaper.
+ */
+ public void setHasLockscreenWallpaper(boolean hasLockscreenWallpaper) {
+ if (hasLockscreenWallpaper != mHasLockscreenWallpaper) {
+ mHasLockscreenWallpaper = hasLockscreenWallpaper;
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onHasLockscreenWallpaperChanged(hasLockscreenWallpaper);
+ }
+ }
+ }
+ }
+
+ /**
+ * @return Whether Keyguard has a lockscreen wallpaper.
+ */
+ public boolean hasLockscreenWallpaper() {
+ return mHasLockscreenWallpaper;
+ }
+
+ /**
+ * Handle {@link #MSG_DPM_STATE_CHANGED}
+ */
+ protected void handleDevicePolicyManagerStateChanged() {
+ updateFingerprintListeningState();
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onDevicePolicyManagerStateChanged();
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_USER_SWITCHING}
+ */
+ protected void handleUserSwitching(int userId, IRemoteCallback reply) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onUserSwitching(userId);
+ }
+ }
+ try {
+ reply.sendResult(null);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_USER_SWITCH_COMPLETE}
+ */
+ protected void handleUserSwitchComplete(int userId) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onUserSwitchComplete(userId);
+ }
+ }
+ }
+
+ /**
+ * This is exposed since {@link Intent#ACTION_BOOT_COMPLETED} is not sticky. If
+ * keyguard crashes sometime after boot, then it will never receive this
+ * broadcast and hence not handle the event. This method is ultimately called by
+ * PhoneWindowManager in this case.
+ */
+ public void dispatchBootCompleted() {
+ mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED);
+ }
+
+ /**
+ * Handle {@link #MSG_BOOT_COMPLETED}
+ */
+ protected void handleBootCompleted() {
+ if (mBootCompleted) return;
+ mBootCompleted = true;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onBootCompleted();
+ }
+ }
+ }
+
+ /**
+ * We need to store this state in the KeyguardUpdateMonitor since this class will not be
+ * destroyed.
+ */
+ public boolean hasBootCompleted() {
+ return mBootCompleted;
+ }
+
+ /**
+ * Handle {@link #MSG_DEVICE_PROVISIONED}
+ */
+ protected void handleDeviceProvisioned() {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onDeviceProvisioned();
+ }
+ }
+ if (mDeviceProvisionedObserver != null) {
+ // We don't need the observer anymore...
+ mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);
+ mDeviceProvisionedObserver = null;
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_PHONE_STATE_CHANGED}
+ */
+ protected void handlePhoneStateChanged(String newState) {
+ if (DEBUG) Log.d(TAG, "handlePhoneStateChanged(" + newState + ")");
+ if (TelephonyManager.EXTRA_STATE_IDLE.equals(newState)) {
+ mPhoneState = TelephonyManager.CALL_STATE_IDLE;
+ } else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(newState)) {
+ mPhoneState = TelephonyManager.CALL_STATE_OFFHOOK;
+ } else if (TelephonyManager.EXTRA_STATE_RINGING.equals(newState)) {
+ mPhoneState = TelephonyManager.CALL_STATE_RINGING;
+ }
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onPhoneStateChanged(mPhoneState);
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_RINGER_MODE_CHANGED}
+ */
+ protected void handleRingerModeChange(int mode) {
+ if (DEBUG) Log.d(TAG, "handleRingerModeChange(" + mode + ")");
+ mRingMode = mode;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onRingerModeChanged(mode);
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_TIME_UPDATE}
+ */
+ private void handleTimeUpdate() {
+ if (DEBUG) Log.d(TAG, "handleTimeUpdate");
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onTimeChanged();
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_BATTERY_UPDATE}
+ */
+ private void handleBatteryUpdate(BatteryStatus status) {
+ if (DEBUG) Log.d(TAG, "handleBatteryUpdate");
+ final boolean batteryUpdateInteresting = isBatteryUpdateInteresting(mBatteryStatus, status);
+ mBatteryStatus = status;
+ if (batteryUpdateInteresting) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onRefreshBatteryInfo(status);
+ }
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_SIM_STATE_CHANGE}
+ */
+ private void handleSimStateChange(int subId, int slotId, State state) {
+
+ if (DEBUG_SIM_STATES) {
+ Log.d(TAG, "handleSimStateChange(subId=" + subId + ", slotId="
+ + slotId + ", state=" + state +")");
+ }
+
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ Log.w(TAG, "invalid subId in handleSimStateChange()");
+ return;
+ }
+
+ SimData data = mSimDatas.get(subId);
+ final boolean changed;
+ if (data == null) {
+ data = new SimData(state, slotId, subId);
+ mSimDatas.put(subId, data);
+ changed = true; // no data yet; force update
+ } else {
+ changed = (data.simState != state || data.subId != subId || data.slotId != slotId);
+ data.simState = state;
+ data.subId = subId;
+ data.slotId = slotId;
+ }
+ if (changed && state != State.UNKNOWN) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onSimStateChanged(subId, slotId, state);
+ }
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_SERVICE_STATE_CHANGE}
+ */
+ private void handleServiceStateChange(int subId, ServiceState serviceState) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "handleServiceStateChange(subId=" + subId + ", serviceState=" + serviceState);
+ }
+
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ Log.w(TAG, "invalid subId in handleServiceStateChange()");
+ return;
+ }
+
+ mServiceStates.put(subId, serviceState);
+
+ for (int j = 0; j < mCallbacks.size(); j++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(j).get();
+ if (cb != null) {
+ cb.onRefreshCarrierInfo();
+ }
+ }
+ }
+
+ /**
+ * Notifies that the visibility state of Keyguard has changed.
+ *
+ * <p>Needs to be called from the main thread.
+ */
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ if (DEBUG) Log.d(TAG, "onKeyguardVisibilityChanged(" + showing + ")");
+ mKeyguardIsVisible = showing;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onKeyguardVisibilityChangedRaw(showing);
+ }
+ }
+ if (!showing) {
+ mFingerprintAlreadyAuthenticated = false;
+ }
+ updateFingerprintListeningState();
+ }
+
+ /**
+ * Handle {@link #MSG_KEYGUARD_RESET}
+ */
+ private void handleKeyguardReset() {
+ if (DEBUG) Log.d(TAG, "handleKeyguardReset");
+ updateFingerprintListeningState();
+ mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
+ }
+
+ private boolean resolveNeedsSlowUnlockTransition() {
+ if (mUserManager.isUserUnlocked(getCurrentUser())) {
+ return false;
+ }
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME);
+ ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity(homeIntent,
+ 0 /* flags */);
+ return FALLBACK_HOME_COMPONENT.equals(resolveInfo.getComponentInfo().getComponentName());
+ }
+
+ /**
+ * Handle {@link #MSG_KEYGUARD_BOUNCER_CHANGED}
+ * @see #sendKeyguardBouncerChanged(boolean)
+ */
+ private void handleKeyguardBouncerChanged(int bouncer) {
+ if (DEBUG) Log.d(TAG, "handleKeyguardBouncerChanged(" + bouncer + ")");
+ boolean isBouncer = (bouncer == 1);
+ mBouncer = isBouncer;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onKeyguardBouncerChanged(isBouncer);
+ }
+ }
+ updateFingerprintListeningState();
+ }
+
+ /**
+ * Handle {@link #MSG_REPORT_EMERGENCY_CALL_ACTION}
+ */
+ private void handleReportEmergencyCallAction() {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onEmergencyCallAction();
+ }
+ }
+ }
+
+ private static boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) {
+ final boolean nowPluggedIn = current.isPluggedIn();
+ final boolean wasPluggedIn = old.isPluggedIn();
+ final boolean stateChangedWhilePluggedIn =
+ wasPluggedIn == true && nowPluggedIn == true
+ && (old.status != current.status);
+
+ // change in plug state is always interesting
+ if (wasPluggedIn != nowPluggedIn || stateChangedWhilePluggedIn) {
+ return true;
+ }
+
+ // change in battery level while plugged in
+ if (nowPluggedIn && old.level != current.level) {
+ return true;
+ }
+
+ // change where battery needs charging
+ if (!nowPluggedIn && current.isBatteryLow() && current.level != old.level) {
+ return true;
+ }
+
+ // change in charging current while plugged in
+ if (nowPluggedIn && current.maxChargingWattage != old.maxChargingWattage) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Remove the given observer's callback.
+ *
+ * @param callback The callback to remove
+ */
+ public void removeCallback(KeyguardUpdateMonitorCallback callback) {
+ if (DEBUG) Log.v(TAG, "*** unregister callback for " + callback);
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ if (mCallbacks.get(i).get() == callback) {
+ mCallbacks.remove(i);
+ }
+ }
+ }
+
+ /**
+ * Register to receive notifications about general keyguard information
+ * (see {@link InfoCallback}.
+ * @param callback The callback to register
+ */
+ public void registerCallback(KeyguardUpdateMonitorCallback callback) {
+ if (DEBUG) Log.v(TAG, "*** register callback for " + callback);
+ // Prevent adding duplicate callbacks
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ if (mCallbacks.get(i).get() == callback) {
+ if (DEBUG) Log.e(TAG, "Object tried to add another callback",
+ new Exception("Called by"));
+ return;
+ }
+ }
+ mCallbacks.add(new WeakReference<KeyguardUpdateMonitorCallback>(callback));
+ removeCallback(null); // remove unused references
+ sendUpdates(callback);
+ }
+
+ public boolean isSwitchingUser() {
+ return mSwitchingUser;
+ }
+
+ public void setSwitchingUser(boolean switching) {
+ mSwitchingUser = switching;
+ updateFingerprintListeningState();
+ }
+
+ private void sendUpdates(KeyguardUpdateMonitorCallback callback) {
+ // Notify listener of the current state
+ callback.onRefreshBatteryInfo(mBatteryStatus);
+ callback.onTimeChanged();
+ callback.onRingerModeChanged(mRingMode);
+ callback.onPhoneStateChanged(mPhoneState);
+ callback.onRefreshCarrierInfo();
+ callback.onClockVisibilityChanged();
+ for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
+ final SimData state = data.getValue();
+ callback.onSimStateChanged(state.subId, state.slotId, state.simState);
+ }
+ }
+
+ public void sendKeyguardReset() {
+ mHandler.obtainMessage(MSG_KEYGUARD_RESET).sendToTarget();
+ }
+
+ /**
+ * @see #handleKeyguardBouncerChanged(int)
+ */
+ public void sendKeyguardBouncerChanged(boolean showingBouncer) {
+ if (DEBUG) Log.d(TAG, "sendKeyguardBouncerChanged(" + showingBouncer + ")");
+ Message message = mHandler.obtainMessage(MSG_KEYGUARD_BOUNCER_CHANGED);
+ message.arg1 = showingBouncer ? 1 : 0;
+ message.sendToTarget();
+ }
+
+ /**
+ * Report that the user successfully entered the SIM PIN or PUK/SIM PIN so we
+ * have the information earlier than waiting for the intent
+ * broadcast from the telephony code.
+ *
+ * NOTE: Because handleSimStateChange() invokes callbacks immediately without going
+ * through mHandler, this *must* be called from the UI thread.
+ */
+ public void reportSimUnlocked(int subId) {
+ if (DEBUG_SIM_STATES) Log.v(TAG, "reportSimUnlocked(subId=" + subId + ")");
+ int slotId = SubscriptionManager.getSlotId(subId);
+ handleSimStateChange(subId, slotId, State.READY);
+ }
+
+ /**
+ * Report that the emergency call button has been pressed and the emergency dialer is
+ * about to be displayed.
+ *
+ * @param bypassHandler runs immediately.
+ *
+ * NOTE: Must be called from UI thread if bypassHandler == true.
+ */
+ public void reportEmergencyCallAction(boolean bypassHandler) {
+ if (!bypassHandler) {
+ mHandler.obtainMessage(MSG_REPORT_EMERGENCY_CALL_ACTION).sendToTarget();
+ } else {
+ handleReportEmergencyCallAction();
+ }
+ }
+
+ /**
+ * @return Whether the device is provisioned (whether they have gone through
+ * the setup wizard)
+ */
+ public boolean isDeviceProvisioned() {
+ return mDeviceProvisioned;
+ }
+
+ public void clearFailedUnlockAttempts() {
+ mFailedAttempts.delete(sCurrentUser);
+ }
+
+ public int getFailedUnlockAttempts(int userId) {
+ return mFailedAttempts.get(userId, 0);
+ }
+
+ public void reportFailedStrongAuthUnlockAttempt(int userId) {
+ mFailedAttempts.put(userId, getFailedUnlockAttempts(userId) + 1);
+ }
+
+ public void clearFingerprintRecognized() {
+ mUserFingerprintAuthenticated.clear();
+ }
+
+ public boolean isSimPinVoiceSecure() {
+ // TODO: only count SIMs that handle voice
+ return isSimPinSecure();
+ }
+
+ public boolean isSimPinSecure() {
+ // True if any SIM is pin secure
+ for (SubscriptionInfo info : getSubscriptionInfo(false /* forceReload */)) {
+ if (isSimPinSecure(getSimState(info.getSubscriptionId()))) return true;
+ }
+ return false;
+ }
+
+ public State getSimState(int subId) {
+ if (mSimDatas.containsKey(subId)) {
+ return mSimDatas.get(subId).simState;
+ } else {
+ return State.UNKNOWN;
+ }
+ }
+
+ /**
+ * @return true if and only if the state has changed for the specified {@code slotId}
+ */
+ private boolean refreshSimState(int subId, int slotId) {
+
+ // This is awful. It exists because there are two APIs for getting the SIM status
+ // that don't return the complete set of values and have different types. In Keyguard we
+ // need IccCardConstants, but TelephonyManager would only give us
+ // TelephonyManager.SIM_STATE*, so we retrieve it manually.
+ final TelephonyManager tele = TelephonyManager.from(mContext);
+ int simState = tele.getSimState(slotId);
+ State state;
+ try {
+ state = State.intToState(simState);
+ } catch(IllegalArgumentException ex) {
+ Log.w(TAG, "Unknown sim state: " + simState);
+ state = State.UNKNOWN;
+ }
+ SimData data = mSimDatas.get(subId);
+ final boolean changed;
+ if (data == null) {
+ data = new SimData(state, slotId, subId);
+ mSimDatas.put(subId, data);
+ changed = true; // no data yet; force update
+ } else {
+ changed = data.simState != state;
+ data.simState = state;
+ }
+ return changed;
+ }
+
+ public static boolean isSimPinSecure(IccCardConstants.State state) {
+ final IccCardConstants.State simState = state;
+ return (simState == IccCardConstants.State.PIN_REQUIRED
+ || simState == IccCardConstants.State.PUK_REQUIRED
+ || simState == IccCardConstants.State.PERM_DISABLED);
+ }
+
+ public DisplayClientState getCachedDisplayClientState() {
+ return mDisplayClientState;
+ }
+
+ // TODO: use these callbacks elsewhere in place of the existing notifyScreen*()
+ // (KeyguardViewMediator, KeyguardHostView)
+ public void dispatchStartedWakingUp() {
+ synchronized (this) {
+ mDeviceInteractive = true;
+ }
+ mHandler.sendEmptyMessage(MSG_STARTED_WAKING_UP);
+ }
+
+ public void dispatchStartedGoingToSleep(int why) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_STARTED_GOING_TO_SLEEP, why, 0));
+ }
+
+ public void dispatchFinishedGoingToSleep(int why) {
+ synchronized(this) {
+ mDeviceInteractive = false;
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_FINISHED_GOING_TO_SLEEP, why, 0));
+ }
+
+ public void dispatchScreenTurnedOn() {
+ synchronized (this) {
+ mScreenOn = true;
+ }
+ mHandler.sendEmptyMessage(MSG_SCREEN_TURNED_ON);
+ }
+
+ public void dispatchScreenTurnedOff() {
+ synchronized(this) {
+ mScreenOn = false;
+ }
+ mHandler.sendEmptyMessage(MSG_SCREEN_TURNED_OFF);
+ }
+
+ public void dispatchDreamingStarted() {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DREAMING_STATE_CHANGED, 1, 0));
+ }
+
+ public void dispatchDreamingStopped() {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DREAMING_STATE_CHANGED, 0, 0));
+ }
+
+ public boolean isDeviceInteractive() {
+ return mDeviceInteractive;
+ }
+
+ public boolean isGoingToSleep() {
+ return mGoingToSleep;
+ }
+
+ /**
+ * Find the next SubscriptionId for a SIM in the given state, favoring lower slot numbers first.
+ * @param state
+ * @return subid or {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if none found
+ */
+ public int getNextSubIdForState(State state) {
+ List<SubscriptionInfo> list = getSubscriptionInfo(false /* forceReload */);
+ int resultId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ int bestSlotId = Integer.MAX_VALUE; // Favor lowest slot first
+ for (int i = 0; i < list.size(); i++) {
+ final SubscriptionInfo info = list.get(i);
+ final int id = info.getSubscriptionId();
+ int slotId = SubscriptionManager.getSlotId(id);
+ if (state == getSimState(id) && bestSlotId > slotId ) {
+ resultId = id;
+ bestSlotId = slotId;
+ }
+ }
+ return resultId;
+ }
+
+ public SubscriptionInfo getSubscriptionInfoForSubId(int subId) {
+ List<SubscriptionInfo> list = getSubscriptionInfo(false /* forceReload */);
+ for (int i = 0; i < list.size(); i++) {
+ SubscriptionInfo info = list.get(i);
+ if (subId == info.getSubscriptionId()) return info;
+ }
+ return null; // not found
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("KeyguardUpdateMonitor state:");
+ pw.println(" SIM States:");
+ for (SimData data : mSimDatas.values()) {
+ pw.println(" " + data.toString());
+ }
+ pw.println(" Subs:");
+ if (mSubscriptionInfo != null) {
+ for (int i = 0; i < mSubscriptionInfo.size(); i++) {
+ pw.println(" " + mSubscriptionInfo.get(i));
+ }
+ }
+ pw.println(" Service states:");
+ for (int subId : mServiceStates.keySet()) {
+ pw.println(" " + subId + "=" + mServiceStates.get(subId));
+ }
+ if (mFpm != null && mFpm.isHardwareDetected()) {
+ final int userId = ActivityManager.getCurrentUser();
+ final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
+ pw.println(" Fingerprint state (user=" + userId + ")");
+ pw.println(" allowed=" + isUnlockingWithFingerprintAllowed());
+ pw.println(" auth'd=" + mUserFingerprintAuthenticated.get(userId));
+ pw.println(" authSinceBoot="
+ + getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
+ pw.println(" disabled(DPM)=" + isFingerprintDisabled(userId));
+ pw.println(" possible=" + isUnlockWithFingerprintPossible(userId));
+ pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
+ pw.println(" trustManaged=" + getUserTrustIsManaged(userId));
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
new file mode 100644
index 000000000000..14d6b599a9b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+import android.app.admin.DevicePolicyManager;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.os.SystemClock;
+import android.hardware.fingerprint.FingerprintManager;
+import android.telephony.TelephonyManager;
+import android.view.WindowManagerPolicy;
+
+import com.android.internal.telephony.IccCardConstants;
+
+/**
+ * Callback for general information relevant to lock screen.
+ */
+public class KeyguardUpdateMonitorCallback {
+
+ private static final long VISIBILITY_CHANGED_COLLAPSE_MS = 1000;
+ private long mVisibilityChangedCalled;
+ private boolean mShowing;
+
+ /**
+ * Called when the battery status changes, e.g. when plugged in or unplugged, charge
+ * level, etc. changes.
+ *
+ * @param status current battery status
+ */
+ public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { }
+
+ /**
+ * Called once per minute or when the time changes.
+ */
+ public void onTimeChanged() { }
+
+ /**
+ * Called when the carrier PLMN or SPN changes.
+ */
+ public void onRefreshCarrierInfo() { }
+
+ /**
+ * Called when the ringer mode changes.
+ * @param state the current ringer state, as defined in
+ * {@link AudioManager#RINGER_MODE_CHANGED_ACTION}
+ */
+ public void onRingerModeChanged(int state) { }
+
+ /**
+ * Called when the phone state changes. String will be one of:
+ * {@link TelephonyManager#EXTRA_STATE_IDLE}
+ * {@link TelephonyManager@EXTRA_STATE_RINGING}
+ * {@link TelephonyManager#EXTRA_STATE_OFFHOOK
+ */
+ public void onPhoneStateChanged(int phoneState) { }
+
+ /**
+ * Called when the visibility of the keyguard changes.
+ * @param showing Indicates if the keyguard is now visible.
+ */
+ public void onKeyguardVisibilityChanged(boolean showing) { }
+
+ public void onKeyguardVisibilityChangedRaw(boolean showing) {
+ final long now = SystemClock.elapsedRealtime();
+ if (showing == mShowing
+ && (now - mVisibilityChangedCalled) < VISIBILITY_CHANGED_COLLAPSE_MS) return;
+ onKeyguardVisibilityChanged(showing);
+ mVisibilityChangedCalled = now;
+ mShowing = showing;
+ }
+
+ /**
+ * Called when the keyguard enters or leaves bouncer mode.
+ * @param bouncer if true, keyguard is now in bouncer mode.
+ */
+ public void onKeyguardBouncerChanged(boolean bouncer) { }
+
+ /**
+ * Called when visibility of lockscreen clock changes, such as when
+ * obscured by a widget.
+ */
+ public void onClockVisibilityChanged() { }
+
+ /**
+ * Called when the device becomes provisioned
+ */
+ public void onDeviceProvisioned() { }
+
+ /**
+ * Called when the device policy changes.
+ * See {@link DevicePolicyManager#ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED}
+ */
+ public void onDevicePolicyManagerStateChanged() { }
+
+ /**
+ * Called when the user change begins.
+ */
+ public void onUserSwitching(int userId) { }
+
+ /**
+ * Called when the user change is complete.
+ */
+ public void onUserSwitchComplete(int userId) { }
+
+ /**
+ * Called when the SIM state changes.
+ * @param slotId
+ * @param simState
+ */
+ public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) { }
+
+ /**
+ * Called when the user's info changed.
+ */
+ public void onUserInfoChanged(int userId) { }
+
+ /**
+ * Called when a user got unlocked.
+ */
+ public void onUserUnlocked() { }
+
+ /**
+ * Called when boot completed.
+ *
+ * Note, this callback will only be received if boot complete occurs after registering with
+ * KeyguardUpdateMonitor.
+ */
+ public void onBootCompleted() { }
+
+ /**
+ * Called when the emergency call button is pressed.
+ */
+ public void onEmergencyCallAction() { }
+
+ /**
+ * Called when the transport background changes.
+ * @param bitmap
+ */
+ public void onSetBackground(Bitmap bitmap) {
+ }
+
+ /**
+ * Called when the device has started waking up.
+ */
+ public void onStartedWakingUp() { }
+
+ /**
+ * Called when the device has started going to sleep.
+ * @param why see {@link #onFinishedGoingToSleep(int)}
+ */
+ public void onStartedGoingToSleep(int why) { }
+
+ /**
+ * Called when the device has finished going to sleep.
+ * @param why either {@link WindowManagerPolicy#OFF_BECAUSE_OF_ADMIN},
+ * {@link WindowManagerPolicy#OFF_BECAUSE_OF_USER}, or
+ * {@link WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}.
+ */
+ public void onFinishedGoingToSleep(int why) { }
+
+ /**
+ * Called when the screen has been turned on.
+ */
+ public void onScreenTurnedOn() { }
+
+ /**
+ * Called when the screen has been turned off.
+ */
+ public void onScreenTurnedOff() { }
+
+ /**
+ * Called when trust changes for a user.
+ */
+ public void onTrustChanged(int userId) { }
+
+ /**
+ * Called when trust being managed changes for a user.
+ */
+ public void onTrustManagedChanged(int userId) { }
+
+ /**
+ * Called after trust was granted with non-zero flags.
+ */
+ public void onTrustGrantedWithFlags(int flags, int userId) { }
+
+ /**
+ * Called when a finger has been acquired.
+ * <p>
+ * It is guaranteed that either {@link #onFingerprintAuthenticated} or
+ * {@link #onFingerprintAuthFailed()} is called after this method eventually.
+ */
+ public void onFingerprintAcquired() { }
+
+ /**
+ * Called when a fingerprint couldn't be authenticated.
+ */
+ public void onFingerprintAuthFailed() { }
+
+ /**
+ * Called when a fingerprint is recognized.
+ * @param userId the user id for which the fingerprint was authenticated
+ */
+ public void onFingerprintAuthenticated(int userId) { }
+
+ /**
+ * Called when fingerprint provides help string (e.g. "Try again")
+ * @param msgId
+ * @param helpString
+ */
+ public void onFingerprintHelp(int msgId, String helpString) { }
+
+ /**
+ * Called when fingerprint provides an semi-permanent error message
+ * (e.g. "Hardware not available").
+ * @param msgId one of the error messages listed in {@link FingerprintManager}
+ * @param errString
+ */
+ public void onFingerprintError(int msgId, String errString) { }
+
+ /**
+ * Called when the state of face unlock changed.
+ */
+ public void onFaceUnlockStateChanged(boolean running, int userId) { }
+
+ /**
+ * Called when the fingerprint running state changed.
+ */
+ public void onFingerprintRunningStateChanged(boolean running) { }
+
+ /**
+ * Called when the state that the user hasn't used strong authentication since quite some time
+ * has changed.
+ */
+ public void onStrongAuthStateChanged(int userId) { }
+
+ /**
+ * Called when the state whether we have a lockscreen wallpaper has changed.
+ */
+ public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { }
+
+ /**
+ * Called when the dream's window state is changed.
+ * @param dreaming true if the dream's window has been created and is visible
+ */
+ public void onDreamingStateChanged(boolean dreaming) { }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/LatencyTracker.java b/packages/SystemUI/src/com/android/keyguard/LatencyTracker.java
new file mode 100644
index 000000000000..cee0afcd37d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/LatencyTracker.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2016 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.keyguard;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.SparseLongArray;
+
+import com.android.systemui.EventLogTags;
+
+/**
+ * Class to track various latencies in SystemUI. It then outputs the latency to logcat so these
+ * latencies can be captured by tests and then used for dashboards.
+ * <p>
+ * This is currently only in Keyguard so it can be shared between SystemUI and Keyguard, but
+ * eventually we'd want to merge these two packages together so Keyguard can use common classes
+ * that are shared with SystemUI.
+ */
+public class LatencyTracker {
+
+ private static final String ACTION_RELOAD_PROPERTY =
+ "com.android.systemui.RELOAD_LATENCY_TRACKER_PROPERTY";
+
+ private static final String TAG = "LatencyTracker";
+
+ /**
+ * Time it takes until the first frame of the notification panel to be displayed while expanding
+ */
+ public static final int ACTION_EXPAND_PANEL = 0;
+
+ /**
+ * Time it takes until the first frame of recents is drawn after invoking it with the button.
+ */
+ public static final int ACTION_TOGGLE_RECENTS = 1;
+
+ /**
+ * Time between we get a fingerprint acquired signal until we start with the unlock animation
+ */
+ public static final int ACTION_FINGERPRINT_WAKE_AND_UNLOCK = 2;
+
+ /**
+ * Time it takes to check PIN/Pattern/Password.
+ */
+ public static final int ACTION_CHECK_CREDENTIAL = 3;
+
+ /**
+ * Time it takes to check fully PIN/Pattern/Password, i.e. that's the time spent including the
+ * actions to unlock a user.
+ */
+ public static final int ACTION_CHECK_CREDENTIAL_UNLOCKED = 4;
+
+ /**
+ * Time it takes to turn on the screen.
+ */
+ public static final int ACTION_TURN_ON_SCREEN = 5;
+
+ private static final String[] NAMES = new String[] {
+ "expand panel",
+ "toggle recents",
+ "fingerprint wake-and-unlock",
+ "check credential",
+ "check credential unlocked",
+ "turn on screen" };
+
+ private static LatencyTracker sLatencyTracker;
+
+ private final SparseLongArray mStartRtc = new SparseLongArray();
+ private boolean mEnabled;
+
+ public static LatencyTracker getInstance(Context context) {
+ if (sLatencyTracker == null) {
+ sLatencyTracker = new LatencyTracker(context);
+ }
+ return sLatencyTracker;
+ }
+
+ private LatencyTracker(Context context) {
+ context.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ reloadProperty();
+ }
+ }, new IntentFilter(ACTION_RELOAD_PROPERTY));
+ reloadProperty();
+ }
+
+ private void reloadProperty() {
+ mEnabled = SystemProperties.getBoolean("debug.systemui.latency_tracking", false);
+ }
+
+ public static boolean isEnabled(Context ctx) {
+ return Build.IS_DEBUGGABLE && getInstance(ctx).mEnabled;
+ }
+
+ /**
+ * Notifies that an action is starting. This needs to be called from the main thread.
+ *
+ * @param action The action to start. One of the ACTION_* values.
+ */
+ public void onActionStart(int action) {
+ if (!mEnabled) {
+ return;
+ }
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, NAMES[action], 0);
+ mStartRtc.put(action, SystemClock.elapsedRealtime());
+ }
+
+ /**
+ * Notifies that an action has ended. This needs to be called from the main thread.
+ *
+ * @param action The action to end. One of the ACTION_* values.
+ */
+ public void onActionEnd(int action) {
+ if (!mEnabled) {
+ return;
+ }
+ long endRtc = SystemClock.elapsedRealtime();
+ long startRtc = mStartRtc.get(action, -1);
+ if (startRtc == -1) {
+ return;
+ }
+ mStartRtc.delete(action);
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, NAMES[action], 0);
+ long duration = endRtc - startRtc;
+ Log.i(TAG, "action=" + action + " latency=" + duration);
+ EventLog.writeEvent(EventLogTags.SYSUI_LATENCY, action, (int) duration);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java
new file mode 100644
index 000000000000..e59602b1cfff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java
@@ -0,0 +1,71 @@
+/*
+ * 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.keyguard;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+
+/**
+ * Hover listener that implements lift-to-activate interaction for
+ * accessibility. May be added to multiple views.
+ */
+class LiftToActivateListener implements View.OnHoverListener {
+ /** Manager used to query accessibility enabled state. */
+ private final AccessibilityManager mAccessibilityManager;
+
+ private boolean mCachedClickableState;
+
+ public LiftToActivateListener(Context context) {
+ mAccessibilityManager = (AccessibilityManager) context.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ }
+
+ @Override
+ public boolean onHover(View v, MotionEvent event) {
+ // When touch exploration is turned on, lifting a finger while
+ // inside the view bounds should perform a click action.
+ if (mAccessibilityManager.isEnabled()
+ && mAccessibilityManager.isTouchExplorationEnabled()) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ // Lift-to-type temporarily disables double-tap
+ // activation by setting the view as not clickable.
+ mCachedClickableState = v.isClickable();
+ v.setClickable(false);
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ final int x = (int) event.getX();
+ final int y = (int) event.getY();
+ if ((x > v.getPaddingLeft()) && (y > v.getPaddingTop())
+ && (x < v.getWidth() - v.getPaddingRight())
+ && (y < v.getHeight() - v.getPaddingBottom())) {
+ v.performClick();
+ }
+ v.setClickable(mCachedClickableState);
+ break;
+ }
+ }
+
+ // Pass the event to View.onHoverEvent() to handle accessibility.
+ v.onHoverEvent(event);
+
+ // Consume the event so it doesn't fall through to other views.
+ return true;
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
new file mode 100644
index 000000000000..1518bdced99a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.HapticFeedbackConstants;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.internal.widget.LockPatternUtils;
+
+public class NumPadKey extends ViewGroup {
+ // list of "ABC", etc per digit, starting with '0'
+ static String sKlondike[];
+
+ private int mDigit = -1;
+ private int mTextViewResId;
+ private PasswordTextView mTextView;
+ private TextView mDigitText;
+ private TextView mKlondikeText;
+ private boolean mEnableHaptics;
+ private PowerManager mPM;
+
+ private View.OnClickListener mListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View thisView) {
+ if (mTextView == null && mTextViewResId > 0) {
+ final View v = NumPadKey.this.getRootView().findViewById(mTextViewResId);
+ if (v != null && v instanceof PasswordTextView) {
+ mTextView = (PasswordTextView) v;
+ }
+ }
+ if (mTextView != null && mTextView.isEnabled()) {
+ mTextView.append(Character.forDigit(mDigit, 10));
+ }
+ userActivity();;
+ }
+ };
+
+ public void userActivity() {
+ mPM.userActivity(SystemClock.uptimeMillis(), false);
+ }
+
+ public NumPadKey(Context context) {
+ this(context, null);
+ }
+
+ public NumPadKey(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NumPadKey(Context context, AttributeSet attrs, int defStyle) {
+ this(context, attrs, defStyle, R.layout.keyguard_num_pad_key);
+ }
+
+ protected NumPadKey(Context context, AttributeSet attrs, int defStyle, int contentResource) {
+ super(context, attrs, defStyle);
+ setFocusable(true);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NumPadKey);
+
+ try {
+ mDigit = a.getInt(R.styleable.NumPadKey_digit, mDigit);
+ mTextViewResId = a.getResourceId(R.styleable.NumPadKey_textView, 0);
+ } finally {
+ a.recycle();
+ }
+
+ setOnClickListener(mListener);
+ setOnHoverListener(new LiftToActivateListener(context));
+ setAccessibilityDelegate(new ObscureSpeechDelegate(context));
+
+ mEnableHaptics = new LockPatternUtils(context).isTactileFeedbackEnabled();
+
+ mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(contentResource, this, true);
+
+ mDigitText = (TextView) findViewById(R.id.digit_text);
+ mDigitText.setText(Integer.toString(mDigit));
+ mKlondikeText = (TextView) findViewById(R.id.klondike_text);
+
+ if (mDigit >= 0) {
+ if (sKlondike == null) {
+ sKlondike = getResources().getStringArray(R.array.lockscreen_num_pad_klondike);
+ }
+ if (sKlondike != null && sKlondike.length > mDigit) {
+ String klondike = sKlondike[mDigit];
+ final int len = klondike.length();
+ if (len > 0) {
+ mKlondikeText.setText(klondike);
+ } else {
+ mKlondikeText.setVisibility(View.INVISIBLE);
+ }
+ }
+ }
+
+ a = context.obtainStyledAttributes(attrs, android.R.styleable.View);
+ if (!a.hasValueOrEmpty(android.R.styleable.View_background)) {
+ setBackground(mContext.getDrawable(R.drawable.ripple_drawable));
+ }
+ a.recycle();
+ setContentDescription(mDigitText.getText().toString());
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ doHapticKeyClick();
+ }
+ return super.onTouchEvent(event);
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ // Reset the "announced headset" flag when detached.
+ ObscureSpeechDelegate.sAnnouncedHeadset = false;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int digitHeight = mDigitText.getMeasuredHeight();
+ int klondikeHeight = mKlondikeText.getMeasuredHeight();
+ int totalHeight = digitHeight + klondikeHeight;
+ int top = getHeight() / 2 - totalHeight / 2;
+ int centerX = getWidth() / 2;
+ int left = centerX - mDigitText.getMeasuredWidth() / 2;
+ int bottom = top + digitHeight;
+ mDigitText.layout(left, top, left + mDigitText.getMeasuredWidth(), bottom);
+ top = (int) (bottom - klondikeHeight * 0.35f);
+ bottom = top + klondikeHeight;
+
+ left = centerX - mKlondikeText.getMeasuredWidth() / 2;
+ mKlondikeText.layout(left, top, left + mKlondikeText.getMeasuredWidth(), bottom);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ // Cause a VIRTUAL_KEY vibration
+ public void doHapticKeyClick() {
+ if (mEnableHaptics) {
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
+ | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/ObscureSpeechDelegate.java b/packages/SystemUI/src/com/android/keyguard/ObscureSpeechDelegate.java
new file mode 100644
index 000000000000..410a43a6789a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/ObscureSpeechDelegate.java
@@ -0,0 +1,102 @@
+/*
+ * 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.keyguard;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.R;
+
+/**
+ * Accessibility delegate that obscures speech for a view when the user has
+ * not turned on the "speak passwords" preference and is not listening
+ * through headphones.
+ */
+class ObscureSpeechDelegate extends AccessibilityDelegate {
+ /** Whether any client has announced the "headset" notification. */
+ static boolean sAnnouncedHeadset = false;
+
+ private final ContentResolver mContentResolver;
+ private final AudioManager mAudioManager;
+
+ public ObscureSpeechDelegate(Context context) {
+ mContentResolver = context.getContentResolver();
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ }
+
+ @Override
+ public void sendAccessibilityEvent(View host, int eventType) {
+ super.sendAccessibilityEvent(host, eventType);
+
+ // Play the "headset required" announcement the first time the user
+ // places accessibility focus on a key.
+ if ((eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED)
+ && !sAnnouncedHeadset && shouldObscureSpeech()) {
+ sAnnouncedHeadset = true;
+ host.announceForAccessibility(host.getContext().getString(
+ R.string.keyboard_headset_required_to_hear_password));
+ }
+ }
+
+ @Override
+ public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(host, event);
+
+ if ((event.getEventType() != AccessibilityEvent.TYPE_ANNOUNCEMENT)
+ && shouldObscureSpeech()) {
+ event.getText().clear();
+ event.setContentDescription(host.getContext().getString(
+ R.string.keyboard_password_character_no_headset));
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+
+ if (shouldObscureSpeech()) {
+ final Context ctx = host.getContext();
+ info.setText(null);
+ info.setContentDescription(
+ ctx.getString(R.string.keyboard_password_character_no_headset));
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private boolean shouldObscureSpeech() {
+ // The user can optionally force speaking passwords.
+ if (Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0, UserHandle.USER_CURRENT) != 0) {
+ return false;
+ }
+
+ // Always speak if the user is listening through headphones.
+ if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) {
+ return false;
+ }
+
+ // Don't speak since this key is used to type a password.
+ return true;
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
new file mode 100644
index 000000000000..48737f9a9bfb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -0,0 +1,703 @@
+/*
+ * 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.keyguard;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import java.util.ArrayList;
+import java.util.Stack;
+
+/**
+ * A View similar to a textView which contains password text and can animate when the text is
+ * changed
+ */
+public class PasswordTextView extends View {
+
+ private static final float DOT_OVERSHOOT_FACTOR = 1.5f;
+ private static final long DOT_APPEAR_DURATION_OVERSHOOT = 320;
+ private static final long APPEAR_DURATION = 160;
+ private static final long DISAPPEAR_DURATION = 160;
+ private static final long RESET_DELAY_PER_ELEMENT = 40;
+ private static final long RESET_MAX_DELAY = 200;
+
+ /**
+ * The overlap between the text disappearing and the dot appearing animation
+ */
+ private static final long DOT_APPEAR_TEXT_DISAPPEAR_OVERLAP_DURATION = 130;
+
+ /**
+ * The duration the text needs to stay there at least before it can morph into a dot
+ */
+ private static final long TEXT_REST_DURATION_AFTER_APPEAR = 100;
+
+ /**
+ * The duration the text should be visible, starting with the appear animation
+ */
+ private static final long TEXT_VISIBILITY_DURATION = 1300;
+
+ /**
+ * The position in time from [0,1] where the overshoot should be finished and the settle back
+ * animation of the dot should start
+ */
+ private static final float OVERSHOOT_TIME_POSITION = 0.5f;
+
+ /**
+ * The raw text size, will be multiplied by the scaled density when drawn
+ */
+ private final int mTextHeightRaw;
+ private final int mGravity;
+ private ArrayList<CharState> mTextChars = new ArrayList<>();
+ private String mText = "";
+ private Stack<CharState> mCharPool = new Stack<>();
+ private int mDotSize;
+ private PowerManager mPM;
+ private int mCharPadding;
+ private final Paint mDrawPaint = new Paint();
+ private Interpolator mAppearInterpolator;
+ private Interpolator mDisappearInterpolator;
+ private Interpolator mFastOutSlowInInterpolator;
+ private boolean mShowPassword;
+ private UserActivityListener mUserActivityListener;
+
+ public interface UserActivityListener {
+ void onUserActivity();
+ }
+
+ public PasswordTextView(Context context) {
+ this(context, null);
+ }
+
+ public PasswordTextView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PasswordTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public PasswordTextView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setFocusableInTouchMode(true);
+ setFocusable(true);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PasswordTextView);
+ try {
+ mTextHeightRaw = a.getInt(R.styleable.PasswordTextView_scaledTextSize, 0);
+ mGravity = a.getInt(R.styleable.PasswordTextView_android_gravity, Gravity.CENTER);
+ mDotSize = a.getDimensionPixelSize(R.styleable.PasswordTextView_dotSize,
+ getContext().getResources().getDimensionPixelSize(R.dimen.password_dot_size));
+ mCharPadding = a.getDimensionPixelSize(R.styleable.PasswordTextView_charPadding,
+ getContext().getResources().getDimensionPixelSize(
+ R.dimen.password_char_padding));
+ } finally {
+ a.recycle();
+ }
+ mDrawPaint.setFlags(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
+ mDrawPaint.setTextAlign(Paint.Align.CENTER);
+ mDrawPaint.setColor(0xffffffff);
+ mDrawPaint.setTypeface(Typeface.create("sans-serif-light", 0));
+ mShowPassword = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.TEXT_SHOW_PASSWORD, 1) == 1;
+ mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.linear_out_slow_in);
+ mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.fast_out_linear_in);
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.fast_out_slow_in);
+ mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ float totalDrawingWidth = getDrawingWidth();
+ float currentDrawPosition;
+ if ((mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT) {
+ if ((mGravity & Gravity.RELATIVE_LAYOUT_DIRECTION) != 0
+ && getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ currentDrawPosition = getWidth() - getPaddingRight() - totalDrawingWidth;
+ } else {
+ currentDrawPosition = getPaddingLeft();
+ }
+ } else {
+ currentDrawPosition = getWidth() / 2 - totalDrawingWidth / 2;
+ }
+ int length = mTextChars.size();
+ Rect bounds = getCharBounds();
+ int charHeight = (bounds.bottom - bounds.top);
+ float yPosition =
+ (getHeight() - getPaddingBottom() - getPaddingTop()) / 2 + getPaddingTop();
+ canvas.clipRect(getPaddingLeft(), getPaddingTop(),
+ getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
+ float charLength = bounds.right - bounds.left;
+ for (int i = 0; i < length; i++) {
+ CharState charState = mTextChars.get(i);
+ float charWidth = charState.draw(canvas, currentDrawPosition, charHeight, yPosition,
+ charLength);
+ currentDrawPosition += charWidth;
+ }
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ private Rect getCharBounds() {
+ float textHeight = mTextHeightRaw * getResources().getDisplayMetrics().scaledDensity;
+ mDrawPaint.setTextSize(textHeight);
+ Rect bounds = new Rect();
+ mDrawPaint.getTextBounds("0", 0, 1, bounds);
+ return bounds;
+ }
+
+ private float getDrawingWidth() {
+ int width = 0;
+ int length = mTextChars.size();
+ Rect bounds = getCharBounds();
+ int charLength = bounds.right - bounds.left;
+ for (int i = 0; i < length; i++) {
+ CharState charState = mTextChars.get(i);
+ if (i != 0) {
+ width += mCharPadding * charState.currentWidthFactor;
+ }
+ width += charLength * charState.currentWidthFactor;
+ }
+ return width;
+ }
+
+
+ public void append(char c) {
+ int visibleChars = mTextChars.size();
+ String textbefore = mText;
+ mText = mText + c;
+ int newLength = mText.length();
+ CharState charState;
+ if (newLength > visibleChars) {
+ charState = obtainCharState(c);
+ mTextChars.add(charState);
+ } else {
+ charState = mTextChars.get(newLength - 1);
+ charState.whichChar = c;
+ }
+ charState.startAppearAnimation();
+
+ // ensure that the previous element is being swapped
+ if (newLength > 1) {
+ CharState previousState = mTextChars.get(newLength - 2);
+ if (previousState.isDotSwapPending) {
+ previousState.swapToDotWhenAppearFinished();
+ }
+ }
+ userActivity();
+ sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length(), 0, 1);
+ }
+
+ public void setUserActivityListener(UserActivityListener userActivitiListener) {
+ mUserActivityListener = userActivitiListener;
+ }
+
+ private void userActivity() {
+ mPM.userActivity(SystemClock.uptimeMillis(), false);
+ if (mUserActivityListener != null) {
+ mUserActivityListener.onUserActivity();
+ }
+ }
+
+ public void deleteLastChar() {
+ int length = mText.length();
+ String textbefore = mText;
+ if (length > 0) {
+ mText = mText.substring(0, length - 1);
+ CharState charState = mTextChars.get(length - 1);
+ charState.startRemoveAnimation(0, 0);
+ }
+ userActivity();
+ sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length() - 1, 1, 0);
+ }
+
+ public String getText() {
+ return mText;
+ }
+
+ private CharState obtainCharState(char c) {
+ CharState charState;
+ if(mCharPool.isEmpty()) {
+ charState = new CharState();
+ } else {
+ charState = mCharPool.pop();
+ charState.reset();
+ }
+ charState.whichChar = c;
+ return charState;
+ }
+
+ public void reset(boolean animated, boolean announce) {
+ String textbefore = mText;
+ mText = "";
+ int length = mTextChars.size();
+ int middleIndex = (length - 1) / 2;
+ long delayPerElement = RESET_DELAY_PER_ELEMENT;
+ for (int i = 0; i < length; i++) {
+ CharState charState = mTextChars.get(i);
+ if (animated) {
+ int delayIndex;
+ if (i <= middleIndex) {
+ delayIndex = i * 2;
+ } else {
+ int distToMiddle = i - middleIndex;
+ delayIndex = (length - 1) - (distToMiddle - 1) * 2;
+ }
+ long startDelay = delayIndex * delayPerElement;
+ startDelay = Math.min(startDelay, RESET_MAX_DELAY);
+ long maxDelay = delayPerElement * (length - 1);
+ maxDelay = Math.min(maxDelay, RESET_MAX_DELAY) + DISAPPEAR_DURATION;
+ charState.startRemoveAnimation(startDelay, maxDelay);
+ charState.removeDotSwapCallbacks();
+ } else {
+ mCharPool.push(charState);
+ }
+ }
+ if (!animated) {
+ mTextChars.clear();
+ }
+ if (announce) {
+ sendAccessibilityEventTypeViewTextChanged(textbefore, 0, textbefore.length(), 0);
+ }
+ }
+
+ void sendAccessibilityEventTypeViewTextChanged(String beforeText, int fromIndex,
+ int removedCount, int addedCount) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled() &&
+ (isFocused() || isSelected() && isShown())) {
+ if (!shouldSpeakPasswordsForAccessibility()) {
+ beforeText = null;
+ }
+ AccessibilityEvent event =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
+ event.setFromIndex(fromIndex);
+ event.setRemovedCount(removedCount);
+ event.setAddedCount(addedCount);
+ event.setBeforeText(beforeText);
+ event.setPassword(true);
+ sendAccessibilityEventUnchecked(event);
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+
+ event.setClassName(PasswordTextView.class.getName());
+ event.setPassword(true);
+ }
+
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+
+ if (shouldSpeakPasswordsForAccessibility()) {
+ final CharSequence text = mText;
+ if (!TextUtils.isEmpty(text)) {
+ event.getText().add(text);
+ }
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+
+ info.setClassName(PasswordTextView.class.getName());
+ info.setPassword(true);
+
+ if (shouldSpeakPasswordsForAccessibility()) {
+ info.setText(mText);
+ }
+
+ info.setEditable(true);
+
+ info.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+ }
+
+ /**
+ * @return true if the user has explicitly allowed accessibility services
+ * to speak passwords.
+ */
+ private boolean shouldSpeakPasswordsForAccessibility() {
+ return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
+ UserHandle.USER_CURRENT_OR_SELF) == 1);
+ }
+
+ private class CharState {
+ char whichChar;
+ ValueAnimator textAnimator;
+ boolean textAnimationIsGrowing;
+ Animator dotAnimator;
+ boolean dotAnimationIsGrowing;
+ ValueAnimator widthAnimator;
+ boolean widthAnimationIsGrowing;
+ float currentTextSizeFactor;
+ float currentDotSizeFactor;
+ float currentWidthFactor;
+ boolean isDotSwapPending;
+ float currentTextTranslationY = 1.0f;
+ ValueAnimator textTranslateAnimator;
+
+ Animator.AnimatorListener removeEndListener = new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mCancelled) {
+ mTextChars.remove(CharState.this);
+ mCharPool.push(CharState.this);
+ reset();
+ cancelAnimator(textTranslateAnimator);
+ textTranslateAnimator = null;
+ }
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mCancelled = false;
+ }
+ };
+
+ Animator.AnimatorListener dotFinishListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ dotAnimator = null;
+ }
+ };
+
+ Animator.AnimatorListener textFinishListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ textAnimator = null;
+ }
+ };
+
+ Animator.AnimatorListener textTranslateFinishListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ textTranslateAnimator = null;
+ }
+ };
+
+ Animator.AnimatorListener widthFinishListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ widthAnimator = null;
+ }
+ };
+
+ private ValueAnimator.AnimatorUpdateListener dotSizeUpdater
+ = new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ currentDotSizeFactor = (float) animation.getAnimatedValue();
+ invalidate();
+ }
+ };
+
+ private ValueAnimator.AnimatorUpdateListener textSizeUpdater
+ = new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ currentTextSizeFactor = (float) animation.getAnimatedValue();
+ invalidate();
+ }
+ };
+
+ private ValueAnimator.AnimatorUpdateListener textTranslationUpdater
+ = new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ currentTextTranslationY = (float) animation.getAnimatedValue();
+ invalidate();
+ }
+ };
+
+ private ValueAnimator.AnimatorUpdateListener widthUpdater
+ = new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ currentWidthFactor = (float) animation.getAnimatedValue();
+ invalidate();
+ }
+ };
+
+ private Runnable dotSwapperRunnable = new Runnable() {
+ @Override
+ public void run() {
+ performSwap();
+ isDotSwapPending = false;
+ }
+ };
+
+ void reset() {
+ whichChar = 0;
+ currentTextSizeFactor = 0.0f;
+ currentDotSizeFactor = 0.0f;
+ currentWidthFactor = 0.0f;
+ cancelAnimator(textAnimator);
+ textAnimator = null;
+ cancelAnimator(dotAnimator);
+ dotAnimator = null;
+ cancelAnimator(widthAnimator);
+ widthAnimator = null;
+ currentTextTranslationY = 1.0f;
+ removeDotSwapCallbacks();
+ }
+
+ void startRemoveAnimation(long startDelay, long widthDelay) {
+ boolean dotNeedsAnimation = (currentDotSizeFactor > 0.0f && dotAnimator == null)
+ || (dotAnimator != null && dotAnimationIsGrowing);
+ boolean textNeedsAnimation = (currentTextSizeFactor > 0.0f && textAnimator == null)
+ || (textAnimator != null && textAnimationIsGrowing);
+ boolean widthNeedsAnimation = (currentWidthFactor > 0.0f && widthAnimator == null)
+ || (widthAnimator != null && widthAnimationIsGrowing);
+ if (dotNeedsAnimation) {
+ startDotDisappearAnimation(startDelay);
+ }
+ if (textNeedsAnimation) {
+ startTextDisappearAnimation(startDelay);
+ }
+ if (widthNeedsAnimation) {
+ startWidthDisappearAnimation(widthDelay);
+ }
+ }
+
+ void startAppearAnimation() {
+ boolean dotNeedsAnimation = !mShowPassword
+ && (dotAnimator == null || !dotAnimationIsGrowing);
+ boolean textNeedsAnimation = mShowPassword
+ && (textAnimator == null || !textAnimationIsGrowing);
+ boolean widthNeedsAnimation = (widthAnimator == null || !widthAnimationIsGrowing);
+ if (dotNeedsAnimation) {
+ startDotAppearAnimation(0);
+ }
+ if (textNeedsAnimation) {
+ startTextAppearAnimation();
+ }
+ if (widthNeedsAnimation) {
+ startWidthAppearAnimation();
+ }
+ if (mShowPassword) {
+ postDotSwap(TEXT_VISIBILITY_DURATION);
+ }
+ }
+
+ /**
+ * Posts a runnable which ensures that the text will be replaced by a dot after {@link
+ * com.android.keyguard.PasswordTextView#TEXT_VISIBILITY_DURATION}.
+ */
+ private void postDotSwap(long delay) {
+ removeDotSwapCallbacks();
+ postDelayed(dotSwapperRunnable, delay);
+ isDotSwapPending = true;
+ }
+
+ private void removeDotSwapCallbacks() {
+ removeCallbacks(dotSwapperRunnable);
+ isDotSwapPending = false;
+ }
+
+ void swapToDotWhenAppearFinished() {
+ removeDotSwapCallbacks();
+ if (textAnimator != null) {
+ long remainingDuration = textAnimator.getDuration()
+ - textAnimator.getCurrentPlayTime();
+ postDotSwap(remainingDuration + TEXT_REST_DURATION_AFTER_APPEAR);
+ } else {
+ performSwap();
+ }
+ }
+
+ private void performSwap() {
+ startTextDisappearAnimation(0);
+ startDotAppearAnimation(DISAPPEAR_DURATION
+ - DOT_APPEAR_TEXT_DISAPPEAR_OVERLAP_DURATION);
+ }
+
+ private void startWidthDisappearAnimation(long widthDelay) {
+ cancelAnimator(widthAnimator);
+ widthAnimator = ValueAnimator.ofFloat(currentWidthFactor, 0.0f);
+ widthAnimator.addUpdateListener(widthUpdater);
+ widthAnimator.addListener(widthFinishListener);
+ widthAnimator.addListener(removeEndListener);
+ widthAnimator.setDuration((long) (DISAPPEAR_DURATION * currentWidthFactor));
+ widthAnimator.setStartDelay(widthDelay);
+ widthAnimator.start();
+ widthAnimationIsGrowing = false;
+ }
+
+ private void startTextDisappearAnimation(long startDelay) {
+ cancelAnimator(textAnimator);
+ textAnimator = ValueAnimator.ofFloat(currentTextSizeFactor, 0.0f);
+ textAnimator.addUpdateListener(textSizeUpdater);
+ textAnimator.addListener(textFinishListener);
+ textAnimator.setInterpolator(mDisappearInterpolator);
+ textAnimator.setDuration((long) (DISAPPEAR_DURATION * currentTextSizeFactor));
+ textAnimator.setStartDelay(startDelay);
+ textAnimator.start();
+ textAnimationIsGrowing = false;
+ }
+
+ private void startDotDisappearAnimation(long startDelay) {
+ cancelAnimator(dotAnimator);
+ ValueAnimator animator = ValueAnimator.ofFloat(currentDotSizeFactor, 0.0f);
+ animator.addUpdateListener(dotSizeUpdater);
+ animator.addListener(dotFinishListener);
+ animator.setInterpolator(mDisappearInterpolator);
+ long duration = (long) (DISAPPEAR_DURATION * Math.min(currentDotSizeFactor, 1.0f));
+ animator.setDuration(duration);
+ animator.setStartDelay(startDelay);
+ animator.start();
+ dotAnimator = animator;
+ dotAnimationIsGrowing = false;
+ }
+
+ private void startWidthAppearAnimation() {
+ cancelAnimator(widthAnimator);
+ widthAnimator = ValueAnimator.ofFloat(currentWidthFactor, 1.0f);
+ widthAnimator.addUpdateListener(widthUpdater);
+ widthAnimator.addListener(widthFinishListener);
+ widthAnimator.setDuration((long) (APPEAR_DURATION * (1f - currentWidthFactor)));
+ widthAnimator.start();
+ widthAnimationIsGrowing = true;
+ }
+
+ private void startTextAppearAnimation() {
+ cancelAnimator(textAnimator);
+ textAnimator = ValueAnimator.ofFloat(currentTextSizeFactor, 1.0f);
+ textAnimator.addUpdateListener(textSizeUpdater);
+ textAnimator.addListener(textFinishListener);
+ textAnimator.setInterpolator(mAppearInterpolator);
+ textAnimator.setDuration((long) (APPEAR_DURATION * (1f - currentTextSizeFactor)));
+ textAnimator.start();
+ textAnimationIsGrowing = true;
+
+ // handle translation
+ if (textTranslateAnimator == null) {
+ textTranslateAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
+ textTranslateAnimator.addUpdateListener(textTranslationUpdater);
+ textTranslateAnimator.addListener(textTranslateFinishListener);
+ textTranslateAnimator.setInterpolator(mAppearInterpolator);
+ textTranslateAnimator.setDuration(APPEAR_DURATION);
+ textTranslateAnimator.start();
+ }
+ }
+
+ private void startDotAppearAnimation(long delay) {
+ cancelAnimator(dotAnimator);
+ if (!mShowPassword) {
+ // We perform an overshoot animation
+ ValueAnimator overShootAnimator = ValueAnimator.ofFloat(currentDotSizeFactor,
+ DOT_OVERSHOOT_FACTOR);
+ overShootAnimator.addUpdateListener(dotSizeUpdater);
+ overShootAnimator.setInterpolator(mAppearInterpolator);
+ long overShootDuration = (long) (DOT_APPEAR_DURATION_OVERSHOOT
+ * OVERSHOOT_TIME_POSITION);
+ overShootAnimator.setDuration(overShootDuration);
+ ValueAnimator settleBackAnimator = ValueAnimator.ofFloat(DOT_OVERSHOOT_FACTOR,
+ 1.0f);
+ settleBackAnimator.addUpdateListener(dotSizeUpdater);
+ settleBackAnimator.setDuration(DOT_APPEAR_DURATION_OVERSHOOT - overShootDuration);
+ settleBackAnimator.addListener(dotFinishListener);
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playSequentially(overShootAnimator, settleBackAnimator);
+ animatorSet.setStartDelay(delay);
+ animatorSet.start();
+ dotAnimator = animatorSet;
+ } else {
+ ValueAnimator growAnimator = ValueAnimator.ofFloat(currentDotSizeFactor, 1.0f);
+ growAnimator.addUpdateListener(dotSizeUpdater);
+ growAnimator.setDuration((long) (APPEAR_DURATION * (1.0f - currentDotSizeFactor)));
+ growAnimator.addListener(dotFinishListener);
+ growAnimator.setStartDelay(delay);
+ growAnimator.start();
+ dotAnimator = growAnimator;
+ }
+ dotAnimationIsGrowing = true;
+ }
+
+ private void cancelAnimator(Animator animator) {
+ if (animator != null) {
+ animator.cancel();
+ }
+ }
+
+ /**
+ * Draw this char to the canvas.
+ *
+ * @return The width this character contributes, including padding.
+ */
+ public float draw(Canvas canvas, float currentDrawPosition, int charHeight, float yPosition,
+ float charLength) {
+ boolean textVisible = currentTextSizeFactor > 0;
+ boolean dotVisible = currentDotSizeFactor > 0;
+ float charWidth = charLength * currentWidthFactor;
+ if (textVisible) {
+ float currYPosition = yPosition + charHeight / 2.0f * currentTextSizeFactor
+ + charHeight * currentTextTranslationY * 0.8f;
+ canvas.save();
+ float centerX = currentDrawPosition + charWidth / 2;
+ canvas.translate(centerX, currYPosition);
+ canvas.scale(currentTextSizeFactor, currentTextSizeFactor);
+ canvas.drawText(Character.toString(whichChar), 0, 0, mDrawPaint);
+ canvas.restore();
+ }
+ if (dotVisible) {
+ canvas.save();
+ float centerX = currentDrawPosition + charWidth / 2;
+ canvas.translate(centerX, yPosition);
+ canvas.drawCircle(0, 0, mDotSize / 2 * currentDotSizeFactor, mDrawPaint);
+ canvas.restore();
+ }
+ return charWidth + mCharPadding * currentWidthFactor;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java b/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java
new file mode 100644
index 000000000000..6977b51e8712
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2012 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.keyguard;
+
+public interface SecurityMessageDisplay {
+
+ void setNextMessageColor(int color);
+
+ void setMessage(CharSequence msg);
+
+ void setMessage(int resId);
+
+ void formatMessage(int resId, Object... formatArgs);
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
new file mode 100644
index 000000000000..327d218913d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
@@ -0,0 +1,91 @@
+/*
+ * 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.keyguard;
+
+/**
+ * The callback used by the keyguard view to tell the {@link KeyguardViewMediator}
+ * various things.
+ */
+public interface ViewMediatorCallback {
+ /**
+ * Reports user activity and requests that the screen stay on.
+ */
+ void userActivity();
+
+ /**
+ * Report that the keyguard is done.
+ *
+ * @param strongAuth whether the user has authenticated with strong authentication like
+ * pattern, password or PIN but not by trust agents or fingerprint
+ * @param targetUserId a user that needs to be the foreground user at the completion.
+ */
+ void keyguardDone(boolean strongAuth, int targetUserId);
+
+ /**
+ * Report that the keyguard is done drawing.
+ */
+ void keyguardDoneDrawing();
+
+ /**
+ * Tell ViewMediator that the current view needs IME input
+ * @param needsInput
+ */
+ void setNeedsInput(boolean needsInput);
+
+ /**
+ * Report that the keyguard is dismissable, pending the next keyguardDone call.
+ *
+ * @param strongAuth whether the user has authenticated with strong authentication like
+ * pattern, password or PIN but not by trust agents or fingerprint
+ * @param targetUserId a user that needs to be the foreground user at the completion.
+ */
+ void keyguardDonePending(boolean strongAuth, int targetUserId);
+
+ /**
+ * Report when keyguard is actually gone
+ */
+ void keyguardGone();
+
+ /**
+ * Report when the UI is ready for dismissing the whole Keyguard.
+ */
+ void readyForKeyguardDone();
+
+ /**
+ * Reset the keyguard and bouncer.
+ */
+ void resetKeyguard();
+
+ /**
+ * Play the "device trusted" sound.
+ */
+ void playTrustedSound();
+
+ /**
+ * @return true if the screen is on
+ */
+ boolean isScreenOn();
+
+ /**
+ * @return one of the reasons why the bouncer needs to be shown right now and the user can't use
+ * his normal unlock method like fingerprint or trust agents. See
+ * {@link KeyguardSecurityView#PROMPT_REASON_NONE},
+ * {@link KeyguardSecurityView#PROMPT_REASON_RESTART} and
+ * {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}.
+ */
+ int getBouncerPromptReason();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
new file mode 100644
index 000000000000..d4149ea6d68f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
@@ -0,0 +1,68 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.systemui;
+
+# ---------------------------
+# PhoneStatusBar.java
+# ---------------------------
+36000 sysui_statusbar_touch (type|1),(x|1),(y|1),(disable1|1),(disable2|1)
+36001 sysui_heads_up_status (key|3),(visible|1)
+36002 sysui_fullscreen_notification (key|3)
+36003 sysui_heads_up_escalation (key|3)
+# sysui_status_bar_state: Logged whenever the status bar / keyguard state changes
+## state: 0: SHADE, 1: KEYGUARD, 2: SHADE_LOCKED
+## keyguardShowing: 1: Keyguard shown to the user (or keyguardOccluded)
+## keyguardOccluded: 1: Keyguard active, but another activity is occluding it
+## bouncerShowing: 1: Bouncer currently shown to the user
+## secure: 1: The user has set up a secure unlock method (PIN, password, etc.)
+## currentlyInsecure: 1: No secure unlock method set up (!secure), or trusted environment (TrustManager)
+36004 sysui_status_bar_state (state|1),(keyguardShowing|1),(keyguardOccluded|1),(bouncerShowing|1),(secure|1),(currentlyInsecure|1)
+
+# ---------------------------
+# PhoneStatusBarView.java
+# ---------------------------
+36010 sysui_panelbar_touch (type|1),(x|1),(y|1),(enabled|1)
+
+# ---------------------------
+# NotificationPanelView.java
+# ---------------------------
+36020 sysui_notificationpanel_touch (type|1),(x|1),(y|1)
+## type: 1: SWIPE_UP_UNLOCK Swiped up to dismiss the lockscreen.
+## 2: SWIPE_DOWN_FULL_SHADE Swiped down to enter full shade.
+## 3: TAP_UNLOCK_HINT Tapped in empty area, causes unlock hint.
+## 4: SWIPE_CAMERA Swiped the camera icon, launches.
+## 5: SWIPE_DIALER Swiped the dialer icon, launches.
+## 6: TAP_LOCK Tapped the (unlocked) lock icon, locks the device.
+## 7: TAP_NOTIFICATION_ACTIVATE Tapped a lockscreen notification, causes "tap again" hint.
+## Note: Second tap logged as notification_clicked.
+36021 sysui_lockscreen_gesture (type|1),(lengthDp|1),(velocityDp|1)
+
+# ---------------------------
+# SettingsPanelView.java
+# ---------------------------
+36030 sysui_quickpanel_touch (type|1),(x|1),(y|1)
+
+# ---------------------------
+# PanelHolder.java
+# ---------------------------
+36040 sysui_panelholder_touch (type|1),(x|1),(y|1)
+
+# ---------------------------
+# SearchPanelView.java
+# ---------------------------
+36050 sysui_searchpanel_touch (type|1),(x|1),(y|1)
+
+# ---------------------------
+# Recents.java, RecentsSystemUser.java
+# ---------------------------
+## type: 1: USER_BIND_SERVICE Secondary user tries binding to the system sysui service
+## 2: USER_SYSTEM_BOUND Secondary user is bound to the system sysui service
+## 3: USER_SYSTEM_UNBOUND Secondary user loses connection after system sysui has died
+## 4: SYSTEM_REGISTER_USER System sysui registers user's callbacks
+## 5: SYSTEM_UNREGISTER_USER System sysui unregisters user's callbacks (after death)
+36060 sysui_recents_connection (type|1),(user|1)
+
+# ---------------------------
+# LatencyTracker.java
+# ---------------------------
+36070 sysui_latency (action|1|5),(latency|1|3)