summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJim Miller <jaggies@google.com>2012-08-23 19:18:12 -0700
committerJim Miller <jaggies@google.com>2012-08-29 12:36:26 -0700
commitdcb3d84b82cc2448d04e73359a716581bfb657db (patch)
tree5e31637cfe9751114f96c32b35baa7664ab4099c
parent0d43c567cea30e6fb7af0f7adadb1c620339c0f5 (diff)
downloadframeworks_base-dcb3d84b82cc2448d04e73359a716581bfb657db.zip
frameworks_base-dcb3d84b82cc2448d04e73359a716581bfb657db.tar.gz
frameworks_base-dcb3d84b82cc2448d04e73359a716581bfb657db.tar.bz2
Replace keyguard with new implementation
This change refactors keyguard to be more modular and maintainable. More specifically, it replaces the top-level view with just one device-dependent view that contains two views: a widget area and a security area. The widget area can be populated with custom widgets. The security area contains the current security method as dictated by the stored password quality. This change contains both the old and the new keyguard with the old keyguard still enabled. The new keyguard will be enabled in a subsequent change. Change-Id: Id75286113771ca1407e9db182172b580f870b612
-rw-r--r--core/res/res/anim/keyguard_security_animate_in.xml33
-rw-r--r--core/res/res/anim/keyguard_security_animate_out.xml32
-rw-r--r--core/res/res/layout-land/keyguard_host_view.xml58
-rw-r--r--core/res/res/layout-port/keyguard_host_view.xml47
-rw-r--r--core/res/res/layout-sw600dp-land/keyguard_host_view.xml67
-rw-r--r--core/res/res/layout-sw600dp-port/keyguard_host_view.xml61
-rw-r--r--core/res/res/layout/keyguard_account_view.xml72
-rw-r--r--core/res/res/layout/keyguard_face_unlock_view.xml31
-rw-r--r--core/res/res/layout/keyguard_navigation.xml61
-rw-r--r--core/res/res/layout/keyguard_password_view.xml100
-rw-r--r--core/res/res/layout/keyguard_pattern_view.xml55
-rw-r--r--core/res/res/layout/keyguard_selector_view.xml82
-rw-r--r--core/res/res/layout/keyguard_sim_pin_view.xml82
-rw-r--r--core/res/res/layout/keyguard_sim_puk_view.xml133
-rw-r--r--core/res/res/layout/keyguard_status_view.xml114
-rwxr-xr-xcore/res/res/values/attrs.xml18
-rw-r--r--core/res/res/values/dimens.xml14
-rw-r--r--core/res/res/values/integers.xml21
-rw-r--r--core/res/res/values/public.xml52
-rwxr-xr-xcore/res/res/values/strings.xml30
-rwxr-xr-xpolicy/src/com/android/internal/policy/impl/PhoneWindowManager.java2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/BiometricSensorUnlock.java (renamed from policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java542
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java317
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java194
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java443
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java59
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java340
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java367
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java62
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java85
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java64
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java263
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java236
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java301
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java44
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java570
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java710
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitorCallback.java)5
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java254
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java318
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java1342
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetView.java140
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/PagedView.java1704
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/AccountUnlockScreen.java (renamed from policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/BiometricSensorUnlock.java80
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/FaceUnlock.java (renamed from policy/src/com/android/internal/policy/impl/FaceUnlock.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreen.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardScreen.java)4
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreenCallback.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardScreenCallback.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardStatusViewManager.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java)6
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitor.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitorCallback.java96
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewBase.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardViewBase.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewCallback.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardViewCallback.java)4
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewManager.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardViewManager.java)4
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewMediator.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java)3
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewProperties.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardViewProperties.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardWindowController.java (renamed from policy/src/com/android/internal/policy/impl/KeyguardWindowController.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardView.java (renamed from policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java)5
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewProperties.java (renamed from policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockScreen.java (renamed from policy/src/com/android/internal/policy/impl/LockScreen.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/PasswordUnlockScreen.java (renamed from policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/PatternUnlockScreen.java (renamed from policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimPukUnlockScreen.java (renamed from policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java)2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimUnlockScreen.java (renamed from policy/src/com/android/internal/policy/impl/SimUnlockScreen.java)2
-rw-r--r--policy/tests/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewTest.java (renamed from policy/tests/src/com/android/internal/policy/impl/LockPatternKeyguardViewTest.java)9
66 files changed, 9731 insertions, 33 deletions
diff --git a/core/res/res/anim/keyguard_security_animate_in.xml b/core/res/res/anim/keyguard_security_animate_in.xml
new file mode 100644
index 0000000..6e1e17a
--- /dev/null
+++ b/core/res/res/anim/keyguard_security_animate_in.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false">
+
+ <scale
+ android:interpolator="@android:anim/decelerate_interpolator"
+ android:fromXScale="0.0"
+ android:toXScale="1.0"
+ android:fromYScale="1.0"
+ android:toYScale="1.0"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillAfter="true"
+ android:duration="@integer/flip_duration"
+ android:startOffset="@integer/flip_duration" />
+
+</set>
+
diff --git a/core/res/res/anim/keyguard_security_animate_out.xml b/core/res/res/anim/keyguard_security_animate_out.xml
new file mode 100644
index 0000000..5d65cd0
--- /dev/null
+++ b/core/res/res/anim/keyguard_security_animate_out.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false">
+
+ <scale
+ android:interpolator="@android:anim/accelerate_interpolator"
+ android:fromXScale="1.0"
+ android:toXScale="0.0"
+ android:fromYScale="1.0"
+ android:toYScale="1.0"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillAfter="true"
+ android:duration="@integer/flip_duration" />
+
+</set>
+
diff --git a/core/res/res/layout-land/keyguard_host_view.xml b/core/res/res/layout-land/keyguard_host_view.xml
new file mode 100644
index 0000000..ca0fa1d
--- /dev/null
+++ b/core/res/res/layout-land/keyguard_host_view.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<!-- This is the host view that generally contains two sub views: the widget view
+ and the security view. -->
+<com.android.internal.policy.impl.keyguard.KeyguardHostView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_host_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <com.android.internal.policy.impl.keyguard.KeyguardWidgetView
+ android:id="@+id/app_widget_container"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:visibility="gone">
+
+ <!-- TODO: Remove this once supported as a widget -->
+ <include layout="@layout/keyguard_status_view"/>
+
+ </com.android.internal.policy.impl.keyguard.KeyguardWidgetView>
+
+ <ViewFlipper
+ android:id="@+id/view_flipper"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center">
+
+ <include layout="@layout/keyguard_selector_view"/>
+ <include layout="@layout/keyguard_account_view"/>
+ <include layout="@layout/keyguard_pattern_view"/>
+ <include layout="@layout/keyguard_password_view"/>
+ <include layout="@layout/keyguard_sim_pin_view"/>
+ <include layout="@layout/keyguard_sim_puk_view"/>
+ <include layout="@layout/keyguard_face_unlock_view"/>
+
+ </ViewFlipper>
+
+</com.android.internal.policy.impl.keyguard.KeyguardHostView>
diff --git a/core/res/res/layout-port/keyguard_host_view.xml b/core/res/res/layout-port/keyguard_host_view.xml
new file mode 100644
index 0000000..5dc2225
--- /dev/null
+++ b/core/res/res/layout-port/keyguard_host_view.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<!-- This is the host view that generally contains two sub views: the widget view
+ and the security view. -->
+<com.android.internal.policy.impl.keyguard.KeyguardHostView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_host_view"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:clipChildren="false">
+
+ <ViewFlipper
+ android:id="@+id/view_flipper"
+ android:layout_height="match_parent"
+ android:gravity="center">
+
+ <include layout="@layout/keyguard_selector_view"/>
+ <include layout="@layout/keyguard_account_view"/>
+ <include layout="@layout/keyguard_pattern_view"/>
+ <include layout="@layout/keyguard_password_view"/>
+ <include layout="@layout/keyguard_sim_pin_view"/>
+ <include layout="@layout/keyguard_sim_puk_view"/>
+ <include layout="@layout/keyguard_face_unlock_view"/>
+
+ </ViewFlipper>
+
+</com.android.internal.policy.impl.keyguard.KeyguardHostView>
+
diff --git a/core/res/res/layout-sw600dp-land/keyguard_host_view.xml b/core/res/res/layout-sw600dp-land/keyguard_host_view.xml
new file mode 100644
index 0000000..c552885
--- /dev/null
+++ b/core/res/res/layout-sw600dp-land/keyguard_host_view.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<!-- This is the host view that generally contains two sub views: the widget view
+ and the security view. -->
+<com.android.internal.policy.impl.keyguard.KeyguardHostView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_host_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <com.android.internal.policy.impl.keyguard.KeyguardWidgetView
+ android:id="@+id/app_widget_container"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:visibility="gone">
+
+ <!-- TODO: Remove this once supported as a widget -->
+ <include layout="@layout/keyguard_status_view"/>
+
+ </com.android.internal.policy.impl.keyguard.KeyguardWidgetView>
+
+ <FrameLayout
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center">
+
+ <ViewFlipper
+ android:id="@+id/view_flipper"
+ android:layout_width="@dimen/kg_security_view_width"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:layout_weight="1"
+ android:gravity="center">
+
+ <include layout="@layout/keyguard_selector_view"/>
+ <include layout="@layout/keyguard_account_view"/>
+ <include layout="@layout/keyguard_pattern_view"/>
+ <include layout="@layout/keyguard_password_view"/>
+ <include layout="@layout/keyguard_sim_pin_view"/>
+ <include layout="@layout/keyguard_sim_puk_view"/>
+ <include layout="@layout/keyguard_face_unlock_view"/>
+
+ </ViewFlipper>
+
+ </FrameLayout>
+
+</com.android.internal.policy.impl.keyguard.KeyguardHostView>
diff --git a/core/res/res/layout-sw600dp-port/keyguard_host_view.xml b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
new file mode 100644
index 0000000..675b69b
--- /dev/null
+++ b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<!-- This is the host view that generally contains two sub views: the widget view
+ and the security view. -->
+<com.android.internal.policy.impl.keyguard.KeyguardHostView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_host_view"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:clipChildren="false">
+
+ <com.android.internal.policy.impl.keyguard.KeyguardWidgetView
+ android:id="@+id/app_widget_container"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:visibility="gone">
+
+ <!-- TODO: Remove this once supported as a widget -->
+ <include layout="@layout/keyguard_status_view"/>
+
+ </com.android.internal.policy.impl.keyguard.KeyguardWidgetView>
+
+ <ViewFlipper
+ android:id="@+id/view_flipper"
+ android:layout_width="@dimen/kg_security_view_width"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:layout_gravity="center">
+
+ <include layout="@layout/keyguard_selector_view"/>
+ <include layout="@layout/keyguard_account_view"/>
+ <include layout="@layout/keyguard_pattern_view"/>
+ <include layout="@layout/keyguard_password_view"/>
+ <include layout="@layout/keyguard_sim_pin_view"/>
+ <include layout="@layout/keyguard_sim_puk_view"/>
+ <include layout="@layout/keyguard_face_unlock_view"/>
+
+ </ViewFlipper>
+
+</com.android.internal.policy.impl.keyguard.KeyguardHostView>
+
diff --git a/core/res/res/layout/keyguard_account_view.xml b/core/res/res/layout/keyguard_account_view.xml
new file mode 100644
index 0000000..481f0c1
--- /dev/null
+++ b/core/res/res/layout/keyguard_account_view.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<com.android.internal.policy.impl.keyguard.KeyguardAccountView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_account_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <include layout="@layout/keyguard_navigation"/>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1">
+
+ <EditText
+ android:id="@+id/login"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="7dip"
+ android:layout_marginEnd="7dip"
+ android:layout_alignParentTop="true"
+ android:hint="@string/kg_login_username_hint"
+ android:inputType="textEmailAddress"
+ />
+
+ <EditText
+ android:id="@+id/password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/login"
+ android:layout_marginTop="15dip"
+ android:layout_marginStart="7dip"
+ android:layout_marginEnd="7dip"
+ android:inputType="textPassword"
+ android:hint="@string/kg_login_password_hint"
+ android:nextFocusRight="@+id/ok"
+ android:nextFocusDown="@+id/ok"
+ />
+
+ <!-- ok below password, aligned to right of screen -->
+ <Button
+ android:id="@+id/ok"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="7dip"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentBottom="true"
+ android:text="@string/kg_login_submit_button"
+ />
+
+ </RelativeLayout>
+
+</com.android.internal.policy.impl.keyguard.KeyguardAccountView>
diff --git a/core/res/res/layout/keyguard_face_unlock_view.xml b/core/res/res/layout/keyguard_face_unlock_view.xml
new file mode 100644
index 0000000..572c013
--- /dev/null
+++ b/core/res/res/layout/keyguard_face_unlock_view.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<!-- This is the screen that shows the 9 circle unlock widget and instructs
+ the user how to unlock their device, or make an emergency call. This
+ is the portrait layout. -->
+<com.android.internal.policy.impl.keyguard.KeyguardFaceUnlockView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_face_unlock_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- TODO -->
+
+</com.android.internal.policy.impl.keyguard.KeyguardFaceUnlockView>
diff --git a/core/res/res/layout/keyguard_navigation.xml b/core/res/res/layout/keyguard_navigation.xml
new file mode 100644
index 0000000..569f93d
--- /dev/null
+++ b/core/res/res/layout/keyguard_navigation.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="left">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView
+ android:id="@+id/back"
+ android:layout_width="20dip"
+ android:layout_height="wrap_content"
+ android:textSize="28dp"
+ android:text="@string/kg_temp_back_string" />
+
+ <!-- message area for security screen -->
+ <TextView
+ android:id="@+id/message_area"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:layout_marginEnd="6dip"
+ android:layout_marginStart="6dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"/>
+
+ </LinearLayout>
+
+ <!-- This is currently only uses for pattern unlock -->
+ <Button android:id="@+id/forgot_password_button"
+ android:layout_gravity="right"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size"
+ android:drawableLeft="@*android:drawable/lockscreen_forgot_password_button"
+ android:drawablePadding="0dip"
+ android:visibility="gone"/>
+
+</LinearLayout>
diff --git a/core/res/res/layout/keyguard_password_view.xml b/core/res/res/layout/keyguard_password_view.xml
new file mode 100644
index 0000000..b9a70e5
--- /dev/null
+++ b/core/res/res/layout/keyguard_password_view.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<com.android.internal.policy.impl.keyguard.KeyguardPasswordView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_password_view"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal">
+
+ <include layout="@layout/keyguard_navigation"/>
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"/>
+
+ <!-- Password entry field -->
+ <!-- Note: the entire container is styled to look like the edit field,
+ since the backspace/IME switcher looks better inside -->
+ <LinearLayout
+ android:layout_gravity="center_vertical|fill_horizontal"
+ android:layout_width="match_parent"
+ android:orientation="horizontal"
+ android:background="@*android:drawable/lockscreen_password_field_dark"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip">
+
+ <EditText android:id="@+id/passwordEntry"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="center_horizontal"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="@*android:dimen/keyguard_lockscreen_pin_margin_left"
+ android:singleLine="true"
+ android:textStyle="normal"
+ android:inputType="textPassword"
+ android:textSize="36sp"
+ android:background="@null"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="#ffffffff"
+ android:imeOptions="flagForceAscii|actionDone"
+ />
+
+ <!-- This delete button is only visible for numeric PIN entry -->
+ <ImageButton android:id="@+id/delete_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@*android:drawable/ic_input_delete"
+ android:clickable="true"
+ android:padding="8dip"
+ android:layout_gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:visibility="gone"
+ />
+
+ <ImageView android:id="@+id/switch_ime_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@*android:drawable/ic_lockscreen_ime"
+ android:clickable="true"
+ android:padding="8dip"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackground"
+ android:visibility="gone"
+ />
+
+ </LinearLayout>
+
+ <!-- Numeric keyboard -->
+ <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
+ android:layout_width="match_parent"
+ android:layout_marginStart="4dip"
+ android:layout_marginEnd="4dip"
+ android:paddingTop="4dip"
+ android:paddingBottom="4dip"
+ android:background="#40000000"
+ android:keyBackground="@*android:drawable/btn_keyboard_key_ics"
+ android:visibility="gone"
+ android:clickable="true"
+ />
+
+</com.android.internal.policy.impl.keyguard.KeyguardPasswordView>
diff --git a/core/res/res/layout/keyguard_pattern_view.xml b/core/res/res/layout/keyguard_pattern_view.xml
new file mode 100644
index 0000000..9cba609
--- /dev/null
+++ b/core/res/res/layout/keyguard_pattern_view.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<!-- This is the screen that shows the 9 circle unlock widget and instructs
+ the user how to unlock their device, or make an emergency call. This
+ is the portrait layout. -->
+<com.android.internal.policy.impl.keyguard.KeyguardPatternView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_pattern_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <GridLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal">
+
+ <include layout="@layout/keyguard_navigation"/>
+
+ <Space android:layout_gravity="fill" />
+
+ <!-- We need MATCH_PARENT here only to force the size of the parent to be passed to
+ the pattern view for it to compute its size. This is an unusual case, caused by
+ LockPatternView's requirement to maintain a square aspect ratio based on the width
+ of the screen. -->
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPatternView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="8dip"
+ android:layout_marginBottom="4dip"
+ android:layout_marginStart="8dip"
+ android:layout_gravity="center_horizontal"
+ />
+
+ </GridLayout>
+
+</com.android.internal.policy.impl.keyguard.KeyguardPatternView>
diff --git a/core/res/res/layout/keyguard_selector_view.xml b/core/res/res/layout/keyguard_selector_view.xml
new file mode 100644
index 0000000..6f6611d
--- /dev/null
+++ b/core/res/res/layout/keyguard_selector_view.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<!-- This is the selector widget that allows the user to select an action. -->
+<com.android.internal.policy.impl.keyguard.KeyguardSelectorView
+ xmlns:prvandroid="http://schemas.android.com/apk/prv/res/android"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_selector_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <com.android.internal.policy.impl.keyguard.KeyguardWidgetView
+ android:id="@+id/app_widget_container"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:visibility="gone">
+ <!-- TODO: Remove this once supported as a widget -->
+ <include layout="@layout/keyguard_status_view"/>
+ </com.android.internal.policy.impl.keyguard.KeyguardWidgetView>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:layout_gravity="center"
+ android:gravity="center">
+
+ <com.android.internal.widget.multiwaveview.GlowPadView
+ android:id="@+id/glow_pad_view"
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+
+ prvandroid:targetDrawables="@*android:array/lockscreen_targets_with_camera"
+ prvandroid:targetDescriptions="@*android:array/lockscreen_target_descriptions_with_camera"
+ prvandroid:directionDescriptions="@*android:array/lockscreen_direction_descriptions"
+ prvandroid:handleDrawable="@*android:drawable/ic_lockscreen_handle"
+ prvandroid:outerRingDrawable="@*android:drawable/ic_lockscreen_outerring"
+ prvandroid:outerRadius="@*android:dimen/glowpadview_target_placement_radius"
+ prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius"
+ prvandroid:snapMargin="@*android:dimen/glowpadview_snap_margin"
+ prvandroid:feedbackCount="1"
+ prvandroid:vibrationDuration="20"
+ prvandroid:glowRadius="@*android:dimen/glowpadview_glow_radius"
+ prvandroid:pointDrawable="@*android:drawable/ic_lockscreen_glowdot"/>
+
+ <Button
+ android:id="@+id/emergency_call_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:drawableLeft="@*android:drawable/lockscreen_emergency_button"
+ android:text="@string/kg_emergency_call_label"
+ style="?android:attr/buttonBarButtonStyle"
+ android:drawablePadding="8dip"
+ android:layout_alignRight="@id/glow_pad_view"
+ android:layout_alignTop="@id/glow_pad_view"
+ />
+
+ </RelativeLayout>
+
+</com.android.internal.policy.impl.keyguard.KeyguardSelectorView>
+
diff --git a/core/res/res/layout/keyguard_sim_pin_view.xml b/core/res/res/layout/keyguard_sim_pin_view.xml
new file mode 100644
index 0000000..122484a
--- /dev/null
+++ b/core/res/res/layout/keyguard_sim_pin_view.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<!-- This is the SIM PIN view that allows the user to enter a SIM PIN to unlock the device. -->
+<com.android.internal.policy.impl.keyguard.KeyguardSimPinView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_sim_pin_view"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal">
+
+ <include layout="@layout/keyguard_navigation"/>
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"/>
+
+ <!-- Password entry field -->
+ <!-- Note: the entire container is styled to look like the edit field,
+ since the backspace/IME switcher looks better inside -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginEnd="6dip"
+ android:layout_marginStart="6dip"
+ android:gravity="center_vertical"
+ android:background="@android:drawable/edit_text">
+
+ <!-- displays dots as user enters pin -->
+ <EditText android:id="@+id/sim_pin_entry"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:maxLines="1"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceLargeInverse"
+ android:textColor="@*android:color/primary_text_holo_light"
+ android:textStyle="bold"
+ android:inputType="textPassword"
+ android:imeOptions="flagForceAscii|actionDone"
+ />
+
+ <ImageButton android:id="@+id/delete_button"
+ android:src="@android:drawable/ic_input_delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="-3dip"
+ android:layout_marginBottom="-3dip"
+ />
+ </LinearLayout>
+
+ <!-- Numeric keyboard -->
+ <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
+ android:layout_width="match_parent"
+ android:layout_marginStart="4dip"
+ android:layout_marginEnd="4dip"
+ android:paddingTop="4dip"
+ android:paddingBottom="4dip"
+ android:background="#80ffffff"
+ android:keyBackground="@*android:drawable/btn_keyboard_key_ics"
+ android:clickable="true"
+ />
+
+</com.android.internal.policy.impl.keyguard.KeyguardSimPinView>
diff --git a/core/res/res/layout/keyguard_sim_puk_view.xml b/core/res/res/layout/keyguard_sim_puk_view.xml
new file mode 100644
index 0000000..8bb76c1
--- /dev/null
+++ b/core/res/res/layout/keyguard_sim_puk_view.xml
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<com.android.internal.policy.impl.keyguard.KeyguardSimPukView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyguard_sim_puk_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center_horizontal">
+
+ <include layout="@layout/keyguard_navigation"/>
+
+ <Space
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"/>
+
+ <LinearLayout android:id="@+id/topDisplayGroup"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:paddingEnd="0dip"
+ android:layout_marginEnd="10dip"
+ android:layout_marginStart="10dip">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginEnd="6dip"
+ android:layout_marginStart="6dip"
+ android:gravity="center_vertical"
+ android:background="@*android:drawable/edit_text">
+
+ <!-- displays dots as user enters puk -->
+ <EditText android:id="@+id/sim_puk_entry"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:maxLines="1"
+ android:textStyle="bold"
+ android:inputType="textPassword"
+ android:textColor="#000"
+ android:hint="@string/kg_puk_enter_puk_hint"
+ />
+
+ <ImageButton android:id="@+id/puk_delete_button"
+ android:src="@*android:drawable/ic_input_delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="-3dip"
+ android:layout_marginBottom="-3dip"
+ />
+
+ </LinearLayout>
+
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginEnd="6dip"
+ android:layout_marginStart="6dip"
+ android:gravity="center_vertical"
+ android:background="@*android:drawable/edit_text">
+
+ <!-- displays dots as user enters new pin -->
+ <EditText android:id="@+id/sim_pin_entry"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:maxLines="1"
+ android:textStyle="bold"
+ android:inputType="textPassword"
+ android:textColor="#000"
+ android:hint="@string/kg_puk_enter_pin_hint"
+ />
+
+ <ImageButton android:id="@+id/pin_delete_button"
+ android:src="@*android:drawable/ic_input_delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="-3dip"
+ android:layout_marginBottom="-3dip"
+ />
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <!-- Numeric keyboard -->
+ <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
+ android:layout_width="match_parent"
+ android:layout_marginStart="4dip"
+ android:layout_marginEnd="4dip"
+ android:paddingTop="4dip"
+ android:paddingBottom="4dip"
+ android:background="#80ffffff"
+ android:keyBackground="@*android:drawable/btn_keyboard_key_ics"
+ android:clickable="true"
+ />
+
+</com.android.internal.policy.impl.keyguard.KeyguardSimPukView>
diff --git a/core/res/res/layout/keyguard_status_view.xml b/core/res/res/layout/keyguard_status_view.xml
new file mode 100644
index 0000000..3560ea2
--- /dev/null
+++ b/core/res/res/layout/keyguard_status_view.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2009, 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.
+*/
+-->
+
+<!-- This is a view that shows general status information in Keyguard. -->
+<com.android.internal.policy.impl.keyguard.KeyguardStatusView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal">
+
+ <com.android.internal.widget.DigitalClock android:id="@+id/time"
+ android:layout_marginTop="@*android:dimen/keyguard_lockscreen_status_line_clockfont_top_margin"
+ android:layout_marginBottom="12dip"
+ android:layout_marginEnd="@*android:dimen/keyguard_lockscreen_status_line_font_right_margin"
+ android:layout_gravity="end">
+
+ <!-- Because we can't have multi-tone fonts, we render two TextViews, one on
+ top of the other. Hence the redundant layout... -->
+ <TextView android:id="@*android:id/timeDisplayBackground"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="none"
+ android:textSize="@*android:dimen/keyguard_lockscreen_clock_font_size"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_marginBottom="6dip"
+ android:textColor="@*android:color/lockscreen_clock_background"
+ />
+
+ <TextView android:id="@*android:id/timeDisplayForeground"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="none"
+ android:textSize="@*android:dimen/keyguard_lockscreen_clock_font_size"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_marginBottom="6dip"
+ android:textColor="@*android:color/lockscreen_clock_foreground"
+ android:layout_alignStart="@*android:id/timeDisplayBackground"
+ android:layout_alignTop="@*android:id/timeDisplayBackground"
+ />
+
+ </com.android.internal.widget.DigitalClock>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_gravity="end"
+ android:layout_marginEnd="@*android:dimen/keyguard_lockscreen_status_line_font_right_margin">
+
+ <TextView
+ android:id="@*android:id/date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size"
+ />
+
+ <TextView
+ android:id="@*android:id/alarm_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size"
+ android:drawablePadding="4dip"
+ />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@*android:id/status1"
+ android:layout_gravity="end"
+ android:layout_marginEnd="@*android:dimen/keyguard_lockscreen_status_line_font_right_margin"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size"
+ android:drawablePadding="4dip"
+ />
+
+ <TextView
+ android:id="@*android:id/carrier"
+ android:layout_gravity="end"
+ android:layout_marginEnd="@*android:dimen/keyguard_lockscreen_status_line_font_right_margin"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@*android:dimen/keyguard_lockscreen_status_line_font_size"
+ android:textColor="?android:attr/textColorSecondary"
+ />
+
+</com.android.internal.policy.impl.keyguard.KeyguardStatusView>
+
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5d8d397..209fff0 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5706,4 +5706,22 @@
<attr name="minHeight" />
</declare-styleable>
+ <!-- PagedView specific attributes. These attributes are used to customize
+ a PagedView view in XML files. -->
+ <declare-styleable name="PagedView">
+ <!-- A spacing override for the icons within a page -->
+ <attr name="pageLayoutWidthGap" format="dimension" />
+ <attr name="pageLayoutHeightGap" format="dimension" />
+ <!-- The padding of the pages that are dynamically created per page -->
+ <attr name="pageLayoutPaddingTop" format="dimension" />
+ <attr name="pageLayoutPaddingBottom" format="dimension" />
+ <attr name="pageLayoutPaddingLeft" format="dimension" />
+ <attr name="pageLayoutPaddingRight" format="dimension" />
+ <!-- The space between adjacent pages of the PagedView. -->
+ <attr name="pageSpacing" format="dimension" />
+ <!-- The padding for the scroll indicator area -->
+ <attr name="scrollIndicatorPaddingLeft" format="dimension" />
+ <attr name="scrollIndicatorPaddingRight" format="dimension" />
+ </declare-styleable>
+
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index f30943a..cc4d05a 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -237,4 +237,18 @@
<dimen name="notification_title_text_size">18dp</dimen>
<!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, Info, Time) -->
<dimen name="notification_subtext_size">12dp</dimen>
+
+ <!-- Keyguard dimensions -->
+ <!-- Width of security view in keyguard. -->
+ <dimen name="kg_security_view_width">500dp</dimen>
+
+ <!-- Height of security view in keyguard. -->
+ <dimen name="kg_security_view_height">0dp</dimen>
+
+ <!-- Width of widget view in keyguard. -->
+ <dimen name="kg_widget_view_width">0dp</dimen>
+
+ <!-- Height of widget view in keyguard. -->
+ <dimen name="kg_widget_view_height">0dp</dimen>
+
</resources>
diff --git a/core/res/res/values/integers.xml b/core/res/res/values/integers.xml
new file mode 100644
index 0000000..603fd7e
--- /dev/null
+++ b/core/res/res/values/integers.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<resources>
+ <integer name="flip_duration">300</integer>
+</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index d761980..1a75f69 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1203,6 +1203,8 @@
<java-symbol type="anim" name="dock_left_exit" />
<java-symbol type="anim" name="dock_right_enter" />
<java-symbol type="anim" name="dock_right_exit" />
+ <java-symbol type="anim" name="keyguard_security_animate_in" />
+ <java-symbol type="anim" name="keyguard_security_animate_out" />
<java-symbol type="array" name="config_keyboardTapVibePattern" />
<java-symbol type="array" name="config_longPressVibePattern" />
<java-symbol type="array" name="config_safeModeDisabledVibePattern" />
@@ -1299,6 +1301,28 @@
<java-symbol type="id" name="two" />
<java-symbol type="id" name="unlock_widget" />
<java-symbol type="id" name="zero" />
+ <java-symbol type="id" name="message_area" />
+ <java-symbol type="id" name="keyguard_selector_view" />
+ <java-symbol type="id" name="keyguard_pattern_view" />
+ <java-symbol type="id" name="keyguard_password_view" />
+ <java-symbol type="id" name="keyguard_face_unlock_view" />
+ <java-symbol type="id" name="keyguard_sim_pin_view" />
+ <java-symbol type="id" name="keyguard_sim_puk_view" />
+ <java-symbol type="id" name="keyguard_account_view" />
+ <java-symbol type="id" name="app_widget_container" />
+ <java-symbol type="id" name="view_flipper" />
+ <java-symbol type="id" name="emergency_call_button" />
+ <java-symbol type="id" name="keyguard_host_view" />
+ <java-symbol type="id" name="delete_button" />
+ <java-symbol type="id" name="lockPatternView" />
+ <java-symbol type="id" name="forgot_password_button" />
+ <java-symbol type="id" name="glow_pad_view" />
+ <java-symbol type="id" name="sim_pin_entry" />
+ <java-symbol type="id" name="delete_button" />
+ <java-symbol type="id" name="sim_puk_entry" />
+ <java-symbol type="id" name="sim_pin_entry" />
+ <java-symbol type="id" name="puk_delete_button" />
+ <java-symbol type="id" name="pin_delete_button" />
<java-symbol type="integer" name="config_carDockRotation" />
<java-symbol type="integer" name="config_defaultUiModeType" />
<java-symbol type="integer" name="config_deskDockRotation" />
@@ -1328,6 +1352,7 @@
<java-symbol type="layout" name="screen_simple_overlay_action_mode" />
<java-symbol type="layout" name="screen_title" />
<java-symbol type="layout" name="screen_title_icons" />
+ <java-symbol type="layout" name="keyguard_host_view" />
<java-symbol type="string" name="abbrev_wday_month_day_no_year" />
<java-symbol type="string" name="android_upgrading_title" />
<java-symbol type="string" name="bugreport_title" />
@@ -1381,6 +1406,33 @@
<java-symbol type="style" name="Animation.LockScreen" />
<java-symbol type="style" name="Theme.Dialog.RecentApplications" />
<java-symbol type="style" name="Theme.ExpandedMenu" />
+ <java-symbol type="string" name="kg_emergency_call_label" />
+ <java-symbol type="string" name="kg_forgot_pattern_button_text" />
+ <java-symbol type="string" name="kg_wrong_pattern" />
+ <java-symbol type="string" name="kg_wrong_password" />
+ <java-symbol type="string" name="kg_wrong_pin" />
+ <java-symbol type="string" name="kg_too_many_failed_attempts_countdown" />
+ <java-symbol type="string" name="kg_pattern_instructions" />
+ <java-symbol type="string" name="kg_sim_pin_instructions" />
+ <java-symbol type="string" name="kg_pin_instructions" />
+ <java-symbol type="string" name="kg_password_instructions" />
+ <java-symbol type="string" name="kg_puk_enter_puk_hint" />
+ <java-symbol type="string" name="kg_puk_enter_pin_hint" />
+ <java-symbol type="string" name="kg_sim_unlock_progress_dialog_message" />
+ <java-symbol type="string" name="kg_password_wrong_pin_code" />
+ <java-symbol type="string" name="kg_invalid_sim_pin_hint" />
+ <java-symbol type="string" name="kg_invalid_sim_puk_hint" />
+ <java-symbol type="string" name="kg_sim_puk_recovery_hint" />
+ <java-symbol type="string" name="kg_invalid_puk" />
+ <java-symbol type="string" name="kg_login_too_many_attempts" />
+ <java-symbol type="string" name="kg_login_instructions" />
+ <java-symbol type="string" name="kg_login_username_hint" />
+ <java-symbol type="string" name="kg_login_password_hint" />
+ <java-symbol type="string" name="kg_login_submit_button" />
+ <java-symbol type="string" name="kg_login_invalid_input" />
+ <java-symbol type="string" name="kg_login_account_recovery_hint" />
+ <java-symbol type="string" name="kg_login_checking_password" />
+
<!-- From services -->
<java-symbol type="anim" name="screen_rotate_0_enter" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e77dde7..b2c4302 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3653,4 +3653,34 @@
<!-- Name of the built-in display. [CHAR LIMIT=50] -->
<string name="display_manager_built_in_display">Built-in Screen</string>
+ <!-- Keyguard strings -->
+ <string name="kg_emergency_call_label">Emergency call</string>
+ <string name="kg_forgot_pattern_button_text">Forgot Pattern</string>
+ <string name="kg_wrong_pattern">Wrong Pattern</string>
+ <string name="kg_wrong_password">Wrong Password</string>
+ <string name="kg_wrong_pin">Wrong PIN</string>
+ <string name="kg_too_many_failed_attempts_countdown">Too many attempts</string>
+ <string name="kg_pattern_instructions">Draw your pattern</string>
+ <string name="kg_sim_pin_instructions">Enter SIM PIN</string>
+ <string name="kg_pin_instructions">Enter PIN</string>
+ <string name="kg_password_instructions">Enter Password</string>
+ <string name="kg_puk_enter_puk_hint">PUK code</string>
+ <string name="kg_puk_enter_pin_hint">New PIN code</string>
+ <string name="kg_sim_unlock_progress_dialog_message">Unlocking SIM card\u2026</string>
+ <string name="kg_password_wrong_pin_code">Incorrect PIN code.</string>
+ <string name="kg_invalid_sim_pin_hint">Type a PIN that is 4 to 8 numbers.</string>
+ <string name="kg_invalid_sim_puk_hint">Type a PUK that is 8 numbers or longer.</string>
+ <string name="kg_sim_puk_recovery_hint">Type PUK and new PIN code</string>
+ <string name="kg_invalid_puk">The PUK you typed isn\'t correct.</string>
+
+ <string name="kg_login_too_many_attempts">Too many pattern attempts</string>
+ <string name="kg_login_instructions">To unlock, sign in with your Google account.</string>
+ <string name="kg_login_username_hint">Username (email)</string>
+ <string name="kg_login_password_hint">Password</string>
+ <string name="kg_login_submit_button">Sign in</string>
+ <string name="kg_login_invalid_input">Invalid username or password.</string>
+ <string name="kg_login_account_recovery_hint">Forgot your username or password\?\nVisit <b>google.com/accounts/recovery</b>.</string>
+ <string name="kg_login_checking_password">Checking\u2026</string>
+ <string name="kg_temp_back_string"> &lt; </string> <!-- TODO: remove this -->
+
</resources>
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 209ad38..ff7b17f 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -61,6 +61,8 @@ import android.provider.Settings;
import com.android.internal.R;
import com.android.internal.policy.PolicyManager;
+import com.android.internal.policy.impl.keyguard_obsolete.KeyguardViewManager;
+import com.android.internal.policy.impl.keyguard_obsolete.KeyguardViewMediator;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.telephony.ITelephony;
import com.android.internal.widget.PointerLocationView;
diff --git a/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard/BiometricSensorUnlock.java
index f476f82..39afaa2 100644
--- a/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/BiometricSensorUnlock.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard;
import android.view.View;
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java
new file mode 100644
index 0000000..31d138b
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java
@@ -0,0 +1,542 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import com.android.internal.policy.IFaceLockCallback;
+import com.android.internal.policy.IFaceLockInterface;
+import com.android.internal.widget.LockPatternUtils;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+
+public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "FULLockscreen";
+
+ private final Context mContext;
+ private final LockPatternUtils mLockPatternUtils;
+
+ // TODO: is mServiceRunning needed or can we just use mIsRunning or check if mService is null?
+ private boolean mServiceRunning = false;
+ // TODO: now that the code has been restructure to do almost all operations from a handler, this
+ // lock may no longer be necessary.
+ private final Object mServiceRunningLock = new Object();
+ private IFaceLockInterface mService;
+ private boolean mBoundToService = false;
+ private View mFaceUnlockView;
+
+ private Handler mHandler;
+ private final int MSG_SHOW_FACE_UNLOCK_VIEW = 0;
+ private final int MSG_HIDE_FACE_UNLOCK_VIEW = 1;
+ private final int MSG_SERVICE_CONNECTED = 2;
+ private final int MSG_SERVICE_DISCONNECTED = 3;
+ private final int MSG_UNLOCK = 4;
+ private final int MSG_CANCEL = 5;
+ private final int MSG_REPORT_FAILED_ATTEMPT = 6;
+ private final int MSG_EXPOSE_FALLBACK = 7;
+ private final int MSG_POKE_WAKELOCK = 8;
+
+ // TODO: This was added for the purpose of adhering to what the biometric interface expects
+ // the isRunning() function to return. However, it is probably not necessary to have both
+ // mRunning and mServiceRunning. I'd just rather wait to change that logic.
+ private volatile boolean mIsRunning = false;
+
+ // Long enough to stay visible while the service starts
+ // Short enough to not have to wait long for backup if service fails to start or crashes
+ // The service can take a couple of seconds to start on the first try after boot
+ private final int SERVICE_STARTUP_VIEW_TIMEOUT = 3000;
+
+ // So the user has a consistent amount of time when brought to the backup method from Face
+ // Unlock
+ private final int BACKUP_LOCK_TIMEOUT = 5000;
+
+ KeyguardSecurityCallback mKeyguardScreenCallback;
+
+ /**
+ * Stores some of the structures that Face Unlock will need to access and creates the handler
+ * will be used to execute messages on the UI thread.
+ */
+ public FaceUnlock(Context context, KeyguardSecurityCallback keyguardScreenCallback) {
+ mContext = context;
+ mLockPatternUtils = new LockPatternUtils(context);
+ mKeyguardScreenCallback = keyguardScreenCallback;
+ mHandler = new Handler(this);
+ }
+
+ /**
+ * Stores and displays the view that Face Unlock is allowed to draw within.
+ * TODO: since the layout object will eventually be shared by multiple biometric unlock
+ * methods, we will have to add our other views (background, cancel button) here.
+ */
+ public void initializeView(View biometricUnlockView) {
+ Log.d(TAG, "initializeView()");
+ mFaceUnlockView = biometricUnlockView;
+ }
+
+ /**
+ * Indicates whether Face Unlock is currently running.
+ */
+ public boolean isRunning() {
+ return mIsRunning;
+ }
+
+ /**
+ * Sets the Face Unlock view to visible, hiding it after the specified amount of time. If
+ * timeoutMillis is 0, no hide is performed. Called on the UI thread.
+ */
+ public void show(long timeoutMillis) {
+ if (DEBUG) Log.d(TAG, "show()");
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ Log.e(TAG, "show() called off of the UI thread");
+ }
+
+ removeDisplayMessages();
+ if (mFaceUnlockView != null) {
+ mFaceUnlockView.setVisibility(View.VISIBLE);
+ }
+ if (timeoutMillis > 0) {
+ mHandler.sendEmptyMessageDelayed(MSG_HIDE_FACE_UNLOCK_VIEW, timeoutMillis);
+ }
+ }
+
+ /**
+ * Hides the Face Unlock view.
+ */
+ public void hide() {
+ if (DEBUG) Log.d(TAG, "hide()");
+ // Remove messages to prevent a delayed show message from undo-ing the hide
+ removeDisplayMessages();
+ mHandler.sendEmptyMessage(MSG_HIDE_FACE_UNLOCK_VIEW);
+ }
+
+ /**
+ * Binds to the Face Unlock service. Face Unlock will be started when the bind completes. The
+ * Face Unlock view is displayed to hide the backup lock while the service is starting up.
+ * Called on the UI thread.
+ */
+ public boolean start() {
+ if (DEBUG) Log.d(TAG, "start()");
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ Log.e(TAG, "start() called off of the UI thread");
+ }
+
+ if (mIsRunning) {
+ Log.w(TAG, "start() called when already running");
+ }
+
+ // Show Face Unlock view, but only for a little bit so lockpattern will become visible if
+ // Face Unlock fails to start or crashes
+ // This must show before bind to guarantee that Face Unlock has a place to display
+ show(SERVICE_STARTUP_VIEW_TIMEOUT);
+ if (!mBoundToService) {
+ Log.d(TAG, "Binding to Face Unlock service");
+ mContext.bindService(new Intent(IFaceLockInterface.class.getName()),
+ mConnection,
+ Context.BIND_AUTO_CREATE,
+ mLockPatternUtils.getCurrentUser());
+ mBoundToService = true;
+ } else {
+ Log.w(TAG, "Attempt to bind to Face Unlock when already bound");
+ }
+
+ mIsRunning = true;
+ return true;
+ }
+
+ /**
+ * Stops Face Unlock and unbinds from the service. Called on the UI thread.
+ */
+ public boolean stop() {
+ if (DEBUG) Log.d(TAG, "stop()");
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ Log.e(TAG, "stop() called off of the UI thread");
+ }
+
+ boolean mWasRunning = mIsRunning;
+ stopUi();
+
+ if (mBoundToService) {
+ if (mService != null) {
+ try {
+ mService.unregisterCallback(mFaceUnlockCallback);
+ } catch (RemoteException e) {
+ // Not much we can do
+ }
+ }
+ Log.d(TAG, "Unbinding from Face Unlock service");
+ mContext.unbindService(mConnection);
+ mBoundToService = false;
+ } else {
+ // This is usually not an error when this happens. Sometimes we will tell it to
+ // unbind multiple times because it's called from both onWindowFocusChanged and
+ // onDetachedFromWindow.
+ if (DEBUG) Log.d(TAG, "Attempt to unbind from Face Unlock when not bound");
+ }
+ mIsRunning = false;
+ return mWasRunning;
+ }
+
+ /**
+ * Frees up resources used by Face Unlock and stops it if it is still running.
+ */
+ public void cleanUp() {
+ if (DEBUG) Log.d(TAG, "cleanUp()");
+ if (mService != null) {
+ try {
+ mService.unregisterCallback(mFaceUnlockCallback);
+ } catch (RemoteException e) {
+ // Not much we can do
+ }
+ stopUi();
+ mService = null;
+ }
+ }
+
+ /**
+ * Returns the Device Policy Manager quality for Face Unlock, which is BIOMETRIC_WEAK.
+ */
+ public int getQuality() {
+ return DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
+ }
+
+ /**
+ * Handles messages such that everything happens on the UI thread in a deterministic order.
+ * Calls from the Face Unlock service come from binder threads. Calls from lockscreen typically
+ * come from the UI thread. This makes sure there are no race conditions between those calls.
+ */
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SHOW_FACE_UNLOCK_VIEW:
+ handleShowFaceUnlockView();
+ break;
+ case MSG_HIDE_FACE_UNLOCK_VIEW:
+ handleHideFaceUnlockView();
+ break;
+ case MSG_SERVICE_CONNECTED:
+ handleServiceConnected();
+ break;
+ case MSG_SERVICE_DISCONNECTED:
+ handleServiceDisconnected();
+ break;
+ case MSG_UNLOCK:
+ handleUnlock();
+ break;
+ case MSG_CANCEL:
+ handleCancel();
+ break;
+ case MSG_REPORT_FAILED_ATTEMPT:
+ handleReportFailedAttempt();
+ break;
+ case MSG_EXPOSE_FALLBACK:
+ handleExposeFallback();
+ break;
+ case MSG_POKE_WAKELOCK:
+ handlePokeWakelock(msg.arg1);
+ break;
+ default:
+ Log.e(TAG, "Unhandled message");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Sets the Face Unlock view to visible, thus covering the backup lock.
+ */
+ void handleShowFaceUnlockView() {
+ if (DEBUG) Log.d(TAG, "handleShowFaceUnlockView()");
+ if (mFaceUnlockView != null) {
+ mFaceUnlockView.setVisibility(View.VISIBLE);
+ } else {
+ Log.e(TAG, "mFaceUnlockView is null in handleShowFaceUnlockView()");
+ }
+ }
+
+ /**
+ * Sets the Face Unlock view to invisible, thus exposing the backup lock.
+ */
+ void handleHideFaceUnlockView() {
+ if (DEBUG) Log.d(TAG, "handleHideFaceUnlockView()");
+ if (mFaceUnlockView != null) {
+ mFaceUnlockView.setVisibility(View.INVISIBLE);
+ } else {
+ Log.e(TAG, "mFaceUnlockView is null in handleHideFaceUnlockView()");
+ }
+ }
+
+ /**
+ * Tells the service to start its UI via an AIDL interface. Called when the
+ * onServiceConnected() callback is received.
+ */
+ void handleServiceConnected() {
+ Log.d(TAG, "handleServiceConnected()");
+
+ // It is possible that an unbind has occurred in the time between the bind and when this
+ // function is reached. If an unbind has already occurred, proceeding on to call startUi()
+ // can result in a fatal error. Note that the onServiceConnected() callback is
+ // asynchronous, so this possibility would still exist if we executed this directly in
+ // onServiceConnected() rather than using a handler.
+ if (!mBoundToService) {
+ Log.d(TAG, "Dropping startUi() in handleServiceConnected() because no longer bound");
+ return;
+ }
+
+ try {
+ mService.registerCallback(mFaceUnlockCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Caught exception connecting to Face Unlock: " + e.toString());
+ mService = null;
+ mBoundToService = false;
+ mIsRunning = false;
+ return;
+ }
+
+ if (mFaceUnlockView != null) {
+ IBinder windowToken = mFaceUnlockView.getWindowToken();
+ if (windowToken != null) {
+ // When switching between portrait and landscape view while Face Unlock is running,
+ // the screen will eventually go dark unless we poke the wakelock when Face Unlock
+ // is restarted.
+ mKeyguardScreenCallback.userActivity(0);
+
+ int[] position;
+ position = new int[2];
+ mFaceUnlockView.getLocationInWindow(position);
+ startUi(windowToken, position[0], position[1], mFaceUnlockView.getWidth(),
+ mFaceUnlockView.getHeight());
+ } else {
+ Log.e(TAG, "windowToken is null in handleServiceConnected()");
+ }
+ }
+ }
+
+ /**
+ * Called when the onServiceDisconnected() callback is received. This should not happen during
+ * normal operation. It indicates an error has occurred.
+ */
+ void handleServiceDisconnected() {
+ Log.e(TAG, "handleServiceDisconnected()");
+ // TODO: this lock may no longer be needed now that everything is being called from a
+ // handler
+ synchronized (mServiceRunningLock) {
+ mService = null;
+ mServiceRunning = false;
+ }
+ mBoundToService = false;
+ mIsRunning = false;
+ }
+
+ /**
+ * Stops the Face Unlock service and tells the device to grant access to the user. Shows the
+ * Face Unlock view to keep the backup lock covered while the device unlocks.
+ */
+ void handleUnlock() {
+ if (DEBUG) Log.d(TAG, "handleUnlock()");
+ removeDisplayMessages();
+ if (mFaceUnlockView != null) {
+ mFaceUnlockView.setVisibility(View.VISIBLE);
+ } else {
+ Log.e(TAG, "mFaceUnlockView is null in handleUnlock()");
+ }
+ stop();
+ mKeyguardScreenCallback.reportSuccessfulUnlockAttempt();
+ mKeyguardScreenCallback.dismiss(true);
+ }
+
+ /**
+ * Stops the Face Unlock service and exposes the backup lock.
+ */
+ void handleCancel() {
+ if (DEBUG) Log.d(TAG, "handleCancel()");
+ if (mFaceUnlockView != null) {
+ mFaceUnlockView.setVisibility(View.INVISIBLE);
+ } else {
+ Log.e(TAG, "mFaceUnlockView is null in handleCancel()");
+ }
+ stop();
+ mKeyguardScreenCallback.userActivity(BACKUP_LOCK_TIMEOUT);
+ }
+
+ /**
+ * Increments the number of failed Face Unlock attempts.
+ */
+ void handleReportFailedAttempt() {
+ if (DEBUG) Log.d(TAG, "handleReportFailedAttempt()");
+ mKeyguardScreenCallback.reportFailedUnlockAttempt();
+ }
+
+ /**
+ * Hides the Face Unlock view to expose the backup lock. Called when the Face Unlock service UI
+ * is started, indicating there is no need to continue displaying the underlying view because
+ * the service UI is now covering the backup lock.
+ */
+ void handleExposeFallback() {
+ if (DEBUG) Log.d(TAG, "handleExposeFallback()");
+ if (mFaceUnlockView != null) {
+ mFaceUnlockView.setVisibility(View.INVISIBLE);
+ } else {
+ Log.e(TAG, "mFaceUnlockView is null in handleExposeFallback()");
+ }
+ }
+
+ /**
+ * Pokes the wakelock to keep the screen alive and active for a specific amount of time.
+ */
+ void handlePokeWakelock(int millis) {
+ mKeyguardScreenCallback.userActivity(millis);
+ }
+
+ /**
+ * Removes show and hide messages from the message queue. Called to prevent delayed show/hide
+ * messages from undoing a new message.
+ */
+ private void removeDisplayMessages() {
+ mHandler.removeMessages(MSG_SHOW_FACE_UNLOCK_VIEW);
+ mHandler.removeMessages(MSG_HIDE_FACE_UNLOCK_VIEW);
+ }
+
+ /**
+ * Implements service connection methods.
+ */
+ private ServiceConnection mConnection = new ServiceConnection() {
+ /**
+ * Called when the Face Unlock service connects after calling bind().
+ */
+ public void onServiceConnected(ComponentName className, IBinder iservice) {
+ Log.d(TAG, "Connected to Face Unlock service");
+ mService = IFaceLockInterface.Stub.asInterface(iservice);
+ mHandler.sendEmptyMessage(MSG_SERVICE_CONNECTED);
+ }
+
+ /**
+ * Called if the Face Unlock service unexpectedly disconnects. This indicates an error.
+ */
+ public void onServiceDisconnected(ComponentName className) {
+ Log.e(TAG, "Unexpected disconnect from Face Unlock service");
+ mHandler.sendEmptyMessage(MSG_SERVICE_DISCONNECTED);
+ }
+ };
+
+ /**
+ * Tells the Face Unlock service to start displaying its UI and start processing.
+ */
+ private void startUi(IBinder windowToken, int x, int y, int w, int h) {
+ if (DEBUG) Log.d(TAG, "startUi()");
+ synchronized (mServiceRunningLock) {
+ if (!mServiceRunning) {
+ Log.d(TAG, "Starting Face Unlock");
+ try {
+ mService.startUi(windowToken, x, y, w, h,
+ mLockPatternUtils.isBiometricWeakLivelinessEnabled());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Caught exception starting Face Unlock: " + e.toString());
+ return;
+ }
+ mServiceRunning = true;
+ } else {
+ Log.w(TAG, "startUi() attempted while running");
+ }
+ }
+ }
+
+ /**
+ * Tells the Face Unlock service to stop displaying its UI and stop processing.
+ */
+ private void stopUi() {
+ if (DEBUG) Log.d(TAG, "stopUi()");
+ // Note that attempting to stop Face Unlock when it's not running is not an issue.
+ // Face Unlock can return, which stops it and then we try to stop it when the
+ // screen is turned off. That's why we check.
+ synchronized (mServiceRunningLock) {
+ if (mServiceRunning) {
+ Log.d(TAG, "Stopping Face Unlock");
+ try {
+ mService.stopUi();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Caught exception stopping Face Unlock: " + e.toString());
+ }
+ mServiceRunning = false;
+ } else {
+ // This is usually not an error when this happens. Sometimes we will tell it to
+ // stop multiple times because it's called from both onWindowFocusChanged and
+ // onDetachedFromWindow.
+ if (DEBUG) Log.d(TAG, "stopUi() attempted while not running");
+ }
+ }
+ }
+
+ /**
+ * Implements the AIDL biometric unlock service callback interface.
+ */
+ private final IFaceLockCallback mFaceUnlockCallback = new IFaceLockCallback.Stub() {
+ /**
+ * Called when Face Unlock wants to grant access to the user.
+ */
+ public void unlock() {
+ if (DEBUG) Log.d(TAG, "unlock()");
+ mHandler.sendEmptyMessage(MSG_UNLOCK);
+ }
+
+ /**
+ * Called when Face Unlock wants to go to the backup.
+ */
+ public void cancel() {
+ if (DEBUG) Log.d(TAG, "cancel()");
+ mHandler.sendEmptyMessage(MSG_CANCEL);
+ }
+
+ /**
+ * Called when Face Unlock wants to increment the number of failed attempts.
+ */
+ public void reportFailedAttempt() {
+ if (DEBUG) Log.d(TAG, "reportFailedAttempt()");
+ mHandler.sendEmptyMessage(MSG_REPORT_FAILED_ATTEMPT);
+ }
+
+ /**
+ * Called when the Face Unlock service starts displaying the UI, indicating that the backup
+ * unlock can be exposed because the Face Unlock service is now covering the backup with its
+ * UI.
+ **/
+ public void exposeFallback() {
+ if (DEBUG) Log.d(TAG, "exposeFallback()");
+ mHandler.sendEmptyMessage(MSG_EXPOSE_FALLBACK);
+ }
+
+ /**
+ * Called when Face Unlock wants to keep the screen alive and active for a specific amount
+ * of time.
+ */
+ public void pokeWakelock(int millis) {
+ if (DEBUG) Log.d(TAG, "pokeWakelock() for " + millis + "ms");
+ Message message = mHandler.obtainMessage(MSG_POKE_WAKELOCK, millis, -1);
+ mHandler.sendMessage(message);
+ }
+
+ };
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java
new file mode 100644
index 0000000..1e73c5b
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java
@@ -0,0 +1,317 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.LoginFilter;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.R;
+
+import java.io.IOException;
+
+/**
+ * When the user forgets their password a bunch of times, we fall back on their
+ * account's login/password to unlock the phone (and reset their lock pattern).
+ */
+public class KeyguardAccountView extends LinearLayout implements KeyguardSecurityView,
+ View.OnClickListener, TextWatcher {
+ private static final int AWAKE_POKE_MILLIS = 30000;
+ private static final String LOCK_PATTERN_PACKAGE = "com.android.settings";
+ private static final String LOCK_PATTERN_CLASS = LOCK_PATTERN_PACKAGE + ".ChooseLockGeneric";
+
+ private KeyguardSecurityCallback mCallback;
+ private LockPatternUtils mLockPatternUtils;
+ private EditText mLogin;
+ private EditText mPassword;
+ private Button mOk;
+ public boolean mEnableFallback;
+ private KeyguardNavigationManager mNavigationManager;
+
+ /**
+ * Shown while making asynchronous check of password.
+ */
+ private ProgressDialog mCheckingDialog;
+
+ public KeyguardAccountView(Context context) {
+ this(context, null, 0);
+ }
+
+ public KeyguardAccountView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeyguardAccountView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mLockPatternUtils = new LockPatternUtils(getContext());
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mNavigationManager = new KeyguardNavigationManager(this);
+
+ mLogin = (EditText) findViewById(R.id.login);
+ mLogin.setFilters(new InputFilter[] { new LoginFilter.UsernameFilterGeneric() } );
+ mLogin.addTextChangedListener(this);
+
+ mPassword = (EditText) findViewById(R.id.password);
+ mPassword.addTextChangedListener(this);
+
+ mOk = (Button) findViewById(R.id.ok);
+ mOk.setOnClickListener(this);
+ reset();
+ }
+
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ }
+
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ public KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+
+ public void afterTextChanged(Editable s) {
+ }
+
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ if (mCallback != null) {
+ mCallback.userActivity(AWAKE_POKE_MILLIS);
+ }
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction,
+ Rect previouslyFocusedRect) {
+ // send focus to the login field
+ return mLogin.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ public boolean needsInput() {
+ return true;
+ }
+
+ public void reset() {
+ // start fresh
+ mLogin.setText("");
+ mPassword.setText("");
+ mLogin.requestFocus();
+ mNavigationManager.setMessage(mLockPatternUtils.isPermanentlyLocked() ?
+ R.string.kg_login_too_many_attempts : R.string.kg_login_instructions);
+ }
+
+ /** {@inheritDoc} */
+ public void cleanUp() {
+ if (mCheckingDialog != null) {
+ mCheckingDialog.hide();
+ }
+ mCallback = null;
+ mLockPatternUtils = null;
+ }
+
+ public void onClick(View v) {
+ mCallback.userActivity(0);
+ if (v == mOk) {
+ asyncCheckPassword();
+ }
+ }
+
+ private void postOnCheckPasswordResult(final boolean success) {
+ // ensure this runs on UI thread
+ mLogin.post(new Runnable() {
+ public void run() {
+ if (success) {
+ // clear out forgotten password
+ mLockPatternUtils.setPermanentlyLocked(false);
+ mLockPatternUtils.setLockPatternEnabled(false);
+ mLockPatternUtils.saveLockPattern(null);
+
+ // launch the 'choose lock pattern' activity so
+ // the user can pick a new one if they want to
+ Intent intent = new Intent();
+ intent.setClassName(LOCK_PATTERN_PACKAGE, LOCK_PATTERN_CLASS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ mCallback.reportSuccessfulUnlockAttempt();
+
+ // dismiss keyguard
+ mCallback.dismiss(true);
+ } else {
+ mNavigationManager.setMessage(R.string.kg_login_invalid_input);
+ mPassword.setText("");
+ mCallback.reportFailedUnlockAttempt();
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ if (mLockPatternUtils.isPermanentlyLocked()) {
+ mCallback.dismiss(false);
+ } else {
+ // TODO: mCallback.forgotPattern(false);
+ }
+ return true;
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ /**
+ * Given the string the user entered in the 'username' field, find
+ * the stored account that they probably intended. Prefer, in order:
+ *
+ * - an exact match for what was typed, or
+ * - a case-insensitive match for what was typed, or
+ * - if they didn't include a domain, an exact match of the username, or
+ * - if they didn't include a domain, a case-insensitive
+ * match of the username.
+ *
+ * If there is a tie for the best match, choose neither --
+ * the user needs to be more specific.
+ *
+ * @return an account name from the database, or null if we can't
+ * find a single best match.
+ */
+ private Account findIntendedAccount(String username) {
+ Account[] accounts = AccountManager.get(mContext).getAccountsByType("com.google");
+
+ // Try to figure out which account they meant if they
+ // typed only the username (and not the domain), or got
+ // the case wrong.
+
+ Account bestAccount = null;
+ int bestScore = 0;
+ for (Account a: accounts) {
+ int score = 0;
+ if (username.equals(a.name)) {
+ score = 4;
+ } else if (username.equalsIgnoreCase(a.name)) {
+ score = 3;
+ } else if (username.indexOf('@') < 0) {
+ int i = a.name.indexOf('@');
+ if (i >= 0) {
+ String aUsername = a.name.substring(0, i);
+ if (username.equals(aUsername)) {
+ score = 2;
+ } else if (username.equalsIgnoreCase(aUsername)) {
+ score = 1;
+ }
+ }
+ }
+ if (score > bestScore) {
+ bestAccount = a;
+ bestScore = score;
+ } else if (score == bestScore) {
+ bestAccount = null;
+ }
+ }
+ return bestAccount;
+ }
+
+ private void asyncCheckPassword() {
+ mCallback.userActivity(AWAKE_POKE_MILLIS);
+ final String login = mLogin.getText().toString();
+ final String password = mPassword.getText().toString();
+ Account account = findIntendedAccount(login);
+ if (account == null) {
+ postOnCheckPasswordResult(false);
+ return;
+ }
+ getProgressDialog().show();
+ Bundle options = new Bundle();
+ options.putString(AccountManager.KEY_PASSWORD, password);
+ AccountManager.get(mContext).confirmCredentials(account, options, null /* activity */,
+ new AccountManagerCallback<Bundle>() {
+ public void run(AccountManagerFuture<Bundle> future) {
+ try {
+ mCallback.userActivity(AWAKE_POKE_MILLIS);
+ final Bundle result = future.getResult();
+ final boolean verified = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+ postOnCheckPasswordResult(verified);
+ } catch (OperationCanceledException e) {
+ postOnCheckPasswordResult(false);
+ } catch (IOException e) {
+ postOnCheckPasswordResult(false);
+ } catch (AuthenticatorException e) {
+ postOnCheckPasswordResult(false);
+ } finally {
+ mLogin.post(new Runnable() {
+ public void run() {
+ getProgressDialog().hide();
+ }
+ });
+ }
+ }
+ }, null /* handler */);
+ }
+
+ private Dialog getProgressDialog() {
+ if (mCheckingDialog == null) {
+ mCheckingDialog = new ProgressDialog(mContext);
+ mCheckingDialog.setMessage(
+ mContext.getString(R.string.kg_login_checking_password));
+ mCheckingDialog.setIndeterminate(true);
+ mCheckingDialog.setCancelable(false);
+ mCheckingDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ }
+ return mCheckingDialog;
+ }
+
+ @Override
+ public void onPause() {
+
+ }
+
+ @Override
+ public void onResume() {
+ reset();
+ }
+
+}
+
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java
new file mode 100644
index 0000000..843151b
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java
@@ -0,0 +1,194 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+import com.android.internal.widget.LockPatternUtils;
+
+public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecurityView {
+
+ // Long enough to stay visible while dialer comes up
+ // Short enough to not be visible if the user goes back immediately
+ private static final int BIOMETRIC_AREA_EMERGENCY_DIALER_TIMEOUT = 1000;
+ private KeyguardSecurityCallback mKeyguardSecurityCallback;
+ private LockPatternUtils mLockPatternUtils;
+
+ public KeyguardFaceUnlockView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardFaceUnlockView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mKeyguardSecurityCallback = callback;
+ }
+
+ @Override
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ @Override
+ public void reset() {
+
+ }
+
+ @Override
+ public void onPause() {
+
+ }
+
+ @Override
+ public void onResume() {
+
+ }
+
+ @Override
+ public boolean needsInput() {
+ return false;
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ return mKeyguardSecurityCallback;
+ }
+
+ // TODO
+ // public void onRefreshBatteryInfo(BatteryStatus status) {
+ // // When someone plugs in or unplugs the device, we hide the biometric sensor area and
+ // // suppress its startup for the next onScreenTurnedOn(). Since plugging/unplugging
+ // // causes the screen to turn on, the biometric unlock would start if it wasn't
+ // // suppressed.
+ // //
+ // // However, if the biometric unlock is already running, we do not want to interrupt it.
+ // final boolean pluggedIn = status.isPluggedIn();
+ // if (mBiometricUnlock != null && mPluggedIn != pluggedIn
+ // && !mBiometricUnlock.isRunning()) {
+ // mBiometricUnlock.stop();
+ // mBiometricUnlock.hide();
+ // mSuppressBiometricUnlock = true;
+ // }
+ // mPluggedIn = pluggedIn;
+ // }
+
+ // We need to stop the biometric unlock when a phone call comes in
+ // @Override
+ // public void onPhoneStateChanged(int phoneState) {
+ // if (DEBUG) Log.d(TAG, "phone state: " + phoneState);
+ // if (phoneState == TelephonyManager.CALL_STATE_RINGING) {
+ // mSuppressBiometricUnlock = true;
+ // mBiometricUnlock.stop();
+ // mBiometricUnlock.hide();
+ // }
+ // }
+
+ // @Override
+ // public void onUserSwitched(int userId) {
+ // if (mBiometricUnlock != null) {
+ // mBiometricUnlock.stop();
+ // }
+ // mLockPatternUtils.setCurrentUser(userId);
+ // updateScreen(getInitialMode(), true);
+ // }
+
+ // /**
+ // * This returns false if there is any condition that indicates that the biometric unlock should
+ // * not be used before the next time the unlock screen is recreated. In other words, if this
+ // * returns false there is no need to even construct the biometric unlock.
+ // */
+ // private boolean useBiometricUnlock() {
+ // final ShowingMode unlockMode = getUnlockMode();
+ // final boolean backupIsTimedOut = (mUpdateMonitor.getFailedAttempts() >=
+ // LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT);
+ // return (mLockPatternUtils.usingBiometricWeak() &&
+ // mLockPatternUtils.isBiometricWeakInstalled() &&
+ // !mUpdateMonitor.getMaxBiometricUnlockAttemptsReached() &&
+ // !backupIsTimedOut &&
+ // (unlockMode == ShowingMode.Pattern || unlockMode == ShowingMode.Password));
+ // }
+
+ // private void initializeBiometricUnlockView(View view) {
+ // boolean restartBiometricUnlock = false;
+ //
+ // if (mBiometricUnlock != null) {
+ // restartBiometricUnlock = mBiometricUnlock.stop();
+ // }
+ //
+ // // Prevents biometric unlock from coming up immediately after a phone call or if there
+ // // is a dialog on top of lockscreen. It is only updated if the screen is off because if the
+ // // screen is on it's either because of an orientation change, or when it first boots.
+ // // In both those cases, we don't want to override the current value of
+ // // mSuppressBiometricUnlock and instead want to use the previous value.
+ // if (!mScreenOn) {
+ // mSuppressBiometricUnlock =
+ // mUpdateMonitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE
+ // || mHasDialog;
+ // }
+ //
+ // // If the biometric unlock is not being used, we don't bother constructing it. Then we can
+ // // simply check if it is null when deciding whether we should make calls to it.
+ // mBiometricUnlock = null;
+ // if (useBiometricUnlock()) {
+ // // TODO: make faceLockAreaView a more general biometricUnlockView
+ // // We will need to add our Face Unlock specific child views programmatically in
+ // // initializeView rather than having them in the XML files.
+ // View biometricUnlockView = view.findViewById(
+ // com.android.internal.R.id.faceLockAreaView);
+ // if (biometricUnlockView != null) {
+ // mBiometricUnlock = new FaceUnlock(mContext, mUpdateMonitor, mLockPatternUtils,
+ // mKeyguardScreenCallback);
+ // mBiometricUnlock.initializeView(biometricUnlockView);
+ //
+ // // If this is being called because the screen turned off, we want to cover the
+ // // backup lock so it is covered when the screen turns back on.
+ // if (!mScreenOn) mBiometricUnlock.show(0);
+ // } else {
+ // Log.w(TAG, "Couldn't find biometric unlock view");
+ // }
+ // }
+ //
+ // if (mBiometricUnlock != null && restartBiometricUnlock) {
+ // maybeStartBiometricUnlock();
+ // }
+ // }
+
+ // /**
+ // * Starts the biometric unlock if it should be started based on a number of factors including
+ // * the mSuppressBiometricUnlock flag. If it should not be started, it hides the biometric
+ // * unlock area.
+ // */
+ // private void maybeStartBiometricUnlock() {
+ // if (mBiometricUnlock != null) {
+ // final boolean backupIsTimedOut = (mUpdateMonitor.getFailedAttempts() >=
+ // LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT);
+ // if (!mSuppressBiometricUnlock
+ // && mUpdateMonitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE
+ // && !mUpdateMonitor.getMaxBiometricUnlockAttemptsReached()
+ // && !backupIsTimedOut) {
+ // mBiometricUnlock.start();
+ // } else {
+ // mBiometricUnlock.hide();
+ // }
+ // }
+ //}
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
new file mode 100644
index 0000000..cd9a800
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
@@ -0,0 +1,443 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.app.ActivityOptions;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.telephony.TelephonyManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.widget.Button;
+import android.widget.ViewFlipper;
+import android.widget.RemoteViews.OnClickHandler;
+
+import com.android.internal.policy.impl.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.R;
+
+import java.util.ArrayList;
+
+public class KeyguardHostView extends KeyguardViewBase {
+ // Use this to debug all of keyguard
+ public static boolean DEBUG;
+
+ static final int APPWIDGET_HOST_ID = 0x4B455947;
+ private static final String KEYGUARD_WIDGET_PREFS = "keyguard_widget_prefs";
+
+ // time after launching EmergencyDialer before the screen goes blank.
+ private static final int EMERGENCY_CALL_TIMEOUT = 10000;
+
+ // intent action for launching emergency dialer activity.
+ static final String ACTION_EMERGENCY_DIAL = "com.android.phone.EmergencyDialer.DIAL";
+
+ private static final String TAG = "KeyguardViewHost";
+
+ private static final int SECURITY_SELECTOR_ID = R.id.keyguard_selector_view;
+ private static final int SECURITY_PATTERN_ID = R.id.keyguard_pattern_view;
+ private static final int SECURITY_PASSWORD_ID = R.id.keyguard_password_view;
+ private static final int SECURITY_BIOMETRIC_ID = R.id.keyguard_face_unlock_view;
+ private static final int SECURITY_SIM_PIN_ID = R.id.keyguard_sim_pin_view;
+ private static final int SECURITY_SIM_PUK_ID = R.id.keyguard_sim_puk_view;
+ private static final int SECURITY_ACCOUNT_ID = R.id.keyguard_account_view;
+
+ private AppWidgetHost mAppWidgetHost;
+ private ViewGroup mAppWidgetContainer;
+ private ViewFlipper mViewFlipper;
+ private Button mEmergencyDialerButton;
+
+ private boolean mScreenOn;
+ private boolean mIsVerifyUnlockOnly;
+ private int mCurrentSecurityId = SECURITY_SELECTOR_ID;
+
+ // KeyguardSecurityViews
+ final private int [] mViewIds = {
+ SECURITY_SELECTOR_ID,
+ SECURITY_PATTERN_ID,
+ SECURITY_PASSWORD_ID,
+ SECURITY_BIOMETRIC_ID,
+ SECURITY_SIM_PIN_ID,
+ SECURITY_SIM_PUK_ID,
+ SECURITY_ACCOUNT_ID,
+ };
+
+ private ArrayList<View> mViews = new ArrayList<View>(mViewIds.length);
+
+ protected Runnable mLaunchRunnable;
+
+ protected int mFailedAttempts;
+ private LockPatternUtils mLockPatternUtils;
+
+ private KeyguardSecurityModel mSecurityModel;
+
+ public KeyguardHostView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardHostView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mAppWidgetHost = new AppWidgetHost(mContext, APPWIDGET_HOST_ID, mOnClickHandler);
+ mSecurityModel = new KeyguardSecurityModel(mContext);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ mCallback.keyguardDoneDrawing();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mAppWidgetContainer = (ViewGroup) findViewById(R.id.app_widget_container);
+ mAppWidgetContainer.setVisibility(VISIBLE);
+
+ // View Flipper
+ mViewFlipper = (ViewFlipper) findViewById(R.id.view_flipper);
+ mViewFlipper.setInAnimation(AnimationUtils.loadAnimation(mContext,
+ R.anim.keyguard_security_animate_in));
+ mViewFlipper.setOutAnimation(AnimationUtils.loadAnimation(mContext,
+ R.anim.keyguard_security_animate_out));
+
+ // Initialize all security views
+ for (int i = 0; i < mViewIds.length; i++) {
+ View view = findViewById(mViewIds[i]);
+ mViews.add(view);
+ if (view != null) {
+ ((KeyguardSecurityView) view).setKeyguardCallback(mCallback);
+ } else {
+ Log.v("*********", "Can't find view id " + mViewIds[i]);
+ }
+ }
+
+ // Enable emergency dialer button
+ mEmergencyDialerButton = (Button) findViewById(R.id.emergency_call_button);
+ mEmergencyDialerButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ takeEmergencyCallAction();
+ }
+ });
+ }
+
+ void setLockPatternUtils(LockPatternUtils utils) {
+ mSecurityModel.setLockPatternUtils(utils);
+ mLockPatternUtils = utils;
+ for (int i = 0; i < mViews.size(); i++) {
+ KeyguardSecurityView ksv = (KeyguardSecurityView) mViews.get(i);
+ if (ksv != null) {
+ ksv.setLockPatternUtils(utils);
+ } else {
+ Log.w(TAG, "**** ksv was null at " + i);
+ }
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mAppWidgetHost.startListening();
+ populateWidgets();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mAppWidgetHost.stopListening();
+ }
+
+ AppWidgetHost getAppWidgetHost() {
+ return mAppWidgetHost;
+ }
+
+ void addWidget(AppWidgetHostView view) {
+ mAppWidgetContainer.addView(view);
+ }
+
+ private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() {
+
+ public void userActivity(long timeout) {
+ mViewMediatorCallback.pokeWakelock(timeout);
+ }
+
+ public void dismiss(boolean authenticated) {
+ showNextSecurityScreenOrFinish(authenticated);
+ }
+
+ public boolean isVerifyUnlockOnly() {
+ // TODO
+ return false;
+ }
+
+ public void reportSuccessfulUnlockAttempt() {
+ KeyguardUpdateMonitor.getInstance(mContext).clearFailedAttempts();
+ }
+
+ public void reportFailedUnlockAttempt() {
+ // TODO: handle biometric attempt differently.
+ KeyguardUpdateMonitor.getInstance(mContext).reportFailedAttempt();
+ }
+
+ public int getFailedAttempts() {
+ return KeyguardUpdateMonitor.getInstance(mContext).getFailedAttempts();
+ }
+
+ public void showBackupUnlock() {
+ // TODO
+ }
+
+ public void keyguardDoneDrawing() {
+ mViewMediatorCallback.keyguardDoneDrawing();
+ }
+
+ };
+
+ public void takeEmergencyCallAction() {
+ mCallback.userActivity(EMERGENCY_CALL_TIMEOUT);
+ if (TelephonyManager.getDefault().getCallState()
+ == TelephonyManager.CALL_STATE_OFFHOOK) {
+ mLockPatternUtils.resumeCall();
+ } else {
+ Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ getContext().startActivity(intent);
+ }
+ }
+
+ protected void showNextSecurityScreenOrFinish(boolean authenticated) {
+ boolean finish = false;
+ if (SECURITY_SELECTOR_ID == mCurrentSecurityId) {
+ int realSecurityId = getSecurityViewIdForMode(mSecurityModel.getSecurityMode());
+ if (realSecurityId == mCurrentSecurityId) {
+ finish = true; // no security required
+ } else {
+ showSecurityScreen(realSecurityId); // switch to the "real" security view
+ }
+ } else if (authenticated) {
+ if ((mCurrentSecurityId == SECURITY_PATTERN_ID
+ || mCurrentSecurityId == SECURITY_PASSWORD_ID
+ || mCurrentSecurityId == SECURITY_ACCOUNT_ID)) {
+ finish = true;
+ }
+ } else {
+ // Not authenticated but we were asked to dismiss so go back to selector screen.
+ showSecurityScreen(SECURITY_SELECTOR_ID);
+ }
+ if (finish) {
+ // If there's a pending runnable because the user interacted with a widget
+ // and we're leaving keyguard, then run it.
+ if (mLaunchRunnable != null) {
+ mLaunchRunnable.run();
+ mViewFlipper.setDisplayedChild(0);
+ mLaunchRunnable = null;
+ }
+ mViewMediatorCallback.keyguardDone(true);
+ }
+ }
+
+ private OnClickHandler mOnClickHandler = new OnClickHandler() {
+ @Override
+ public boolean onClickHandler(final View view,
+ final android.app.PendingIntent pendingIntent,
+ final Intent fillInIntent) {
+ if (pendingIntent.isActivity()) {
+ mLaunchRunnable = new Runnable() {
+ public void run() {
+ try {
+ // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
+ Context context = view.getContext();
+ ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(view,
+ 0, 0,
+ view.getMeasuredWidth(), view.getMeasuredHeight());
+ context.startIntentSender(
+ pendingIntent.getIntentSender(), fillInIntent,
+ Intent.FLAG_ACTIVITY_NEW_TASK,
+ Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle());
+ } catch (IntentSender.SendIntentException e) {
+ android.util.Log.e(TAG, "Cannot send pending intent: ", e);
+ } catch (Exception e) {
+ android.util.Log.e(TAG, "Cannot send pending intent due to " +
+ "unknown exception: ", e);
+ }
+ }
+ };
+
+ mCallback.dismiss(false);
+ return true;
+ } else {
+ return super.onClickHandler(view, pendingIntent, fillInIntent);
+ }
+ };
+ };
+
+ @Override
+ public void reset() {
+
+ }
+
+ private KeyguardSecurityView getSecurityView(int securitySelectorId) {
+ final int children = mViewFlipper.getChildCount();
+ for (int child = 0; child < children; child++) {
+ if (mViewFlipper.getChildAt(child).getId() == securitySelectorId) {
+ return ((KeyguardSecurityView)mViewFlipper.getChildAt(child));
+ }
+ }
+ return null;
+ }
+
+ private void showSecurityScreen(int securityViewId) {
+
+ if (securityViewId == mCurrentSecurityId) return;
+
+ KeyguardSecurityView oldView = getSecurityView(mCurrentSecurityId);
+ KeyguardSecurityView newView = getSecurityView(securityViewId);
+
+ // Emulate Activity life cycle
+ oldView.onPause();
+ newView.onResume();
+
+ mViewMediatorCallback.setNeedsInput(newView.needsInput());
+ mCurrentSecurityId = securityViewId;
+
+ // Find and show this child.
+ final int childCount = mViewFlipper.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ if (securityViewId == mViewFlipper.getChildAt(i).getId()) {
+ mViewFlipper.setDisplayedChild(i);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onScreenTurnedOn() {
+ if (DEBUG) Log.d(TAG, "screen on");
+ mScreenOn = true;
+ showSecurityScreen(mCurrentSecurityId);
+ }
+
+ @Override
+ public void onScreenTurnedOff() {
+ if (DEBUG) Log.d(TAG, "screen off");
+ mScreenOn = false;
+ showSecurityScreen(SECURITY_SELECTOR_ID);
+ }
+
+ @Override
+ public void show() {
+ onScreenTurnedOn();
+ }
+
+ private boolean isSecure() {
+ SecurityMode mode = mSecurityModel.getSecurityMode();
+ switch (mode) {
+ case Pattern:
+ return mLockPatternUtils.isLockPatternEnabled();
+ case Password:
+ return mLockPatternUtils.isLockPasswordEnabled();
+ case SimPin:
+ case SimPuk:
+ case Account:
+ return true;
+ case None:
+ return false;
+ default:
+ throw new IllegalStateException("Unknown security mode " + mode);
+ }
+ }
+
+ @Override
+ public void wakeWhenReadyTq(int keyCode) {
+ if (DEBUG) Log.d(TAG, "onWakeKey");
+ if (keyCode == KeyEvent.KEYCODE_MENU && isSecure()) {
+ if (DEBUG) Log.d(TAG, "switching screens to unlock screen because wake key was MENU");
+ showSecurityScreen(SECURITY_SELECTOR_ID);
+ mViewMediatorCallback.pokeWakelock();
+ } else {
+ if (DEBUG) Log.d(TAG, "poking wake lock immediately");
+ mViewMediatorCallback.pokeWakelock();
+ }
+ }
+
+ @Override
+ public void verifyUnlock() {
+ SecurityMode securityMode = mSecurityModel.getSecurityMode();
+ if (securityMode == KeyguardSecurityModel.SecurityMode.None) {
+ mViewMediatorCallback.keyguardDone(true);
+ } else if (securityMode != KeyguardSecurityModel.SecurityMode.Pattern
+ && securityMode != KeyguardSecurityModel.SecurityMode.Password) {
+ // can only verify unlock when in pattern/password mode
+ mViewMediatorCallback.keyguardDone(false);
+ } else {
+ // otherwise, go to the unlock screen, see if they can verify it
+ mIsVerifyUnlockOnly = true;
+ showSecurityScreen(getSecurityViewIdForMode(securityMode));
+ }
+ }
+
+ private int getSecurityViewIdForMode(SecurityMode securityMode) {
+ switch (securityMode) {
+ case None: return SECURITY_SELECTOR_ID;
+ case Pattern: return SECURITY_PATTERN_ID;
+ case Password: return SECURITY_PASSWORD_ID;
+ case Biometric: return SECURITY_BIOMETRIC_ID;
+ case Account: return SECURITY_ACCOUNT_ID;
+ case SimPin: return SECURITY_SIM_PIN_ID;
+ case SimPuk: return SECURITY_SIM_PUK_ID;
+ }
+ return 0;
+ }
+
+ private void addWidget(int appId) {
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
+ AppWidgetProviderInfo appWidgetInfo = appWidgetManager.getAppWidgetInfo(appId);
+ AppWidgetHostView view = getAppWidgetHost().createView(mContext, appId, appWidgetInfo);
+ addWidget(view);
+ }
+
+ private void populateWidgets() {
+ SharedPreferences prefs = mContext.getSharedPreferences(
+ KEYGUARD_WIDGET_PREFS, Context.MODE_PRIVATE);
+ for (String key : prefs.getAll().keySet()) {
+ int appId = prefs.getInt(key, -1);
+ if (appId != -1) {
+ Log.w(TAG, "populate: adding " + key);
+ addWidget(appId);
+ } else {
+ Log.w(TAG, "populate: can't find " + key);
+ }
+ }
+ }
+
+ @Override
+ public void cleanUp() {
+
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java
new file mode 100644
index 0000000..d3feced
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardNavigationManager.java
@@ -0,0 +1,59 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+public class KeyguardNavigationManager {
+
+ private TextView mMessageArea;
+ private KeyguardSecurityView mKeyguardSecurityView;
+
+ public KeyguardNavigationManager(KeyguardSecurityView view) {
+ mKeyguardSecurityView = view;
+ mMessageArea = (TextView) ((View) view).findViewById(R.id.message_area);
+ mMessageArea.setSelected(true); // Make marquee work
+ mMessageArea.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ mKeyguardSecurityView.getCallback().dismiss(false);
+ }
+ });
+ }
+
+ public void setMessage(CharSequence msg) {
+ mMessageArea.setText(msg);
+ }
+
+ public void setMessage(int resId) {
+ if (resId != 0) {
+ mMessageArea.setText(resId);
+ } else {
+ mMessageArea.setText("");
+ }
+ }
+
+ public void setMessage(int resId, Object... formatArgs) {
+ if (resId != 0) {
+ mMessageArea.setText(mMessageArea.getContext().getString(resId, formatArgs));
+ } else {
+ mMessageArea.setText("");
+ }
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java
new file mode 100644
index 0000000..6938561
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java
@@ -0,0 +1,340 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
+import java.util.List;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import com.android.internal.widget.PasswordEntryKeyboardView;
+
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.security.KeyStore;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextWatcher;
+import android.text.method.DigitsKeyListener;
+import android.text.method.TextKeyListener;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import com.android.internal.widget.PasswordEntryKeyboardHelper;
+/**
+ * Displays a dialer-like interface or alphanumeric (latin-1) key entry for the user to enter
+ * an unlock password
+ */
+
+public class KeyguardPasswordView extends LinearLayout
+ implements KeyguardSecurityView, OnEditorActionListener {
+ private KeyguardSecurityCallback mCallback;
+ private EditText mPasswordEntry;
+ private LockPatternUtils mLockPatternUtils;
+ private PasswordEntryKeyboardView mKeyboardView;
+ private PasswordEntryKeyboardHelper mKeyboardHelper;
+ private boolean mIsAlpha;
+ private KeyguardNavigationManager mNavigationManager;
+
+ // 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.
+ private static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
+
+ public KeyguardPasswordView(Context context) {
+ super(context);
+ }
+
+ public KeyguardPasswordView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ }
+
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ public void reset() {
+ // start fresh
+ mPasswordEntry.setText("");
+ mPasswordEntry.requestFocus();
+
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
+ if (deadline != 0) {
+ handleAttemptLockout(deadline);
+ } else {
+ mNavigationManager.setMessage(
+ mIsAlpha ? R.string.kg_password_instructions : R.string.kg_pin_instructions);
+ }
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mLockPatternUtils = new LockPatternUtils(mContext); // TODO: use common one
+
+ mNavigationManager = new KeyguardNavigationManager(this);
+
+ final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality();
+ mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == quality
+ || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == quality
+ || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == quality;
+
+ mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
+ mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
+ mPasswordEntry.setOnEditorActionListener(this);
+
+ mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false);
+ mKeyboardHelper.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled());
+
+ boolean imeOrDeleteButtonVisible = false;
+ if (mIsAlpha) {
+ // We always use the system IME for alpha keyboard, so hide lockscreen's soft keyboard
+ mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA);
+ mKeyboardView.setVisibility(View.GONE);
+ } else {
+ // Use lockscreen's numeric keyboard if the physical keyboard isn't showing
+ mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
+ mKeyboardView.setVisibility(getResources().getConfiguration().hardKeyboardHidden
+ == Configuration.HARDKEYBOARDHIDDEN_NO ? View.INVISIBLE : View.VISIBLE);
+
+ // The delete button is of the PIN keyboard itself in some (e.g. tablet) layouts,
+ // not a separate view
+ View pinDelete = findViewById(R.id.delete_button);
+ if (pinDelete != null) {
+ pinDelete.setVisibility(View.VISIBLE);
+ imeOrDeleteButtonVisible = true;
+ pinDelete.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mKeyboardHelper.handleBackspace();
+ }
+ });
+ }
+ }
+
+ mPasswordEntry.requestFocus();
+
+ // This allows keyboards with overlapping qwerty/numeric keys to choose just numeric keys.
+ if (mIsAlpha) {
+ mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
+ mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
+ | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ } else {
+ mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance());
+ mPasswordEntry.setInputType(InputType.TYPE_CLASS_NUMBER
+ | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+ }
+
+ // Poke the wakelock any time the text is selected or modified
+ mPasswordEntry.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mCallback.userActivity(0); // TODO: customize timeout for text?
+ }
+ });
+
+ mPasswordEntry.addTextChangedListener(new TextWatcher() {
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ public void afterTextChanged(Editable s) {
+ mCallback.userActivity(0);
+ }
+ });
+
+ // If there's more than one IME, enable the IME switcher button
+ View switchImeButton = findViewById(R.id.switch_ime_button);
+ final InputMethodManager imm = (InputMethodManager) getContext().getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ if (mIsAlpha && switchImeButton != null && hasMultipleEnabledIMEsOrSubtypes(imm, false)) {
+ switchImeButton.setVisibility(View.VISIBLE);
+ imeOrDeleteButtonVisible = true;
+ switchImeButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mCallback.userActivity(0); // Leave the screen on a bit longer
+ imm.showInputMethodPicker();
+ }
+ });
+ }
+
+ // If no icon is visible, reset the left margin on the password field so the text is
+ // still centered.
+ if (!imeOrDeleteButtonVisible) {
+ android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams();
+ if (params instanceof MarginLayoutParams) {
+ ((MarginLayoutParams)params).leftMargin = 0;
+ mPasswordEntry.setLayoutParams(params);
+ }
+ }
+ }
+
+ /**
+ * 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
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ // send focus to the password field
+ return mPasswordEntry.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ private void verifyPasswordAndUnlock() {
+ String entry = mPasswordEntry.getText().toString();
+ boolean wrongPassword = true;
+ if (mLockPatternUtils.checkPassword(entry)) {
+ mCallback.reportSuccessfulUnlockAttempt();
+ KeyStore.getInstance().password(entry);
+ mCallback.dismiss(true);
+ wrongPassword = false;
+ } else 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.
+ mCallback.reportFailedUnlockAttempt();
+ if (0 == (mCallback.getFailedAttempts()
+ % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
+ long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
+ handleAttemptLockout(deadline);
+ }
+ }
+ mNavigationManager.setMessage(wrongPassword ?
+ (mIsAlpha ? R.string.kg_wrong_password : R.string.kg_wrong_pin) : 0);
+ mPasswordEntry.setText("");
+ }
+
+ // Prevent user from using the PIN/Password entry until scheduled deadline.
+ private void handleAttemptLockout(long elapsedRealtimeDeadline) {
+ mPasswordEntry.setEnabled(false);
+ mKeyboardView.setEnabled(false);
+ long elapsedRealtime = SystemClock.elapsedRealtime();
+ new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ int secondsRemaining = (int) (millisUntilFinished / 1000);
+ mNavigationManager.setMessage(
+ R.string.kg_too_many_failed_attempts_countdown, secondsRemaining);
+ }
+
+ @Override
+ public void onFinish() {
+ mPasswordEntry.setEnabled(true);
+ mKeyboardView.setEnabled(true);
+ }
+ }.start();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ mCallback.userActivity(0);
+ return false;
+ }
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ // Check if this was the result of hitting the enter key
+ if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT) {
+ verifyPasswordAndUnlock();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean needsInput() {
+ return mIsAlpha;
+ }
+
+ @Override
+ public void onPause() {
+
+ }
+
+ @Override
+ public void onResume() {
+ reset();
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+}
+
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java
new file mode 100644
index 0000000..a95cfcb
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java
@@ -0,0 +1,367 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.security.KeyStore;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.R;
+
+import java.io.IOException;
+import java.util.List;
+
+public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView {
+
+ private static final String TAG = "SecurityPatternView";
+ private static final boolean DEBUG = false;
+
+ // 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 long we stay awake after the user hits the first dot.
+ private static final int UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS = 2000;
+
+ // how many cells the user has to cross before we poke the wakelock
+ private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
+
+ private int mFailedPatternAttemptsSinceLastTimeout = 0;
+ private int mTotalFailedPatternAttempts = 0;
+ private CountDownTimer mCountdownTimer = null;
+ private LockPatternUtils mLockPatternUtils;
+ private LockPatternView mLockPatternView;
+ private Button mForgotPatternButton;
+ private KeyguardSecurityCallback mCallback;
+ private boolean mEnableFallback;
+ private KeyguardNavigationManager mNavigationManager;
+
+ /**
+ * 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() {
+ public void run() {
+ mLockPatternView.clearPattern();
+ }
+ };
+
+ enum FooterMode {
+ Normal,
+ ForgotLockPattern,
+ VerifyUnlocked
+ }
+
+ public KeyguardPatternView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardPatternView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ }
+
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mNavigationManager = new KeyguardNavigationManager(this);
+ mLockPatternUtils = mLockPatternUtils == null
+ ? new LockPatternUtils(mContext) : mLockPatternUtils;
+
+ mLockPatternView = (LockPatternView) findViewById(R.id.lockPatternView);
+ mLockPatternView.setSaveEnabled(false);
+ mLockPatternView.setFocusable(false);
+ mLockPatternView.setOnPatternListener(new UnlockPatternListener());
+
+ // stealth mode will be the same for the life of this screen
+ mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled());
+
+ // vibrate mode will be the same for the life of this screen
+ mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
+
+ mForgotPatternButton = (Button) findViewById(R.id.forgot_password_button);
+ mForgotPatternButton.setText(R.string.kg_forgot_pattern_button_text);
+ mForgotPatternButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mCallback.showBackupUnlock();
+ }
+ });
+
+ setFocusableInTouchMode(true);
+
+ maybeEnableFallback(mContext);
+ }
+
+ private void updateFooter(FooterMode mode) {
+ switch (mode) {
+ case Normal:
+ if (DEBUG) Log.d(TAG, "mode normal");
+ mForgotPatternButton.setVisibility(View.GONE);
+ break;
+ case ForgotLockPattern:
+ if (DEBUG) Log.d(TAG, "mode ForgotLockPattern");
+ mForgotPatternButton.setVisibility(View.VISIBLE);
+ break;
+ case VerifyUnlocked:
+ if (DEBUG) Log.d(TAG, "mode VerifyUnlocked");
+ mForgotPatternButton.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ final boolean result = super.dispatchTouchEvent(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();
+ }
+ return result;
+ }
+
+ public void reset() {
+ // reset lock pattern
+ mLockPatternView.enableInput();
+ mLockPatternView.setEnabled(true);
+ mLockPatternView.clearPattern();
+
+ // if the user is currently locked out, enforce it.
+ long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
+ if (deadline != 0) {
+ handleAttemptLockout(deadline);
+ } else {
+ mNavigationManager.setMessage(R.string.kg_pattern_instructions);
+ }
+
+ // the footer depends on how many total attempts the user has failed
+ if (mCallback.isVerifyUnlockOnly()) {
+ updateFooter(FooterMode.VerifyUnlocked);
+ } else if (mEnableFallback &&
+ (mTotalFailedPatternAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
+ updateFooter(FooterMode.ForgotLockPattern);
+ } else {
+ updateFooter(FooterMode.Normal);
+ }
+
+ }
+
+ /** TODO: hook this up */
+ public void cleanUp() {
+ if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);
+ mLockPatternUtils = null;
+ mLockPatternView.setOnPatternListener(null);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (hasWindowFocus) {
+ // when timeout dialog closes we want to update our state
+ reset();
+ }
+ }
+
+ private class UnlockPatternListener implements LockPatternView.OnPatternListener {
+
+ public void onPatternStart() {
+ mLockPatternView.removeCallbacks(mCancelPatternRunnable);
+ }
+
+ public void onPatternCleared() {
+ }
+
+ public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
+ // To guard against accidental poking of the wakelock, look for
+ // the user actually trying to draw a pattern of some minimal length.
+ if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
+ mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
+ } else {
+ // Give just a little extra time if they hit one of the first few dots
+ mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS);
+ }
+ }
+
+ public void onPatternDetected(List<LockPatternView.Cell> pattern) {
+ if (mLockPatternUtils.checkPattern(pattern)) {
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
+ mCallback.dismiss(true); // keyguardDone(true)
+ KeyStore.getInstance().password(LockPatternUtils.patternToString(pattern));
+ } else {
+ if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
+ mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
+ }
+ mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
+ if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
+ mTotalFailedPatternAttempts++;
+ mFailedPatternAttemptsSinceLastTimeout++;
+ mCallback.reportFailedUnlockAttempt();
+ }
+ if (mFailedPatternAttemptsSinceLastTimeout
+ >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
+ long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
+ handleAttemptLockout(deadline);
+ } else {
+ mNavigationManager.setMessage(R.string.kg_wrong_pattern);
+ mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
+ }
+ }
+ }
+ }
+
+ private void maybeEnableFallback(Context context) {
+ // Ask the account manager if we have an account that can be used as a
+ // fallback in case the user forgets his pattern.
+ AccountAnalyzer accountAnalyzer = new AccountAnalyzer(AccountManager.get(context));
+ accountAnalyzer.start();
+ }
+
+ private class AccountAnalyzer implements AccountManagerCallback<Bundle> {
+ private final AccountManager mAccountManager;
+ private final Account[] mAccounts;
+ private int mAccountIndex;
+
+ private AccountAnalyzer(AccountManager accountManager) {
+ mAccountManager = accountManager;
+ mAccounts = accountManager.getAccountsByType("com.google");
+ }
+
+ private void next() {
+ // if we are ready to enable the fallback or if we depleted the list of accounts
+ // then finish and get out
+ if (mAccountIndex >= mAccounts.length) {
+ mEnableFallback = true;
+ return;
+ }
+
+ // lookup the confirmCredentials intent for the current account
+ mAccountManager.confirmCredentials(mAccounts[mAccountIndex], null, null, this, null);
+ }
+
+ public void start() {
+ mEnableFallback = false;
+ mAccountIndex = 0;
+ next();
+ }
+
+ public void run(AccountManagerFuture<Bundle> future) {
+ try {
+ Bundle result = future.getResult();
+ if (result.getParcelable(AccountManager.KEY_INTENT) != null) {
+ mEnableFallback = true;
+ }
+ } catch (OperationCanceledException e) {
+ // just skip the account if we are unable to query it
+ } catch (IOException e) {
+ // just skip the account if we are unable to query it
+ } catch (AuthenticatorException e) {
+ // just skip the account if we are unable to query it
+ } finally {
+ mAccountIndex++;
+ next();
+ }
+ }
+ }
+
+ private void handleAttemptLockout(long elapsedRealtimeDeadline) {
+ mLockPatternView.clearPattern();
+ mLockPatternView.setEnabled(false);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ final int secondsRemaining = (int) (millisUntilFinished / 1000);
+ mNavigationManager.setMessage(
+ R.string.kg_too_many_failed_attempts_countdown,secondsRemaining);
+ }
+
+ @Override
+ public void onFinish() {
+ mLockPatternView.setEnabled(true);
+ mNavigationManager.setMessage(R.string.kg_pattern_instructions);
+ // TODO mUnlockIcon.setVisibility(View.VISIBLE);
+ mFailedPatternAttemptsSinceLastTimeout = 0;
+ if (mEnableFallback) {
+ updateFooter(FooterMode.ForgotLockPattern);
+ } else {
+ updateFooter(FooterMode.Normal);
+ }
+ }
+
+ }.start();
+ }
+
+ @Override
+ public boolean needsInput() {
+ return false;
+ }
+
+ @Override
+ public void onPause() {
+ if (mCountdownTimer != null) {
+ mCountdownTimer.cancel();
+ mCountdownTimer = null;
+ }
+ }
+
+ @Override
+ public void onResume() {
+ reset();
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+}
+
+
+
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java
new file mode 100644
index 0000000..1a4a40b
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityCallback.java
@@ -0,0 +1,62 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+public interface KeyguardSecurityCallback {
+
+ /**
+ * Dismiss the given security screen.
+ * @param securityVerified true if the user correctly entered credentials for the given screen.
+ */
+ void dismiss(boolean securityVerified);
+
+ /**
+ * Manually report user activity to keep the device awake. If timeout is 0,
+ * uses user-defined timeout.
+ * @param timeout
+ */
+ void userActivity(long timeout);
+
+ /**
+ * Checks if keyguard is in "verify credentials" mode.
+ * @return true if user has been asked to verify security.
+ */
+ boolean isVerifyUnlockOnly();
+
+ /**
+ * Call when user correctly enters their credentials
+ */
+ void reportSuccessfulUnlockAttempt();
+
+ /**
+ * Call when the user incorrectly enters their credentials
+ */
+ void reportFailedUnlockAttempt();
+
+ /**
+ * Gets the number of attempts thus far as reported by {@link #reportFailedUnlockAttempt()}
+ * @return number of failed attempts
+ */
+ int getFailedAttempts();
+
+ /**
+ * Shows the backup unlock for the current method. If none available, this call is a NOP.
+ */
+ void showBackupUnlock();
+
+ void keyguardDoneDrawing();
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
new file mode 100644
index 0000000..d041dd3
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
@@ -0,0 +1,85 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.widget.LockPatternUtils;
+
+public class KeyguardSecurityModel {
+ /**
+ * The different types of security available for {@link Mode#UnlockScreen}.
+ * @see com.android.internal.policy.impl.LockPatternKeyguardView#getUnlockMode()
+ */
+ enum SecurityMode {
+ None, // No security enabled
+ Pattern, // Unlock by drawing a pattern.
+ Password, // Unlock by entering a password or PIN
+ Biometric, // Unlock with a biometric key (e.g. finger print or face unlock)
+ Account, // Unlock by entering an account's login and password.
+ SimPin, // Unlock by entering a sim pin.
+ SimPuk // Unlock by entering a sim puk
+ }
+
+ private Context mContext;
+ private LockPatternUtils mLockPatternUtils;
+
+ KeyguardSecurityModel(Context context) {
+ mContext = context;
+ mLockPatternUtils = new LockPatternUtils(context);
+ }
+
+ void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ SecurityMode getSecurityMode() {
+ KeyguardUpdateMonitor mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ final IccCardConstants.State simState = mUpdateMonitor.getSimState();
+ if (simState == IccCardConstants.State.PIN_REQUIRED) {
+ return SecurityMode.SimPin;
+ } else if (simState == IccCardConstants.State.PUK_REQUIRED) {
+ return SecurityMode.SimPuk;
+ } else {
+ final int mode = mLockPatternUtils.getKeyguardStoredPasswordQuality();
+ switch (mode) {
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
+ return mLockPatternUtils.isLockPasswordEnabled() ?
+ SecurityMode.Password : SecurityMode.None;
+
+ case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
+ case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
+ if (mLockPatternUtils.isLockPatternEnabled()) {
+ return mLockPatternUtils.isPermanentlyLocked() ?
+ SecurityMode.Account : SecurityMode.Pattern;
+ } else {
+ return SecurityMode.None;
+ }
+ default:
+ throw new IllegalStateException("Unknown unlock mode:" + mode);
+ }
+ }
+ }
+
+ SecurityMode getBackupFor(SecurityMode mode) {
+ return SecurityMode.None; // TODO: handle biometric unlock, etc.
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java
new file mode 100644
index 0000000..d80c1db
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java
@@ -0,0 +1,64 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import com.android.internal.widget.LockPatternUtils;
+
+public interface KeyguardSecurityView {
+ /**
+ * 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.
+ */
+ void onResume();
+
+ /**
+ * 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();
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java
new file mode 100644
index 0000000..b69697f
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java
@@ -0,0 +1,263 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.animation.ObjectAnimator;
+import android.app.ActivityManagerNative;
+import android.app.SearchManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.MediaStore;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.multiwaveview.GlowPadView;
+import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener;
+import com.android.internal.R;
+
+public class KeyguardSelectorView extends LinearLayout implements KeyguardSecurityView {
+ private static final boolean DEBUG = KeyguardHostView.DEBUG;
+ private static final String TAG = "SecuritySelectorView";
+ private static final String ASSIST_ICON_METADATA_NAME =
+ "com.android.systemui.action_assist_icon";
+ private KeyguardSecurityCallback mCallback;
+ private GlowPadView mGlowPadView;
+ private Button mEmergencyCallButton;
+ private ObjectAnimator mAnim;
+ private boolean mCameraDisabled;
+ private boolean mSearchDisabled;
+ private LockPatternUtils mLockPatternUtils;
+
+ OnTriggerListener mOnTriggerListener = new OnTriggerListener() {
+
+ public void onTrigger(View v, int target) {
+ final int resId = mGlowPadView.getResourceIdForTarget(target);
+ switch (resId) {
+ case com.android.internal.R.drawable.ic_action_assist_generic:
+ Intent assistIntent =
+ ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
+ .getAssistIntent(mContext, UserHandle.USER_CURRENT);
+ if (assistIntent != null) {
+ launchActivity(assistIntent);
+ } else {
+ Log.w(TAG, "Failed to get intent for assist activity");
+ }
+ mCallback.userActivity(0);
+ break;
+
+ case com.android.internal.R.drawable.ic_lockscreen_camera:
+ launchActivity(new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA));
+ mCallback.userActivity(0);
+ break;
+
+ case com.android.internal.R.drawable.ic_lockscreen_unlock_phantom:
+ case com.android.internal.R.drawable.ic_lockscreen_unlock:
+ mCallback.dismiss(false);
+ break;
+ }
+ }
+
+ public void onReleased(View v, int handle) {
+ doTransition(mEmergencyCallButton, 1.0f);
+ }
+
+ public void onGrabbed(View v, int handle) {
+ doTransition(mEmergencyCallButton, 0.0f);
+ }
+
+ public void onGrabbedStateChange(View v, int handle) {
+
+ }
+
+ public void onFinishFinalAnimation() {
+
+ }
+
+ };
+
+ KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
+
+ private boolean mEmergencyDialerDisableBecauseSimLocked;
+
+ @Override
+ public void onDevicePolicyManagerStateChanged() {
+ updateTargets();
+ }
+
+ @Override
+ public void onSimStateChanged(IccCardConstants.State simState) {
+ // Some carriers aren't capable of handling emergency calls while the SIM is locked
+ mEmergencyDialerDisableBecauseSimLocked = KeyguardUpdateMonitor.isSimLocked(simState)
+ && !mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked();
+ updateTargets();
+ }
+
+ void onPhoneStateChanged(int phoneState) {
+ if (mEmergencyCallButton != null) {
+ mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked();
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton,
+ phoneState, !mEmergencyDialerDisableBecauseSimLocked);
+ }
+ };
+ };
+
+ public KeyguardSelectorView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardSelectorView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mLockPatternUtils = new LockPatternUtils(getContext());
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mGlowPadView = (GlowPadView) findViewById(R.id.glow_pad_view);
+ mGlowPadView.setOnTriggerListener(mOnTriggerListener);
+ mEmergencyCallButton = (Button) findViewById(R.id.emergency_call_button);
+ KeyguardUpdateMonitor.getInstance(getContext()).registerCallback(mInfoCallback);
+ updateTargets();
+ }
+
+ public boolean isTargetPresent(int resId) {
+ return mGlowPadView.getTargetPosition(resId) != -1;
+ }
+
+ private void updateTargets() {
+ boolean disabledByAdmin = mLockPatternUtils.getDevicePolicyManager()
+ .getCameraDisabled(null);
+ final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(getContext());
+ boolean disabledBySimState = monitor.isSimLocked();
+ boolean cameraTargetPresent =
+ isTargetPresent(com.android.internal.R.drawable.ic_lockscreen_camera);
+ boolean searchTargetPresent =
+ isTargetPresent(com.android.internal.R.drawable.ic_action_assist_generic);
+
+ if (disabledByAdmin) {
+ Log.v(TAG, "Camera disabled by Device Policy");
+ } else if (disabledBySimState) {
+ Log.v(TAG, "Camera disabled by Sim State");
+ }
+ boolean searchActionAvailable =
+ ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
+ .getAssistIntent(mContext, UserHandle.USER_CURRENT) != null;
+ mCameraDisabled = disabledByAdmin || disabledBySimState || !cameraTargetPresent;
+ mSearchDisabled = disabledBySimState || !searchActionAvailable || !searchTargetPresent;
+ updateResources();
+ }
+
+ public void updateResources() {
+ // Update the search icon with drawable from the search .apk
+ if (!mSearchDisabled) {
+ Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
+ .getAssistIntent(mContext, UserHandle.USER_CURRENT);
+ if (intent != null) {
+ // XXX Hack. We need to substitute the icon here but haven't formalized
+ // the public API. The "_google" metadata will be going away, so
+ // DON'T USE IT!
+ ComponentName component = intent.getComponent();
+ boolean replaced = mGlowPadView.replaceTargetDrawablesIfPresent(component,
+ ASSIST_ICON_METADATA_NAME + "_google",
+ com.android.internal.R.drawable.ic_action_assist_generic);
+
+ if (!replaced && !mGlowPadView.replaceTargetDrawablesIfPresent(component,
+ ASSIST_ICON_METADATA_NAME,
+ com.android.internal.R.drawable.ic_action_assist_generic)) {
+ Slog.w(TAG, "Couldn't grab icon from package " + component);
+ }
+ }
+ }
+
+ mGlowPadView.setEnableTarget(com.android.internal.R.drawable
+ .ic_lockscreen_camera, !mCameraDisabled);
+ mGlowPadView.setEnableTarget(com.android.internal.R.drawable
+ .ic_action_assist_generic, !mSearchDisabled);
+ }
+
+ void doTransition(Object v, float to) {
+ if (mAnim != null) {
+ mAnim.cancel();
+ }
+ mAnim = ObjectAnimator.ofFloat(mEmergencyCallButton, "alpha", to);
+ mAnim.start();
+ }
+
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ }
+
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ /**
+ * Launches the said intent for the current foreground user.
+ * @param intent
+ */
+ private void launchActivity(Intent intent) {
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_SINGLE_TOP
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ try {
+ ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
+ } catch (RemoteException e) {
+ Log.w(TAG, "can't dismiss keyguard on launch");
+ }
+ try {
+ mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Activity not found for intent + " + intent.getAction());
+ }
+ }
+
+ @Override
+ public void reset() {
+ mGlowPadView.reset(false);
+ }
+
+ @Override
+ public boolean needsInput() {
+ return false;
+ }
+
+ @Override
+ public void onPause() {
+ KeyguardUpdateMonitor.getInstance(getContext()).removeCallback(mInfoCallback);
+ }
+
+ @Override
+ public void onResume() {
+ KeyguardUpdateMonitor.getInstance(getContext()).registerCallback(mInfoCallback);
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java
new file mode 100644
index 0000000..294ea5c
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java
@@ -0,0 +1,236 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.PasswordEntryKeyboardHelper;
+import com.android.internal.widget.PasswordEntryKeyboardView;
+import com.android.internal.R;
+
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+/**
+ * Displays a dialer like interface to unlock the SIM PIN.
+ */
+public class KeyguardSimPinView extends LinearLayout
+ implements KeyguardSecurityView, OnEditorActionListener {
+
+ private static final int DIGIT_PRESS_WAKE_MILLIS = 5000;
+
+ private EditText mPinEntry;
+ private ProgressDialog mSimUnlockProgressDialog = null;
+ private KeyguardSecurityCallback mCallback;
+ private PasswordEntryKeyboardView mKeyboardView;
+ private PasswordEntryKeyboardHelper mKeyboardHelper;
+ private LockPatternUtils mLockPatternUtils;
+ private KeyguardNavigationManager mNavigationManager;
+
+ public KeyguardSimPinView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardSimPinView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mLockPatternUtils = new LockPatternUtils(getContext());
+ }
+
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mNavigationManager = new KeyguardNavigationManager(this);
+
+ mPinEntry = (EditText) findViewById(R.id.sim_pin_entry);
+ mPinEntry.setOnEditorActionListener(this);
+
+ mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
+ mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false);
+ mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
+ mKeyboardHelper.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled());
+
+ final View deleteButton = findViewById(R.id.delete_button);
+ if (deleteButton != null) {
+ deleteButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mKeyboardHelper.handleBackspace();
+ }
+ });
+ }
+
+ setFocusableInTouchMode(true);
+ reset();
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ return mPinEntry.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ public void reset() {
+ // start fresh
+ mNavigationManager.setMessage(R.string.kg_sim_pin_instructions);
+
+ // make sure that the number of entered digits is consistent when we
+ // erase the SIM unlock code, including orientation changes.
+ mPinEntry.setText("");
+ mPinEntry.requestFocus();
+ }
+
+ /** {@inheritDoc} */
+ public void cleanUp() {
+ // 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;
+
+ protected CheckSimPin(String pin) {
+ mPin = pin;
+ }
+
+ abstract void onSimLockChangedResponse(boolean success);
+
+ @Override
+ public void run() {
+ try {
+ final boolean result = ITelephony.Stub.asInterface(ServiceManager
+ .checkService("phone")).supplyPin(mPin);
+ post(new Runnable() {
+ public void run() {
+ onSimLockChangedResponse(result);
+ }
+ });
+ } catch (RemoteException e) {
+ post(new Runnable() {
+ public void run() {
+ onSimLockChangedResponse(false);
+ }
+ });
+ }
+ }
+ }
+
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ // Check if this was the result of hitting the enter key
+ mCallback.userActivity(DIGIT_PRESS_WAKE_MILLIS);
+ if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT) {
+ checkPin();
+ return true;
+ }
+ return false;
+ }
+
+ 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 void checkPin() {
+ if (mPinEntry.getText().length() < 4) {
+ // otherwise, display a message to the user, and don't submit.
+ mNavigationManager.setMessage(R.string.kg_invalid_sim_pin_hint);
+ mPinEntry.setText("");
+ mCallback.userActivity(0);
+ return;
+ }
+
+ getSimUnlockProgressDialog().show();
+
+ new CheckSimPin(mPinEntry.getText().toString()) {
+ void onSimLockChangedResponse(final boolean success) {
+ post(new Runnable() {
+ public void run() {
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.hide();
+ }
+ if (success) {
+ // before closing the keyguard, report back that the sim is unlocked
+ // so it knows right away.
+ KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked();
+ mCallback.dismiss(false); //
+ } else {
+ mNavigationManager.setMessage(R.string.kg_password_wrong_pin_code);
+ mPinEntry.setText("");
+ }
+ mCallback.userActivity(0);
+ }
+ });
+ }
+ }.start();
+ }
+
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ public boolean needsInput() {
+ return false; // This view provides its own keypad
+ }
+
+ public void onPause() {
+
+ }
+
+ public void onResume() {
+ reset();
+ }
+
+ public KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java
new file mode 100644
index 0000000..801dfc3
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.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.internal.policy.impl.keyguard;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.Editable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.PasswordEntryKeyboardHelper;
+import com.android.internal.widget.PasswordEntryKeyboardView;
+import com.android.internal.R;
+
+public class KeyguardSimPukView extends LinearLayout implements View.OnClickListener,
+ View.OnFocusChangeListener, KeyguardSecurityView, OnEditorActionListener {
+
+ private static final int DIGIT_PRESS_WAKE_MILLIS = 5000;
+
+ private TextView mPukText;
+ private TextView mPinText;
+ private TextView mFocusedEntry;
+
+ private View mDelPukButton;
+ private View mDelPinButton;
+
+ private ProgressDialog mSimUnlockProgressDialog = null;
+ private KeyguardSecurityCallback mCallback;
+
+ private KeyguardNavigationManager mNavigationManager;
+
+ private PasswordEntryKeyboardView mKeyboardView;
+
+ private PasswordEntryKeyboardHelper mKeyboardHelper;
+
+ private LockPatternUtils mLockPatternUtils;
+
+ public KeyguardSimPukView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardSimPukView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mLockPatternUtils = new LockPatternUtils(getContext());
+ }
+
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ mLockPatternUtils = new LockPatternUtils(getContext());
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mNavigationManager = new KeyguardNavigationManager(this);
+
+ mPukText = (TextView) findViewById(R.id.sim_puk_entry);
+ mPukText.setOnEditorActionListener(this);
+ mPinText = (TextView) findViewById(R.id.sim_pin_entry);
+ mPinText.setOnEditorActionListener(this);
+ mDelPukButton = findViewById(R.id.puk_delete_button);
+ mDelPukButton.setOnClickListener(this);
+ mDelPinButton = findViewById(R.id.pin_delete_button);
+ mDelPinButton.setOnClickListener(this);
+
+ mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
+ mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false);
+ mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
+ mKeyboardHelper.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled());
+
+ mNavigationManager.setMessage(R.string.kg_sim_puk_recovery_hint);
+
+ mPinText.setFocusableInTouchMode(true);
+ mPinText.setOnFocusChangeListener(this);
+ mPukText.setFocusableInTouchMode(true);
+ mPukText.setOnFocusChangeListener(this);
+
+ setFocusableInTouchMode(true);
+
+ reset();
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ return mPukText.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ public boolean needsInput() {
+ return false; // This view provides its own keypad
+ }
+
+ public void onPause() {
+
+ }
+
+ public void onResume() {
+ reset();
+ }
+
+ /** {@inheritDoc} */
+ public void cleanUp() {
+ // 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;
+
+ protected CheckSimPuk(String puk, String pin) {
+ mPuk = puk;
+ mPin = pin;
+ }
+
+ abstract void onSimLockChangedResponse(boolean success);
+
+ @Override
+ public void run() {
+ try {
+ final boolean result = ITelephony.Stub.asInterface(ServiceManager
+ .checkService("phone")).supplyPuk(mPuk, mPin);
+
+ post(new Runnable() {
+ public void run() {
+ onSimLockChangedResponse(result);
+ }
+ });
+ } catch (RemoteException e) {
+ post(new Runnable() {
+ public void run() {
+ onSimLockChangedResponse(false);
+ }
+ });
+ }
+ }
+ }
+
+ public void onClick(View v) {
+ if (v == mDelPukButton) {
+ if (mFocusedEntry != mPukText)
+ mPukText.requestFocus();
+ final Editable digits = mPukText.getEditableText();
+ final int len = digits.length();
+ if (len > 0) {
+ digits.delete(len-1, len);
+ }
+ } else if (v == mDelPinButton) {
+ if (mFocusedEntry != mPinText)
+ mPinText.requestFocus();
+ final Editable digits = mPinText.getEditableText();
+ final int len = digits.length();
+ if (len > 0) {
+ digits.delete(len-1, len);
+ }
+ }
+ mCallback.userActivity(DIGIT_PRESS_WAKE_MILLIS);
+ }
+
+ @Override
+ public void onFocusChange(View view, boolean hasFocus) {
+ if (hasFocus)
+ mFocusedEntry = (TextView) view;
+ }
+
+ 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 void checkPuk() {
+ // make sure the puk is at least 8 digits long.
+ if (mPukText.getText().length() < 8) {
+ // otherwise, display a message to the user, and don't submit.
+ mNavigationManager.setMessage(R.string.kg_invalid_sim_puk_hint);
+ mPukText.setText("");
+ return;
+ }
+
+ // make sure the PIN is between 4 and 8 digits
+ if (mPinText.getText().length() < 4
+ || mPinText.getText().length() > 8) {
+ // otherwise, display a message to the user, and don't submit.
+ mNavigationManager.setMessage(R.string.kg_invalid_sim_pin_hint);
+ mPinText.setText("");
+ return;
+ }
+
+ getSimUnlockProgressDialog().show();
+
+ new CheckSimPuk(mPukText.getText().toString(),
+ mPinText.getText().toString()) {
+ void onSimLockChangedResponse(final boolean success) {
+ mPinText.post(new Runnable() {
+ public void run() {
+ if (mSimUnlockProgressDialog != null) {
+ mSimUnlockProgressDialog.hide();
+ }
+ if (success) {
+ mCallback.dismiss(true);
+ } else {
+ mNavigationManager.setMessage(R.string.kg_invalid_puk);
+ mPukText.setText("");
+ mPinText.setText("");
+ }
+ }
+ });
+ }
+ }.start();
+ }
+
+ @Override
+ public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
+ // Check if this was the result of hitting the enter key
+ mCallback.userActivity(DIGIT_PRESS_WAKE_MILLIS);
+ if (actionId == EditorInfo.IME_NULL
+ || actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT) {
+ if (view == mPukText && mPukText.getText().length() < 8) {
+ mNavigationManager.setMessage(R.string.kg_invalid_sim_puk_hint);
+ mPukText.setText("");
+ mPukText.requestFocus();
+ return true;
+ } else if (view == mPinText) {
+ if (mPinText.getText().length() < 4 || mPinText.getText().length() > 8) {
+ mNavigationManager.setMessage(R.string.kg_invalid_sim_pin_hint);
+ mPinText.setText("");
+ mPinText.requestFocus();
+ } else {
+ checkPuk();
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ }
+
+ @Override
+ public void reset() {
+ mNavigationManager.setMessage(R.string.kg_sim_puk_recovery_hint);
+ mPinText.setText("");
+ mPukText.setText("");
+ mPukText.requestFocus();
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java
new file mode 100644
index 0000000..d6ce967
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java
@@ -0,0 +1,44 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.GridLayout;
+
+public class KeyguardStatusView extends GridLayout {
+ 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);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ // StatusView manages all of the widgets in this view.
+ new KeyguardStatusViewManager(this);
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java
new file mode 100644
index 0000000..06ed88a
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java
@@ -0,0 +1,570 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import com.android.internal.R;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.widget.DigitalClock;
+import com.android.internal.widget.LockPatternUtils;
+
+import java.util.Date;
+
+import libcore.util.MutableInt;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+/***
+ * Manages a number of views inside of LockScreen layouts. See below for a list of widgets
+ */
+class KeyguardStatusViewManager {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "KeyguardStatusView";
+
+ public static final int LOCK_ICON = 0; // R.drawable.ic_lock_idle_lock;
+ public static final int ALARM_ICON = R.drawable.ic_lock_idle_alarm;
+ public static final int CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging;
+ public static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery;
+
+ private static final int INSTRUCTION_TEXT = 10;
+ private static final int CARRIER_TEXT = 11;
+ private static final int CARRIER_HELP_TEXT = 12;
+ private static final int HELP_MESSAGE_TEXT = 13;
+ private static final int OWNER_INFO = 14;
+ private static final int BATTERY_INFO = 15;
+
+ private StatusMode mStatus;
+ private String mDateFormatString;
+
+ // Views that this class controls.
+ // NOTE: These may be null in some LockScreen screens and should protect from NPE
+ private TextView mCarrierView;
+ private TextView mDateView;
+ private TextView mStatus1View;
+ private TextView mOwnerInfoView;
+ private TextView mAlarmStatusView;
+
+ // Top-level container view for above views
+ private View mContainer;
+
+ // are we showing battery information?
+ private boolean mShowingBatteryInfo = false;
+
+ // last known plugged in state
+ private boolean mPluggedIn = false;
+
+ // last known battery level
+ private int mBatteryLevel = 100;
+
+ // last known SIM state
+ protected IccCardConstants.State mSimState;
+
+ private LockPatternUtils mLockPatternUtils;
+ private KeyguardUpdateMonitor mUpdateMonitor;
+
+ // Shadowed text values
+ private CharSequence mCarrierText;
+ private CharSequence mCarrierHelpText;
+ private String mHelpMessageText;
+ private String mInstructionText;
+ private CharSequence mOwnerInfoText;
+ private boolean mShowingStatus;
+ private CharSequence mPlmn;
+ private CharSequence mSpn;
+ protected int mPhoneState;
+ private DigitalClock mDigitalClock;
+ protected boolean mBatteryCharged;
+ protected boolean mBatteryIsLow;
+ private boolean mEmergencyButtonEnabledBecauseSimLocked;
+ private Button mEmergencyCallButton;
+ private boolean mEmergencyCallButtonEnabledInScreen;
+
+ /**
+ *
+ * @param view the containing view of all widgets
+ * @param updateMonitor the update monitor to use
+ * @param lockPatternUtils lock pattern util object
+ * @param callback used to invoke emergency dialer
+ * @param emergencyButtonEnabledInScreen whether emergency button is enabled by default
+ */
+ public KeyguardStatusViewManager(View view) {
+ if (DEBUG) Log.v(TAG, "KeyguardStatusViewManager()");
+ mContainer = view;
+ mDateFormatString = getContext().getString(R.string.abbrev_wday_month_day_no_year);
+ mLockPatternUtils = new LockPatternUtils(view.getContext());
+ mUpdateMonitor = KeyguardUpdateMonitor.getInstance(view.getContext());
+
+ mCarrierView = (TextView) findViewById(R.id.carrier);
+ mDateView = (TextView) findViewById(R.id.date);
+ mStatus1View = (TextView) findViewById(R.id.status1);
+ mAlarmStatusView = (TextView) findViewById(R.id.alarm_status);
+ mOwnerInfoView = (TextView) findViewById(R.id.propertyOf);
+ mDigitalClock = (DigitalClock) findViewById(R.id.time);
+
+ // Registering this callback immediately updates the battery state, among other things.
+ mUpdateMonitor.registerCallback(mInfoCallback);
+
+ resetStatusInfo();
+ refreshDate();
+ updateOwnerInfo();
+
+ // Required to get Marquee to work.
+ final View scrollableViews[] = { mCarrierView, mDateView, mStatus1View, mOwnerInfoView,
+ mAlarmStatusView };
+ for (View v : scrollableViews) {
+ if (v != null) {
+ v.setSelected(true);
+ }
+ }
+ }
+
+ void setInstructionText(String string) {
+ mInstructionText = string;
+ update(INSTRUCTION_TEXT, string);
+ }
+
+ void setCarrierText(CharSequence string) {
+ mCarrierText = string;
+ update(CARRIER_TEXT, string);
+ }
+
+ void setOwnerInfo(CharSequence string) {
+ mOwnerInfoText = string;
+ update(OWNER_INFO, string);
+ }
+
+ /**
+ * Sets the carrier help text message, if view is present. Carrier help text messages are
+ * typically for help dealing with SIMS and connectivity.
+ *
+ * @param resId resource id of the message
+ */
+ public void setCarrierHelpText(int resId) {
+ mCarrierHelpText = getText(resId);
+ update(CARRIER_HELP_TEXT, mCarrierHelpText);
+ }
+
+ private CharSequence getText(int resId) {
+ return resId == 0 ? null : getContext().getText(resId);
+ }
+
+ /**
+ * Unlock help message. This is typically for help with unlock widgets, e.g. "wrong password"
+ * or "try again."
+ *
+ * @param textResId
+ * @param lockIcon
+ */
+ public void setHelpMessage(int textResId, int lockIcon) {
+ final CharSequence tmp = getText(textResId);
+ mHelpMessageText = tmp == null ? null : tmp.toString();
+ update(HELP_MESSAGE_TEXT, mHelpMessageText);
+ }
+
+ private void update(int what, CharSequence string) {
+ updateStatusLines(mShowingStatus);
+ }
+
+ public void onPause() {
+ if (DEBUG) Log.v(TAG, "onPause()");
+ mUpdateMonitor.removeCallback(mInfoCallback);
+ }
+
+ /** {@inheritDoc} */
+ public void onResume() {
+ if (DEBUG) Log.v(TAG, "onResume()");
+
+ // First update the clock, if present.
+ if (mDigitalClock != null) {
+ mDigitalClock.updateTime();
+ }
+
+ mUpdateMonitor.registerCallback(mInfoCallback);
+ resetStatusInfo();
+ }
+
+ void resetStatusInfo() {
+ mInstructionText = null;
+ updateStatusLines(true);
+ }
+
+ /**
+ * Update the status lines based on these rules:
+ * AlarmStatus: Alarm state always gets it's own line.
+ * Status1 is shared between help, battery status and generic unlock instructions,
+ * prioritized in that order.
+ * @param showStatusLines status lines are shown if true
+ */
+ void updateStatusLines(boolean showStatusLines) {
+ if (DEBUG) Log.v(TAG, "updateStatusLines(" + showStatusLines + ")");
+ mShowingStatus = showStatusLines;
+ updateAlarmInfo();
+ updateOwnerInfo();
+ updateStatus1();
+ updateCarrierText();
+ }
+
+ private void updateAlarmInfo() {
+ if (mAlarmStatusView != null) {
+ String nextAlarm = mLockPatternUtils.getNextAlarm();
+ boolean showAlarm = mShowingStatus && !TextUtils.isEmpty(nextAlarm);
+ mAlarmStatusView.setText(nextAlarm);
+ mAlarmStatusView.setCompoundDrawablesWithIntrinsicBounds(ALARM_ICON, 0, 0, 0);
+ mAlarmStatusView.setVisibility(showAlarm ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ private void updateOwnerInfo() {
+ final ContentResolver res = getContext().getContentResolver();
+ final boolean ownerInfoEnabled = Settings.Secure.getInt(res,
+ Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1) != 0;
+ mOwnerInfoText = ownerInfoEnabled ?
+ Settings.Secure.getString(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO) : null;
+ if (mOwnerInfoView != null) {
+ mOwnerInfoView.setText(mOwnerInfoText);
+ mOwnerInfoView.setVisibility(TextUtils.isEmpty(mOwnerInfoText) ? View.GONE:View.VISIBLE);
+ }
+ }
+
+ private void updateStatus1() {
+ if (mStatus1View != null) {
+ MutableInt icon = new MutableInt(0);
+ CharSequence string = getPriorityTextMessage(icon);
+ mStatus1View.setText(string);
+ mStatus1View.setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0);
+ mStatus1View.setVisibility(mShowingStatus ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ private void updateCarrierText() {
+ mCarrierView.setText(mCarrierText);
+ }
+
+ private CharSequence getAltTextMessage(MutableInt icon) {
+ // If we have replaced the status area with a single widget, then this code
+ // prioritizes what to show in that space when all transient messages are gone.
+ CharSequence string = null;
+ if (mShowingBatteryInfo) {
+ // Battery status
+ if (mPluggedIn) {
+ // Charging, charged or waiting to charge.
+ string = getContext().getString(mBatteryCharged ? R.string.lockscreen_charged
+ :R.string.lockscreen_plugged_in, mBatteryLevel);
+ icon.value = CHARGING_ICON;
+ } else if (mBatteryIsLow) {
+ // Battery is low
+ string = getContext().getString(R.string.lockscreen_low_battery);
+ icon.value = BATTERY_LOW_ICON;
+ }
+ } else {
+ string = mCarrierText;
+ }
+ return string;
+ }
+
+ private CharSequence getPriorityTextMessage(MutableInt icon) {
+ CharSequence string = null;
+ if (!TextUtils.isEmpty(mInstructionText)) {
+ // Instructions only
+ string = mInstructionText;
+ icon.value = LOCK_ICON;
+ } else if (mShowingBatteryInfo) {
+ // Battery status
+ if (mPluggedIn) {
+ // Charging, charged or waiting to charge.
+ string = getContext().getString(mBatteryCharged ? R.string.lockscreen_charged
+ :R.string.lockscreen_plugged_in, mBatteryLevel);
+ icon.value = CHARGING_ICON;
+ } else if (mBatteryIsLow) {
+ // Battery is low
+ string = getContext().getString(R.string.lockscreen_low_battery);
+ icon.value = BATTERY_LOW_ICON;
+ }
+ } else if (mOwnerInfoView == null && mOwnerInfoText != null) {
+ string = mOwnerInfoText;
+ }
+ return string;
+ }
+
+ void refreshDate() {
+ if (mDateView != null) {
+ mDateView.setText(DateFormat.format(mDateFormatString, new Date()));
+ }
+ }
+
+ /**
+ * Determine the current status of the lock screen given the sim state and other stuff.
+ */
+ public 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 = (!mUpdateMonitor.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.SimMissing;
+ 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 Context getContext() {
+ return mContainer.getContext();
+ }
+
+ /**
+ * Update carrier text, carrier help and emergency button to match the current status based
+ * on SIM state.
+ *
+ * @param simState
+ */
+ private void updateCarrierStateWithSimStatus(IccCardConstants.State simState) {
+ if (DEBUG) Log.d(TAG, "updateCarrierTextWithSimStatus(), simState = " + simState);
+
+ CharSequence carrierText = null;
+ int carrierHelpTextId = 0;
+ mEmergencyButtonEnabledBecauseSimLocked = false;
+ mStatus = getStatusForIccState(simState);
+ mSimState = simState;
+ switch (mStatus) {
+ case Normal:
+ carrierText = makeCarierString(mPlmn, mSpn);
+ break;
+
+ case NetworkLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.lockscreen_network_locked_message),
+ mPlmn);
+ carrierHelpTextId = R.string.lockscreen_instructions_when_pattern_disabled;
+ break;
+
+ case SimMissing:
+ // 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"
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.lockscreen_missing_sim_message_short),
+ mPlmn);
+ carrierHelpTextId = R.string.lockscreen_missing_sim_instructions_long;
+ break;
+
+ case SimPermDisabled:
+ carrierText = getContext().getText(
+ R.string.lockscreen_permanent_disabled_sim_message_short);
+ carrierHelpTextId = R.string.lockscreen_permanent_disabled_sim_instructions;
+ mEmergencyButtonEnabledBecauseSimLocked = true;
+ break;
+
+ case SimMissingLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.lockscreen_missing_sim_message_short),
+ mPlmn);
+ carrierHelpTextId = R.string.lockscreen_missing_sim_instructions;
+ mEmergencyButtonEnabledBecauseSimLocked = true;
+ break;
+
+ case SimLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.lockscreen_sim_locked_message),
+ mPlmn);
+ mEmergencyButtonEnabledBecauseSimLocked = true;
+ break;
+
+ case SimPukLocked:
+ carrierText = makeCarrierStringOnEmergencyCapable(
+ getContext().getText(R.string.lockscreen_sim_puk_locked_message),
+ mPlmn);
+ if (!mLockPatternUtils.isPukUnlockScreenEnable()) {
+ // This means we're showing the PUK unlock screen
+ mEmergencyButtonEnabledBecauseSimLocked = true;
+ }
+ break;
+ }
+
+ setCarrierText(carrierText);
+ setCarrierHelpText(carrierHelpTextId);
+ updateEmergencyCallButtonState(mPhoneState);
+ }
+
+
+ /*
+ * Add emergencyCallMessage to carrier string only if phone supports emergency calls.
+ */
+ private CharSequence makeCarrierStringOnEmergencyCapable(
+ CharSequence simMessage, CharSequence emergencyCallMessage) {
+ if (mLockPatternUtils.isEmergencyCallCapable()) {
+ return makeCarierString(simMessage, emergencyCallMessage);
+ }
+ return simMessage;
+ }
+
+ private View findViewById(int id) {
+ return mContainer.findViewById(id);
+ }
+
+ /**
+ * The status of this lock screen. Primarily used for widgets on LockScreen.
+ */
+ enum StatusMode {
+ /**
+ * Normal case (sim card present, it's not locked)
+ */
+ Normal(true),
+
+ /**
+ * The sim card is 'network locked'.
+ */
+ NetworkLocked(true),
+
+ /**
+ * The sim card is missing.
+ */
+ SimMissing(false),
+
+ /**
+ * The sim card is missing, and this is the device isn't provisioned, so we don't let
+ * them get past the screen.
+ */
+ SimMissingLocked(false),
+
+ /**
+ * The sim card is PUK locked, meaning they've entered the wrong sim unlock code too many
+ * times.
+ */
+ SimPukLocked(false),
+
+ /**
+ * The sim card is locked.
+ */
+ SimLocked(true),
+
+ /**
+ * The sim card is permanently disabled due to puk unlock failure
+ */
+ SimPermDisabled(false);
+
+ private final boolean mShowStatusLines;
+
+ StatusMode(boolean mShowStatusLines) {
+ this.mShowStatusLines = mShowStatusLines;
+ }
+
+ /**
+ * @return Whether the status lines (battery level and / or next alarm) are shown while
+ * in this state. Mostly dictated by whether this is room for them.
+ */
+ public boolean shouldShowStatusLines() {
+ return mShowStatusLines;
+ }
+ }
+
+ private void updateEmergencyCallButtonState(int phoneState) {
+ if (mEmergencyCallButton != null) {
+ boolean enabledBecauseSimLocked =
+ mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked()
+ && mEmergencyButtonEnabledBecauseSimLocked;
+ boolean shown = mEmergencyCallButtonEnabledInScreen || enabledBecauseSimLocked;
+ mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton,
+ phoneState, shown);
+ }
+ }
+
+ private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
+
+ @Override
+ public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
+ mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow();
+ mPluggedIn = status.isPluggedIn();
+ mBatteryLevel = status.level;
+ mBatteryCharged = status.isCharged();
+ mBatteryIsLow = status.isBatteryLow();
+ final MutableInt tmpIcon = new MutableInt(0);
+ update(BATTERY_INFO, getAltTextMessage(tmpIcon));
+ }
+
+ @Override
+ public void onTimeChanged() {
+ refreshDate();
+ }
+
+ @Override
+ public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
+ mPlmn = plmn;
+ mSpn = spn;
+ updateCarrierStateWithSimStatus(mSimState);
+ }
+
+ @Override
+ public void onPhoneStateChanged(int phoneState) {
+ mPhoneState = phoneState;
+ updateEmergencyCallButtonState(phoneState);
+ }
+
+ @Override
+ public void onSimStateChanged(IccCardConstants.State simState) {
+ updateCarrierStateWithSimStatus(simState);
+ }
+ };
+
+ /**
+ * Performs concentenation of PLMN/SPN
+ * @param plmn
+ * @param spn
+ * @return
+ */
+ private static CharSequence makeCarierString(CharSequence plmn, CharSequence spn) {
+ final boolean plmnValid = !TextUtils.isEmpty(plmn);
+ final boolean spnValid = !TextUtils.isEmpty(spn);
+ if (plmnValid && spnValid) {
+ return plmn + "|" + spn;
+ } else if (plmnValid) {
+ return plmn;
+ } else if (spnValid) {
+ return spn;
+ } else {
+ return "";
+ }
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
new file mode 100644
index 0000000..dad0dff
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
@@ -0,0 +1,710 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import static android.os.BatteryManager.BATTERY_STATUS_FULL;
+import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
+import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
+import static android.os.BatteryManager.EXTRA_STATUS;
+import static android.os.BatteryManager.EXTRA_PLUGGED;
+import static android.os.BatteryManager.EXTRA_LEVEL;
+import static android.os.BatteryManager.EXTRA_HEALTH;
+import android.media.AudioManager;
+import android.os.BatteryManager;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Settings;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.TelephonyIntents;
+
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import com.android.internal.R;
+import com.google.android.collect.Lists;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * 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 #getFailedAttempts()}, {@link #reportFailedAttempt()}
+ * and {@link #clearFailedAttempts()}. Maybe we should rename this 'KeyguardContext'...
+ */
+public class KeyguardUpdateMonitor {
+
+ private static final String TAG = "KeyguardUpdateMonitor";
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_SIM_STATES = DEBUG || false;
+ private static final int FAILED_BIOMETRIC_UNLOCK_ATTEMPTS_BEFORE_BACKUP = 3;
+ private static final int LOW_BATTERY_THRESHOLD = 20;
+
+ // Callback messages
+ private static final int MSG_TIME_UPDATE = 301;
+ private static final int MSG_BATTERY_UPDATE = 302;
+ private static final int MSG_CARRIER_INFO_UPDATE = 303;
+ 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_CLOCK_VISIBILITY_CHANGED = 307;
+ private static final int MSG_DEVICE_PROVISIONED = 308;
+ protected static final int MSG_DPM_STATE_CHANGED = 309;
+ protected static final int MSG_USER_SWITCHED = 310;
+ protected static final int MSG_USER_REMOVED = 311;
+
+ private static KeyguardUpdateMonitor sInstance;
+
+ private final Context mContext;
+
+ // Telephony state
+ private IccCardConstants.State mSimState = IccCardConstants.State.READY;
+ private CharSequence mTelephonyPlmn;
+ private CharSequence mTelephonySpn;
+ private int mRingMode;
+ private int mPhoneState;
+
+ // Device provisioning state
+ private boolean mDeviceProvisioned;
+
+ // Battery status
+ private BatteryStatus mBatteryStatus;
+
+ // Password attempts
+ private int mFailedAttempts = 0;
+ private int mFailedBiometricUnlockAttempts = 0;
+
+ private boolean mClockVisible;
+
+ private ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
+ mCallbacks = Lists.newArrayList();
+ private ContentObserver mContentObserver;
+
+ 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_CARRIER_INFO_UPDATE:
+ handleCarrierInfoUpdate();
+ break;
+ case MSG_SIM_STATE_CHANGE:
+ handleSimStateChange((SimArgs) msg.obj);
+ break;
+ case MSG_RINGER_MODE_CHANGED:
+ handleRingerModeChange(msg.arg1);
+ break;
+ case MSG_PHONE_STATE_CHANGED:
+ handlePhoneStateChanged((String)msg.obj);
+ break;
+ case MSG_CLOCK_VISIBILITY_CHANGED:
+ handleClockVisibilityChanged();
+ break;
+ case MSG_DEVICE_PROVISIONED:
+ handleDeviceProvisioned();
+ break;
+ case MSG_DPM_STATE_CHANGED:
+ handleDevicePolicyManagerStateChanged();
+ break;
+ case MSG_USER_SWITCHED:
+ handleUserSwitched(msg.arg1);
+ break;
+ case MSG_USER_REMOVED:
+ handleUserRemoved(msg.arg1);
+ break;
+ }
+ }
+ };
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+
+ 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.sendMessage(mHandler.obtainMessage(MSG_TIME_UPDATE));
+ } else if (TelephonyIntents.SPN_STRINGS_UPDATED_ACTION.equals(action)) {
+ mTelephonyPlmn = getTelephonyPlmnFrom(intent);
+ mTelephonySpn = getTelephonySpnFrom(intent);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_CARRIER_INFO_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 Message msg = mHandler.obtainMessage(
+ MSG_BATTERY_UPDATE, new BatteryStatus(status, level, plugged, health));
+ mHandler.sendMessage(msg);
+ } else if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
+ if (DEBUG_SIM_STATES) {
+ Log.v(TAG, "action " + action + " state" +
+ intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE));
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(
+ MSG_SIM_STATE_CHANGE, SimArgs.fromIntent(intent)));
+ } 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 (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
+ .equals(action)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DPM_STATE_CHANGED));
+ } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED,
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0));
+ } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_REMOVED,
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0));
+ }
+ }
+ };
+
+ /**
+ * 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 SimArgs {
+ public final IccCardConstants.State simState;
+
+ SimArgs(IccCardConstants.State state) {
+ simState = state;
+ }
+
+ static SimArgs fromIntent(Intent intent) {
+ IccCardConstants.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);
+ 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 {
+ state = IccCardConstants.State.UNKNOWN;
+ }
+ return new SimArgs(state);
+ }
+
+ public String toString() {
+ return simState.toString();
+ }
+ }
+
+ /* package */ static class BatteryStatus {
+ public final int status;
+ public final int level;
+ public final int plugged;
+ public final int health;
+ public BatteryStatus(int status, int level, int plugged, int health) {
+ this.status = status;
+ this.level = level;
+ this.plugged = plugged;
+ this.health = health;
+ }
+
+ /**
+ * Determine whether the device is plugged in (USB or power).
+ * @return true if the device is plugged in.
+ */
+ boolean isPluggedIn() {
+ return plugged == BatteryManager.BATTERY_PLUGGED_AC
+ || plugged == BatteryManager.BATTERY_PLUGGED_USB;
+ }
+
+ /**
+ * 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 static KeyguardUpdateMonitor getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new KeyguardUpdateMonitor(context);
+ }
+ return sInstance;
+ }
+
+ private KeyguardUpdateMonitor(Context context) {
+ mContext = context;
+
+ mDeviceProvisioned = Settings.Secure.getInt(
+ mContext.getContentResolver(), Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
+
+ // 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
+ mSimState = IccCardConstants.State.NOT_READY;
+ mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0);
+ mTelephonyPlmn = getDefaultPlmn();
+
+ // 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(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+ filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
+ filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
+ filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ filter.addAction(Intent.ACTION_USER_REMOVED);
+ context.registerReceiver(mBroadcastReceiver, filter);
+ }
+
+ private void watchForDeviceProvisioning() {
+ mContentObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ mDeviceProvisioned = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
+ if (mDeviceProvisioned) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DEVICE_PROVISIONED));
+ }
+ if (DEBUG) Log.d(TAG, "DEVICE_PROVISIONED state = " + mDeviceProvisioned);
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.DEVICE_PROVISIONED),
+ false, mContentObserver);
+
+ // prevent a race condition between where we check the flag and where we register the
+ // observer by grabbing the value once again...
+ boolean provisioned = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
+ if (provisioned != mDeviceProvisioned) {
+ mDeviceProvisioned = provisioned;
+ if (mDeviceProvisioned) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DEVICE_PROVISIONED));
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_DPM_STATE_CHANGED}
+ */
+ protected void handleDevicePolicyManagerStateChanged() {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onDevicePolicyManagerStateChanged();
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_USER_SWITCHED}
+ */
+ protected void handleUserSwitched(int userId) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onUserSwitched(userId);
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_USER_SWITCHED}
+ */
+ protected void handleUserRemoved(int userId) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onUserRemoved(userId);
+ }
+ }
+ }
+
+ /**
+ * 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 (mContentObserver != null) {
+ // We don't need the observer anymore...
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ mContentObserver = 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_CARRIER_INFO_UPDATE}
+ */
+ private void handleCarrierInfoUpdate() {
+ if (DEBUG) Log.d(TAG, "handleCarrierInfoUpdate: plmn = " + mTelephonyPlmn
+ + ", spn = " + mTelephonySpn);
+
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_SIM_STATE_CHANGE}
+ */
+ private void handleSimStateChange(SimArgs simArgs) {
+ final IccCardConstants.State state = simArgs.simState;
+
+ if (DEBUG) {
+ Log.d(TAG, "handleSimStateChange: intentValue = " + simArgs + " "
+ + "state resolved to " + state.toString());
+ }
+
+ if (state != IccCardConstants.State.UNKNOWN && state != mSimState) {
+ mSimState = state;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onSimStateChanged(state);
+ }
+ }
+ }
+ }
+
+ /**
+ * Handle {@link #MSG_CLOCK_VISIBILITY_CHANGED}
+ */
+ private void handleClockVisibilityChanged() {
+ if (DEBUG) Log.d(TAG, "handleClockVisibilityChanged()");
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onClockVisibilityChanged();
+ }
+ }
+ }
+
+ 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;
+ }
+ return false;
+ }
+
+ /**
+ * @param intent The intent with action {@link TelephonyIntents#SPN_STRINGS_UPDATED_ACTION}
+ * @return The string to use for the plmn, or null if it should not be shown.
+ */
+ private CharSequence getTelephonyPlmnFrom(Intent intent) {
+ if (intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
+ final String plmn = intent.getStringExtra(TelephonyIntents.EXTRA_PLMN);
+ return (plmn != null) ? plmn : getDefaultPlmn();
+ }
+ return null;
+ }
+
+ /**
+ * @return The default plmn (no service)
+ */
+ private CharSequence getDefaultPlmn() {
+ return mContext.getResources().getText(R.string.lockscreen_carrier_default);
+ }
+
+ /**
+ * @param intent The intent with action {@link Telephony.Intents#SPN_STRINGS_UPDATED_ACTION}
+ * @return The string to use for the plmn, or null if it should not be shown.
+ */
+ private CharSequence getTelephonySpnFrom(Intent intent) {
+ if (intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
+ final String spn = intent.getStringExtra(TelephonyIntents.EXTRA_SPN);
+ if (spn != null) {
+ return spn;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Remove the given observer's callback.
+ *
+ * @param observer The observer to remove
+ */
+ public void removeCallback(Object observer) {
+ mCallbacks.remove(observer);
+ }
+
+ /**
+ * Register to receive notifications about general keyguard information
+ * (see {@link InfoCallback}.
+ * @param callback The callback.
+ */
+ public void registerCallback(KeyguardUpdateMonitorCallback callback) {
+ if (!mCallbacks.contains(callback)) {
+ mCallbacks.add(new WeakReference<KeyguardUpdateMonitorCallback>(callback));
+ // Notify listener of the current state
+ callback.onRefreshBatteryInfo(mBatteryStatus);
+ callback.onTimeChanged();
+ callback.onRingerModeChanged(mRingMode);
+ callback.onPhoneStateChanged(mPhoneState);
+ callback.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
+ callback.onClockVisibilityChanged();
+ callback.onSimStateChanged(mSimState);
+ } else {
+ if (DEBUG) Log.e(TAG, "Object tried to add another callback",
+ new Exception("Called by"));
+ }
+
+ // Clean up any unused references
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ if (mCallbacks.get(i).get() == null) {
+ mCallbacks.remove(i);
+ }
+ }
+ }
+
+ public void reportClockVisible(boolean visible) {
+ mClockVisible = visible;
+ mHandler.obtainMessage(MSG_CLOCK_VISIBILITY_CHANGED).sendToTarget();
+ }
+
+ public IccCardConstants.State getSimState() {
+ return mSimState;
+ }
+
+ /**
+ * 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() {
+ handleSimStateChange(new SimArgs(IccCardConstants.State.READY));
+ }
+
+ public CharSequence getTelephonyPlmn() {
+ return mTelephonyPlmn;
+ }
+
+ public CharSequence getTelephonySpn() {
+ return mTelephonySpn;
+ }
+
+ /**
+ * @return Whether the device is provisioned (whether they have gone through
+ * the setup wizard)
+ */
+ public boolean isDeviceProvisioned() {
+ return mDeviceProvisioned;
+ }
+
+ public int getFailedAttempts() {
+ return mFailedAttempts;
+ }
+
+ public void clearFailedAttempts() {
+ mFailedAttempts = 0;
+ mFailedBiometricUnlockAttempts = 0;
+ }
+
+ public void reportFailedAttempt() {
+ mFailedAttempts++;
+ }
+
+ public boolean isClockVisible() {
+ return mClockVisible;
+ }
+
+ public int getPhoneState() {
+ return mPhoneState;
+ }
+
+ public void reportFailedBiometricUnlockAttempt() {
+ mFailedBiometricUnlockAttempts++;
+ }
+
+ public boolean getMaxBiometricUnlockAttemptsReached() {
+ return mFailedBiometricUnlockAttempts >= FAILED_BIOMETRIC_UNLOCK_ATTEMPTS_BEFORE_BACKUP;
+ }
+
+ public boolean isSimLocked() {
+ return isSimLocked(mSimState);
+ }
+
+ public static boolean isSimLocked(IccCardConstants.State state) {
+ return state == IccCardConstants.State.PIN_REQUIRED
+ || state == IccCardConstants.State.PUK_REQUIRED
+ || state == IccCardConstants.State.PERM_DISABLED;
+ }
+
+ public boolean isSimPinSecure() {
+ return isSimPinSecure(mSimState);
+ }
+
+ 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);
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitorCallback.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java
index d791419..3d65e68 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitorCallback.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java
@@ -13,12 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard;
import android.app.admin.DevicePolicyManager;
import android.media.AudioManager;
-import com.android.internal.policy.impl.KeyguardUpdateMonitor.BatteryStatus;
import com.android.internal.telephony.IccCardConstants;
/**
@@ -31,7 +30,7 @@ class KeyguardUpdateMonitorCallback {
*
* @param status current battery status
*/
- void onRefreshBatteryInfo(BatteryStatus status) { }
+ void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { }
/**
* Called once per minute or when the time changes.
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java
new file mode 100644
index 0000000..ad5de0e
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java
@@ -0,0 +1,254 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.IAudioService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.TelephonyManager;
+import android.view.KeyEvent;
+import android.widget.LinearLayout;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * 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 abstract class KeyguardViewBase extends LinearLayout {
+
+ private static final int BACKGROUND_COLOR = 0x70000000;
+ private AudioManager mAudioManager;
+ private TelephonyManager mTelephonyManager = null;
+ protected KeyguardViewMediator.ViewMediatorCallback mViewMediatorCallback;
+
+ // 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 = true;
+
+ // This is a faster way to draw the background on devices without hardware acceleration
+ private static final Drawable mBackgroundDrawable = new Drawable() {
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawColor(BACKGROUND_COLOR, PorterDuff.Mode.SRC);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+ };
+
+ public KeyguardViewBase(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardViewBase(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ resetBackground();
+ }
+
+ public void resetBackground() {
+ setBackground(mBackgroundDrawable);
+ }
+
+ /**
+ * Called when you need to reset the state of your view.
+ */
+ abstract public void reset();
+
+ /**
+ * Called when the screen turned off.
+ */
+ abstract public void onScreenTurnedOff();
+
+ /**
+ * Called when the screen turned on.
+ */
+ abstract public void onScreenTurnedOn();
+
+ /**
+ * Called when the view needs to be shown.
+ */
+ abstract public void show();
+
+ /**
+ * Called when a key has woken the device to give us a chance to adjust our
+ * state according the the key. We are responsible for waking the device
+ * (by poking the wake lock) once we are ready.
+ *
+ * The 'Tq' suffix is per the documentation in {@link android.view.WindowManagerPolicy}.
+ * Be sure not to take any action that takes a long time; any significant
+ * action should be posted to a handler.
+ *
+ * @param keyCode The wake key, which may be relevant for configuring the
+ * keyguard. May be {@link KeyEvent#KEYCODE_UNKNOWN} if waking for a reason
+ * other than a key press.
+ */
+ abstract public void wakeWhenReadyTq(int keyCode);
+
+ /**
+ * Verify that the user can get past the keyguard securely. This is called,
+ * for example, when the phone disables the keyguard but then wants to launch
+ * something else that requires secure access.
+ *
+ * The result will be propogated back via {@link KeyguardViewCallback#keyguardDone(boolean)}
+ */
+ abstract public void verifyUnlock();
+
+ /**
+ * Called before this view is being removed.
+ */
+ abstract public void cleanUp();
+
+ @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.
+ */
+ private 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: {
+ 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.adjustLocalOrRemoteStreamVolume(
+ AudioManager.STREAM_MUSIC,
+ keyCode == KeyEvent.KEYCODE_VOLUME_UP
+ ? AudioManager.ADJUST_RAISE
+ : AudioManager.ADJUST_LOWER);
+ // 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: {
+ handleMediaKeyEvent(event);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ void handleMediaKeyEvent(KeyEvent keyEvent) {
+ IAudioService audioService = IAudioService.Stub.asInterface(
+ ServiceManager.checkService(Context.AUDIO_SERVICE));
+ if (audioService != null) {
+ try {
+ audioService.dispatchMediaKeyEvent(keyEvent);
+ } catch (RemoteException e) {
+ Log.e("KeyguardViewBase", "dispatchMediaKeyEvent threw exception " + e);
+ }
+ } else {
+ Slog.w("KeyguardViewBase", "Unable to find IAudioService for media key event");
+ }
+ }
+
+ @Override
+ public void dispatchSystemUiVisibilityChanged(int visibility) {
+ super.dispatchSystemUiVisibilityChanged(visibility);
+ setSystemUiVisibility(STATUS_BAR_DISABLE_BACK);
+ }
+
+ public void setViewMediatorCallback(
+ KeyguardViewMediator.ViewMediatorCallback viewMediatorCallback) {
+ mViewMediatorCallback = viewMediatorCallback;
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
new file mode 100644
index 0000000..61003bf
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
@@ -0,0 +1,318 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewManager;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.R;
+
+/**
+ * Manages creating, showing, hiding and resetting the keyguard. Calls back
+ * via {@link com.android.internal.policy.impl.KeyguardViewCallback} to poke
+ * the wake lock and report that the keyguard is done, which is in turn,
+ * reported to this class by the current {@link KeyguardViewBase}.
+ */
+public class KeyguardViewManager {
+ private final static boolean DEBUG = false;
+ private static String TAG = "KeyguardViewManager";
+
+ private final Context mContext;
+ private final ViewManager mViewManager;
+ private final KeyguardViewMediator.ViewMediatorCallback mViewMediatorCallback;
+
+ private WindowManager.LayoutParams mWindowLayoutParams;
+ private boolean mNeedsInput = false;
+
+ private FrameLayout mKeyguardHost;
+ private KeyguardHostView mKeyguardView;
+
+ private boolean mScreenOn = false;
+ private LockPatternUtils mLockPatternUtils;
+
+ public interface ShowListener {
+ void onShown(IBinder windowToken);
+ };
+
+ /**
+ * @param context Used to create views.
+ * @param viewManager Keyguard will be attached to this.
+ * @param callback Used to notify of changes.
+ * @param lockPatternUtils
+ */
+ public KeyguardViewManager(Context context, ViewManager viewManager,
+ KeyguardViewMediator.ViewMediatorCallback callback,
+ LockPatternUtils lockPatternUtils) {
+ mContext = context;
+ mViewManager = viewManager;
+ mViewMediatorCallback = callback;
+ mLockPatternUtils = lockPatternUtils;
+ }
+
+ /**
+ * Show the keyguard. Will handle creating and attaching to the view manager
+ * lazily.
+ */
+ public synchronized void show() {
+ if (DEBUG) Log.d(TAG, "show(); mKeyguardView==" + mKeyguardView);
+
+ boolean enableScreenRotation = shouldEnableScreenRotation();
+
+ maybeCreateKeyguardLocked(enableScreenRotation);
+ maybeEnableScreenRotation(enableScreenRotation);
+
+ // Disable aspects of the system/status/navigation bars that are not appropriate or
+ // useful for the lockscreen but can be re-shown by dialogs or SHOW_WHEN_LOCKED activities.
+ // Other disabled bits are handled by the KeyguardViewMediator talking directly to the
+ // status bar service.
+ int visFlags = View.STATUS_BAR_DISABLE_BACK | View.STATUS_BAR_DISABLE_HOME;
+ if (DEBUG) Log.v(TAG, "KGVM: Set visibility on " + mKeyguardHost + " to " + visFlags);
+ mKeyguardHost.setSystemUiVisibility(visFlags);
+
+ mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
+ mKeyguardHost.setVisibility(View.VISIBLE);
+ mKeyguardView.show();
+ mKeyguardView.requestFocus();
+ }
+
+ private boolean shouldEnableScreenRotation() {
+ Resources res = mContext.getResources();
+ return SystemProperties.getBoolean("lockscreen.rot_override",false)
+ || res.getBoolean(com.android.internal.R.bool.config_enableLockScreenRotation);
+ }
+
+ class ViewManagerHost extends FrameLayout {
+ public ViewManagerHost(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ maybeCreateKeyguardLocked(shouldEnableScreenRotation());
+ }
+ }
+
+ private void maybeCreateKeyguardLocked(boolean enableScreenRotation) {
+ final boolean isActivity = (mContext instanceof Activity); // for test activity
+
+ if (mKeyguardHost == null) {
+ if (DEBUG) Log.d(TAG, "keyguard host is null, creating it...");
+
+ mKeyguardHost = new ViewManagerHost(mContext);
+
+ int flags = WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN
+ | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER
+ | WindowManager.LayoutParams.FLAG_SLIPPERY;
+
+ if (!mNeedsInput) {
+ flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ }
+ if (ActivityManager.isHighEndGfx()) {
+ flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ }
+
+ final int stretch = ViewGroup.LayoutParams.MATCH_PARENT;
+ final int type = isActivity ? WindowManager.LayoutParams.TYPE_APPLICATION
+ : WindowManager.LayoutParams.TYPE_KEYGUARD;
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ stretch, stretch, type, flags, PixelFormat.TRANSLUCENT);
+ lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+ lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen;
+ if (ActivityManager.isHighEndGfx()) {
+ lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ lp.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
+ }
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY;
+ lp.setTitle(isActivity ? "KeyguardMock" : "Keyguard");
+ mWindowLayoutParams = lp;
+ mViewManager.addView(mKeyguardHost, lp);
+ }
+ inflateKeyguardView();
+ mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
+ }
+
+ private void inflateKeyguardView() {
+ if (mKeyguardView != null) {
+ mKeyguardHost.removeView(mKeyguardView);
+ }
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ View view = inflater.inflate(R.layout.keyguard_host_view, mKeyguardHost, true);
+ mKeyguardView = (KeyguardHostView) view.findViewById(R.id.keyguard_host_view);
+ mKeyguardView.setLockPatternUtils(mLockPatternUtils);
+ mKeyguardView.setViewMediatorCallback(mViewMediatorCallback);
+
+ if (mScreenOn) {
+ mKeyguardView.show();
+ }
+ }
+
+ private void maybeEnableScreenRotation(boolean enableScreenRotation) {
+ // TODO: move this outside
+ if (enableScreenRotation) {
+ if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen On!");
+ mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
+ } else {
+ if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen Off!");
+ mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+ }
+ mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
+ }
+
+ public void setNeedsInput(boolean needsInput) {
+ mNeedsInput = needsInput;
+ if (mWindowLayoutParams != null) {
+ if (needsInput) {
+ mWindowLayoutParams.flags &=
+ ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ } else {
+ mWindowLayoutParams.flags |=
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ }
+
+ try {
+ mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams);
+ } catch (java.lang.IllegalArgumentException e) {
+ // TODO: Ensure this method isn't called on views that are changing...
+ Log.w(TAG,"Can't update input method on " + mKeyguardHost + " window not attached");
+ }
+ }
+ }
+
+ /**
+ * Reset the state of the view.
+ */
+ public synchronized void reset() {
+ if (DEBUG) Log.d(TAG, "reset()");
+ if (mKeyguardView != null) {
+ mKeyguardView.reset();
+ }
+ }
+
+ public synchronized void onScreenTurnedOff() {
+ if (DEBUG) Log.d(TAG, "onScreenTurnedOff()");
+ mScreenOn = false;
+ if (mKeyguardView != null) {
+ mKeyguardView.onScreenTurnedOff();
+ }
+ }
+
+ public synchronized void onScreenTurnedOn(
+ final KeyguardViewManager.ShowListener showListener) {
+ if (DEBUG) Log.d(TAG, "onScreenTurnedOn()");
+ mScreenOn = true;
+ if (mKeyguardView != null) {
+ mKeyguardView.onScreenTurnedOn();
+
+ // Caller should wait for this window to be shown before turning
+ // on the screen.
+ if (mKeyguardHost.getVisibility() == View.VISIBLE) {
+ // Keyguard may be in the process of being shown, but not yet
+ // updated with the window manager... give it a chance to do so.
+ mKeyguardHost.post(new Runnable() {
+ public void run() {
+ if (mKeyguardHost.getVisibility() == View.VISIBLE) {
+ showListener.onShown(mKeyguardHost.getWindowToken());
+ } else {
+ showListener.onShown(null);
+ }
+ }
+ });
+ } else {
+ showListener.onShown(null);
+ }
+ } else {
+ showListener.onShown(null);
+ }
+ }
+
+ public synchronized void verifyUnlock() {
+ if (DEBUG) Log.d(TAG, "verifyUnlock()");
+ show();
+ mKeyguardView.verifyUnlock();
+ }
+
+ /**
+ * A key has woken the device. We use this to potentially adjust the state
+ * of the lock screen based on the key.
+ *
+ * The 'Tq' suffix is per the documentation in {@link android.view.WindowManagerPolicy}.
+ * Be sure not to take any action that takes a long time; any significant
+ * action should be posted to a handler.
+ *
+ * @param keyCode The wake key. May be {@link KeyEvent#KEYCODE_UNKNOWN} if waking
+ * for a reason other than a key press.
+ */
+ public boolean wakeWhenReadyTq(int keyCode) {
+ if (DEBUG) Log.d(TAG, "wakeWhenReady(" + keyCode + ")");
+ if (mKeyguardView != null) {
+ mKeyguardView.wakeWhenReadyTq(keyCode);
+ return true;
+ } else {
+ Log.w(TAG, "mKeyguardView is null in wakeWhenReadyTq");
+ return false;
+ }
+ }
+
+ /**
+ * Hides the keyguard view
+ */
+ public synchronized void hide() {
+ if (DEBUG) Log.d(TAG, "hide()");
+
+ if (mKeyguardHost != null) {
+ mKeyguardHost.setVisibility(View.GONE);
+ // Don't do this right away, so we can let the view continue to animate
+ // as it goes away.
+ if (mKeyguardView != null) {
+ final KeyguardViewBase lastView = mKeyguardView;
+ mKeyguardView = null;
+ mKeyguardHost.postDelayed(new Runnable() {
+ public void run() {
+ synchronized (KeyguardViewManager.this) {
+ lastView.cleanUp();
+ mKeyguardHost.removeView(lastView);
+ }
+ }
+ }, 500);
+ }
+ }
+ }
+
+ /**
+ * @return Whether the keyguard is showing
+ */
+ public synchronized boolean isShowing() {
+ return (mKeyguardHost != null && mKeyguardHost.getVisibility() == View.VISIBLE);
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
new file mode 100644
index 0000000..d6733ea
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
@@ -0,0 +1,1342 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.widget.LockPatternUtils;
+
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.media.SoundPool;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
+
+
+/**
+ * Mediates requests related to the keyguard. This includes queries about the
+ * state of the keyguard, power management events that effect whether the keyguard
+ * should be shown or reset, callbacks to the phone window manager to notify
+ * it of when the keyguard is showing, and events from the keyguard view itself
+ * stating that the keyguard was succesfully unlocked.
+ *
+ * Note that the keyguard view is shown when the screen is off (as appropriate)
+ * so that once the screen comes on, it will be ready immediately.
+ *
+ * Example queries about the keyguard:
+ * - is {movement, key} one that should wake the keygaurd?
+ * - is the keyguard showing?
+ * - are input events restricted due to the state of the keyguard?
+ *
+ * Callbacks to the phone window manager:
+ * - the keyguard is showing
+ *
+ * Example external events that translate to keyguard view changes:
+ * - screen turned off -> reset the keyguard, and show it so it will be ready
+ * next time the screen turns on
+ * - keyboard is slid open -> if the keyguard is not secure, hide it
+ *
+ * Events from the keyguard view:
+ * - user succesfully unlocked keyguard -> hide keyguard view, and no longer
+ * restrict input events.
+ *
+ * Note: in addition to normal power managment events that effect the state of
+ * whether the keyguard should be showing, external apps and services may request
+ * that the keyguard be disabled via {@link #setKeyguardEnabled(boolean)}. When
+ * false, this will override all other conditions for turning on the keyguard.
+ *
+ * Threading and synchronization:
+ * This class is created by the initialization routine of the {@link WindowManagerPolicy},
+ * and runs on its thread. The keyguard UI is created from that thread in the
+ * constructor of this class. The apis may be called from other threads, including the
+ * {@link com.android.server.input.InputManagerService}'s and {@link android.view.WindowManager}'s.
+ * Therefore, methods on this class are synchronized, and any action that is pointed
+ * directly to the keyguard UI is posted to a {@link Handler} to ensure it is taken on the UI
+ * thread of the keyguard.
+ */
+public class KeyguardViewMediator {
+ private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
+ private final static boolean DEBUG = false;
+ private final static boolean DBG_WAKE = false;
+
+ private final static String TAG = "KeyguardViewMediator";
+
+ private static final String DELAYED_KEYGUARD_ACTION =
+ "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD";
+
+ // used for handler messages
+ private static final int TIMEOUT = 1;
+ private static final int SHOW = 2;
+ private static final int HIDE = 3;
+ private static final int RESET = 4;
+ private static final int VERIFY_UNLOCK = 5;
+ private static final int NOTIFY_SCREEN_OFF = 6;
+ private static final int NOTIFY_SCREEN_ON = 7;
+ private static final int WAKE_WHEN_READY = 8;
+ private static final int KEYGUARD_DONE = 9;
+ private static final int KEYGUARD_DONE_DRAWING = 10;
+ private static final int KEYGUARD_DONE_AUTHENTICATING = 11;
+ private static final int SET_HIDDEN = 12;
+ private static final int KEYGUARD_TIMEOUT = 13;
+
+ /**
+ * The default amount of time we stay awake (used for all key input)
+ */
+ protected static final int AWAKE_INTERVAL_DEFAULT_MS = 10000;
+
+ /**
+ * How long to wait after the screen turns off due to timeout before
+ * turning on the keyguard (i.e, the user has this much time to turn
+ * the screen back on without having to face the keyguard).
+ */
+ private static final int KEYGUARD_LOCK_AFTER_DELAY_DEFAULT = 5000;
+
+ /**
+ * How long we'll wait for the {@link KeyguardViewCallback#keyguardDoneDrawing()}
+ * callback before unblocking a call to {@link #setKeyguardEnabled(boolean)}
+ * that is reenabling the keyguard.
+ */
+ private static final int KEYGUARD_DONE_DRAWING_TIMEOUT_MS = 2000;
+
+ /**
+ * Allow the user to expand the status bar when the keyguard is engaged
+ * (without a pattern or password).
+ */
+ private static final boolean ENABLE_INSECURE_STATUS_BAR_EXPAND = true;
+
+ /** The stream type that the lock sounds are tied to. */
+ private int mMasterStreamType;
+
+ private Context mContext;
+ private AlarmManager mAlarmManager;
+ private AudioManager mAudioManager;
+ private StatusBarManager mStatusBarManager;
+ private boolean mShowLockIcon;
+ private boolean mShowingLockIcon;
+
+ private boolean mSystemReady;
+
+ // Whether the next call to playSounds() should be skipped. Defaults to
+ // true because the first lock (on boot) should be silent.
+ private boolean mSuppressNextLockSound = true;
+
+
+ /** High level access to the power manager for WakeLocks */
+ private PowerManager mPM;
+
+ /**
+ * Used to keep the device awake while the keyguard is showing, i.e for
+ * calls to {@link #pokeWakelock()}
+ */
+ private PowerManager.WakeLock mWakeLock;
+
+ /**
+ * Used to keep the device awake while to ensure the keyguard finishes opening before
+ * we sleep.
+ */
+ private PowerManager.WakeLock mShowKeyguardWakeLock;
+
+ /**
+ * Does not turn on screen, held while a call to {@link KeyguardViewManager#wakeWhenReadyTq(int)}
+ * is called to make sure the device doesn't sleep before it has a chance to poke
+ * the wake lock.
+ * @see #wakeWhenReadyLocked(int)
+ */
+ private PowerManager.WakeLock mWakeAndHandOff;
+
+ private KeyguardViewManager mKeyguardViewManager;
+
+ // these are protected by synchronized (this)
+
+ /**
+ * External apps (like the phone app) can tell us to disable the keygaurd.
+ */
+ private boolean mExternallyEnabled = true;
+
+ /**
+ * Remember if an external call to {@link #setKeyguardEnabled} with value
+ * false caused us to hide the keyguard, so that we need to reshow it once
+ * the keygaurd is reenabled with another call with value true.
+ */
+ private boolean mNeedToReshowWhenReenabled = false;
+
+ // cached value of whether we are showing (need to know this to quickly
+ // answer whether the input should be restricted)
+ private boolean mShowing = false;
+
+ // true if the keyguard is hidden by another window
+ private boolean mHidden = false;
+
+ /**
+ * Helps remember whether the screen has turned on since the last time
+ * it turned off due to timeout. see {@link #onScreenTurnedOff(int)}
+ */
+ private int mDelayedShowingSequence;
+
+ private int mWakelockSequence;
+
+ /**
+ * If the user has disabled the keyguard, then requests to exit, this is
+ * how we'll ultimately let them know whether it was successful. We use this
+ * var being non-null as an indicator that there is an in progress request.
+ */
+ private WindowManagerPolicy.OnKeyguardExitResult mExitSecureCallback;
+
+ // the properties of the keyguard
+
+ private KeyguardUpdateMonitor mUpdateMonitor;
+
+ private boolean mScreenOn;
+
+ // last known state of the cellular connection
+ private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE;
+
+ /**
+ * we send this intent when the keyguard is dismissed.
+ */
+ private Intent mUserPresentIntent;
+
+ /**
+ * {@link #setKeyguardEnabled} waits on this condition when it reenables
+ * the keyguard.
+ */
+ private boolean mWaitingUntilKeyguardVisible = false;
+ private LockPatternUtils mLockPatternUtils;
+
+ private SoundPool mLockSounds;
+ private int mLockSoundId;
+ private int mUnlockSoundId;
+ private int mLockSoundStreamId;
+
+ /**
+ * The volume applied to the lock/unlock sounds.
+ */
+ private final float mLockSoundVolume;
+
+ /**
+ * The callback used by the keyguard view to tell the {@link KeyguardViewMediator}
+ * various things.
+ */
+ public interface ViewMediatorCallback {
+
+ /**
+ * Request the wakelock to be poked for the default amount of time.
+ */
+ void pokeWakelock();
+
+ /**
+ * Request the wakelock to be poked for a specific amount of time.
+ * @param millis The amount of time in millis.
+ */
+ void pokeWakelock(long millis);
+
+ /**
+ * Report that the keyguard is done.
+ * @param authenticated Whether the user securely got past the keyguard.
+ * the only reason for this to be false is if the keyguard was instructed
+ * to appear temporarily to verify the user is supposed to get past the
+ * keyguard, and the user fails to do so.
+ */
+ void keyguardDone(boolean authenticated);
+
+ /**
+ * Report that the keyguard is done drawing.
+ */
+ void keyguardDoneDrawing();
+
+ /**
+ * Tell ViewMediator that the current view needs IME input
+ * @param needsInput
+ */
+ void setNeedsInput(boolean needsInput);
+ }
+
+ KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
+
+ @Override
+ public void onUserSwitched(int userId) {
+ mLockPatternUtils.setCurrentUser(userId);
+ synchronized (KeyguardViewMediator.this) {
+ resetStateLocked();
+ }
+ }
+
+ @Override
+ public void onUserRemoved(int userId) {
+ mLockPatternUtils.removeUser(userId);
+ }
+
+ @Override
+ void onPhoneStateChanged(int phoneState) {
+ synchronized (KeyguardViewMediator.this) {
+ if (TelephonyManager.CALL_STATE_IDLE == phoneState // call ending
+ && !mScreenOn // screen off
+ && mExternallyEnabled) { // not disabled by any app
+
+ // note: this is a way to gracefully reenable the keyguard when the call
+ // ends and the screen is off without always reenabling the keyguard
+ // each time the screen turns off while in call (and having an occasional ugly
+ // flicker while turning back on the screen and disabling the keyguard again).
+ if (DEBUG) Log.d(TAG, "screen is off and call ended, let's make sure the "
+ + "keyguard is showing");
+ doKeyguardLocked();
+ }
+ }
+ };
+
+ @Override
+ public void onClockVisibilityChanged() {
+ adjustStatusBarLocked();
+ }
+
+ @Override
+ public void onDeviceProvisioned() {
+ mContext.sendBroadcast(mUserPresentIntent);
+ }
+
+ @Override
+ public void onSimStateChanged(IccCardConstants.State simState) {
+ if (DEBUG) Log.d(TAG, "onSimStateChanged: " + simState);
+
+ switch (simState) {
+ case NOT_READY:
+ case ABSENT:
+ // only force lock screen in case of missing sim if user hasn't
+ // gone through setup wizard
+ synchronized (this) {
+ if (!mUpdateMonitor.isDeviceProvisioned()) {
+ if (!isShowing()) {
+ if (DEBUG) Log.d(TAG, "ICC_ABSENT isn't showing,"
+ + " we need to show the keyguard since the "
+ + "device isn't provisioned yet.");
+ doKeyguardLocked();
+ } else {
+ resetStateLocked();
+ }
+ }
+ }
+ break;
+ case PIN_REQUIRED:
+ case PUK_REQUIRED:
+ synchronized (this) {
+ if (!isShowing()) {
+ if (DEBUG) Log.d(TAG, "INTENT_VALUE_ICC_LOCKED and keygaurd isn't "
+ + "showing; need to show keyguard so user can enter sim pin");
+ doKeyguardLocked();
+ } else {
+ resetStateLocked();
+ }
+ }
+ break;
+ case PERM_DISABLED:
+ synchronized (this) {
+ if (!isShowing()) {
+ if (DEBUG) Log.d(TAG, "PERM_DISABLED and "
+ + "keygaurd isn't showing.");
+ doKeyguardLocked();
+ } else {
+ if (DEBUG) Log.d(TAG, "PERM_DISABLED, resetStateLocked to"
+ + "show permanently disabled message in lockscreen.");
+ resetStateLocked();
+ }
+ }
+ break;
+ case READY:
+ synchronized (this) {
+ if (isShowing()) {
+ resetStateLocked();
+ }
+ }
+ break;
+ }
+ }
+
+ };
+
+ ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() {
+ public void pokeWakelock() {
+ KeyguardViewMediator.this.pokeWakelock();
+ }
+
+ public void pokeWakelock(long holdMs) {
+ KeyguardViewMediator.this.pokeWakelock(holdMs);
+ }
+
+ public void keyguardDone(boolean authenticated) {
+ KeyguardViewMediator.this.keyguardDone(authenticated, true);
+ }
+
+ public void keyguardDoneDrawing() {
+ mHandler.sendEmptyMessage(KEYGUARD_DONE_DRAWING);
+ }
+
+ @Override
+ public void setNeedsInput(boolean needsInput) {
+ mKeyguardViewManager.setNeedsInput(needsInput);
+ }
+ };
+
+ public void pokeWakelock() {
+ pokeWakelock(AWAKE_INTERVAL_DEFAULT_MS);
+ }
+
+ public void pokeWakelock(long holdMs) {
+ synchronized (this) {
+ if (DBG_WAKE) Log.d(TAG, "pokeWakelock(" + holdMs + ")");
+ mWakeLock.acquire();
+ mHandler.removeMessages(TIMEOUT);
+ mWakelockSequence++;
+ Message msg = mHandler.obtainMessage(TIMEOUT, mWakelockSequence, 0);
+ mHandler.sendMessageDelayed(msg, holdMs);
+ }
+ }
+
+ /**
+ * Construct a KeyguardViewMediator
+ * @param context
+ * @param lockPatternUtils optional mock interface for LockPatternUtils
+ */
+ public KeyguardViewMediator(Context context, LockPatternUtils lockPatternUtils) {
+ mContext = context;
+ mPM = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = mPM.newWakeLock(
+ PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "keyguard");
+ mWakeLock.setReferenceCounted(false);
+ mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
+ mShowKeyguardWakeLock.setReferenceCounted(false);
+
+ mWakeAndHandOff = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "keyguardWakeAndHandOff");
+ mWakeAndHandOff.setReferenceCounted(false);
+
+ mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(DELAYED_KEYGUARD_ACTION));
+
+ mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+ mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+
+ mLockPatternUtils = lockPatternUtils != null
+ ? lockPatternUtils : new LockPatternUtils(mContext);
+
+ WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+
+ mKeyguardViewManager = new KeyguardViewManager(context, wm, mViewMediatorCallback,
+ mLockPatternUtils);
+
+ mUserPresentIntent = new Intent(Intent.ACTION_USER_PRESENT);
+ mUserPresentIntent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+ | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+
+ final ContentResolver cr = mContext.getContentResolver();
+ mShowLockIcon = (Settings.System.getInt(cr, "show_status_bar_lock", 0) == 1);
+
+ mScreenOn = mPM.isScreenOn();
+
+ mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0);
+ String soundPath = Settings.System.getString(cr, Settings.System.LOCK_SOUND);
+ if (soundPath != null) {
+ mLockSoundId = mLockSounds.load(soundPath, 1);
+ }
+ if (soundPath == null || mLockSoundId == 0) {
+ if (DEBUG) Log.d(TAG, "failed to load sound from " + soundPath);
+ }
+ soundPath = Settings.System.getString(cr, Settings.System.UNLOCK_SOUND);
+ if (soundPath != null) {
+ mUnlockSoundId = mLockSounds.load(soundPath, 1);
+ }
+ if (soundPath == null || mUnlockSoundId == 0) {
+ if (DEBUG) Log.d(TAG, "failed to load sound from " + soundPath);
+ }
+ int lockSoundDefaultAttenuation = context.getResources().getInteger(
+ com.android.internal.R.integer.config_lockSoundVolumeDb);
+ mLockSoundVolume = (float)Math.pow(10, (float)lockSoundDefaultAttenuation/20);
+ }
+
+ /**
+ * Let us know that the system is ready after startup.
+ */
+ public void onSystemReady() {
+ synchronized (this) {
+ if (DEBUG) Log.d(TAG, "onSystemReady");
+ mSystemReady = true;
+ mUpdateMonitor.registerCallback(mUpdateCallback);
+ doKeyguardLocked();
+ }
+ }
+
+ /**
+ * Called to let us know the screen was turned off.
+ * @param why either {@link WindowManagerPolicy#OFF_BECAUSE_OF_USER},
+ * {@link WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT} or
+ * {@link WindowManagerPolicy#OFF_BECAUSE_OF_PROX_SENSOR}.
+ */
+ public void onScreenTurnedOff(int why) {
+ synchronized (this) {
+ mScreenOn = false;
+ if (DEBUG) Log.d(TAG, "onScreenTurnedOff(" + why + ")");
+
+ // Lock immediately based on setting if secure (user has a pin/pattern/password).
+ // This also "locks" the device when not secure to provide easy access to the
+ // camera while preventing unwanted input.
+ final boolean lockImmediately =
+ mLockPatternUtils.getPowerButtonInstantlyLocks() || !mLockPatternUtils.isSecure();
+
+ if (mExitSecureCallback != null) {
+ if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled");
+ mExitSecureCallback.onKeyguardExitResult(false);
+ mExitSecureCallback = null;
+ if (!mExternallyEnabled) {
+ hideLocked();
+ }
+ } else if (mShowing) {
+ notifyScreenOffLocked();
+ resetStateLocked();
+ } else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT
+ || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) {
+ // if the screen turned off because of timeout or the user hit the power button
+ // and we don't need to lock immediately, set an alarm
+ // to enable it a little bit later (i.e, give the user a chance
+ // to turn the screen back on within a certain window without
+ // having to unlock the screen)
+ final ContentResolver cr = mContext.getContentResolver();
+
+ // From DisplaySettings
+ long displayTimeout = Settings.System.getInt(cr, SCREEN_OFF_TIMEOUT,
+ KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT);
+
+ // From SecuritySettings
+ final long lockAfterTimeout = Settings.Secure.getInt(cr,
+ Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+ KEYGUARD_LOCK_AFTER_DELAY_DEFAULT);
+
+ // From DevicePolicyAdmin
+ final long policyTimeout = mLockPatternUtils.getDevicePolicyManager()
+ .getMaximumTimeToLock(null);
+
+ long timeout;
+ if (policyTimeout > 0) {
+ // policy in effect. Make sure we don't go beyond policy limit.
+ displayTimeout = Math.max(displayTimeout, 0); // ignore negative values
+ timeout = Math.min(policyTimeout - displayTimeout, lockAfterTimeout);
+ } else {
+ timeout = lockAfterTimeout;
+ }
+
+ if (timeout <= 0) {
+ // Lock now
+ mSuppressNextLockSound = true;
+ doKeyguardLocked();
+ } else {
+ // Lock in the future
+ long when = SystemClock.elapsedRealtime() + timeout;
+ Intent intent = new Intent(DELAYED_KEYGUARD_ACTION);
+ intent.putExtra("seq", mDelayedShowingSequence);
+ PendingIntent sender = PendingIntent.getBroadcast(mContext,
+ 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, sender);
+ if (DEBUG) Log.d(TAG, "setting alarm to turn off keyguard, seq = "
+ + mDelayedShowingSequence);
+ }
+ } else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR) {
+ // Do not enable the keyguard if the prox sensor forced the screen off.
+ } else {
+ doKeyguardLocked();
+ }
+ }
+ }
+
+ /**
+ * Let's us know the screen was turned on.
+ */
+ public void onScreenTurnedOn(KeyguardViewManager.ShowListener showListener) {
+ synchronized (this) {
+ mScreenOn = true;
+ mDelayedShowingSequence++;
+ if (DEBUG) Log.d(TAG, "onScreenTurnedOn, seq = " + mDelayedShowingSequence);
+ if (showListener != null) {
+ notifyScreenOnLocked(showListener);
+ }
+ }
+ }
+
+ /**
+ * Same semantics as {@link WindowManagerPolicy#enableKeyguard}; provide
+ * a way for external stuff to override normal keyguard behavior. For instance
+ * the phone app disables the keyguard when it receives incoming calls.
+ */
+ public void setKeyguardEnabled(boolean enabled) {
+ synchronized (this) {
+ if (DEBUG) Log.d(TAG, "setKeyguardEnabled(" + enabled + ")");
+
+ mExternallyEnabled = enabled;
+
+ if (!enabled && mShowing) {
+ if (mExitSecureCallback != null) {
+ if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring");
+ // we're in the process of handling a request to verify the user
+ // can get past the keyguard. ignore extraneous requests to disable / reenable
+ return;
+ }
+
+ // hiding keyguard that is showing, remember to reshow later
+ if (DEBUG) Log.d(TAG, "remembering to reshow, hiding keyguard, "
+ + "disabling status bar expansion");
+ mNeedToReshowWhenReenabled = true;
+ hideLocked();
+ } else if (enabled && mNeedToReshowWhenReenabled) {
+ // reenabled after previously hidden, reshow
+ if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling "
+ + "status bar expansion");
+ mNeedToReshowWhenReenabled = false;
+
+ if (mExitSecureCallback != null) {
+ if (DEBUG) Log.d(TAG, "onKeyguardExitResult(false), resetting");
+ mExitSecureCallback.onKeyguardExitResult(false);
+ mExitSecureCallback = null;
+ resetStateLocked();
+ } else {
+ showLocked();
+
+ // block until we know the keygaurd is done drawing (and post a message
+ // to unblock us after a timeout so we don't risk blocking too long
+ // and causing an ANR).
+ mWaitingUntilKeyguardVisible = true;
+ mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
+ if (DEBUG) Log.d(TAG, "waiting until mWaitingUntilKeyguardVisible is false");
+ while (mWaitingUntilKeyguardVisible) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ if (DEBUG) Log.d(TAG, "done waiting for mWaitingUntilKeyguardVisible");
+ }
+ }
+ }
+ }
+
+ /**
+ * @see android.app.KeyguardManager#exitKeyguardSecurely
+ */
+ public void verifyUnlock(WindowManagerPolicy.OnKeyguardExitResult callback) {
+ synchronized (this) {
+ if (DEBUG) Log.d(TAG, "verifyUnlock");
+ if (!mUpdateMonitor.isDeviceProvisioned()) {
+ // don't allow this api when the device isn't provisioned
+ if (DEBUG) Log.d(TAG, "ignoring because device isn't provisioned");
+ callback.onKeyguardExitResult(false);
+ } else if (mExternallyEnabled) {
+ // this only applies when the user has externally disabled the
+ // keyguard. this is unexpected and means the user is not
+ // using the api properly.
+ Log.w(TAG, "verifyUnlock called when not externally disabled");
+ callback.onKeyguardExitResult(false);
+ } else if (mExitSecureCallback != null) {
+ // already in progress with someone else
+ callback.onKeyguardExitResult(false);
+ } else {
+ mExitSecureCallback = callback;
+ verifyUnlockLocked();
+ }
+ }
+ }
+
+ /**
+ * Is the keyguard currently showing?
+ */
+ public boolean isShowing() {
+ return mShowing;
+ }
+
+ /**
+ * Is the keyguard currently showing and not being force hidden?
+ */
+ public boolean isShowingAndNotHidden() {
+ return mShowing && !mHidden;
+ }
+
+ /**
+ * Notify us when the keyguard is hidden by another window
+ */
+ public void setHidden(boolean isHidden) {
+ if (DEBUG) Log.d(TAG, "setHidden " + isHidden);
+ mHandler.removeMessages(SET_HIDDEN);
+ Message msg = mHandler.obtainMessage(SET_HIDDEN, (isHidden ? 1 : 0), 0);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Handles SET_HIDDEN message sent by setHidden()
+ */
+ private void handleSetHidden(boolean isHidden) {
+ synchronized (KeyguardViewMediator.this) {
+ if (mHidden != isHidden) {
+ mHidden = isHidden;
+ updateActivityLockScreenState();
+ adjustUserActivityLocked();
+ adjustStatusBarLocked();
+ }
+ }
+ }
+
+ /**
+ * Used by PhoneWindowManager to enable the keyguard due to a user activity timeout.
+ * This must be safe to call from any thread and with any window manager locks held.
+ */
+ public void doKeyguardTimeout() {
+ mHandler.removeMessages(KEYGUARD_TIMEOUT);
+ Message msg = mHandler.obtainMessage(KEYGUARD_TIMEOUT);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Given the state of the keyguard, is the input restricted?
+ * Input is restricted when the keyguard is showing, or when the keyguard
+ * was suppressed by an app that disabled the keyguard or we haven't been provisioned yet.
+ */
+ public boolean isInputRestricted() {
+ return mShowing || mNeedToReshowWhenReenabled || !mUpdateMonitor.isDeviceProvisioned();
+ }
+
+ /**
+ * Enable the keyguard if the settings are appropriate. Return true if all
+ * work that will happen is done; returns false if the caller can wait for
+ * the keyguard to be shown.
+ */
+ private void doKeyguardLocked() {
+ // if another app is disabling us, don't show
+ if (!mExternallyEnabled) {
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
+
+ // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes
+ // for an occasional ugly flicker in this situation:
+ // 1) receive a call with the screen on (no keyguard) or make a call
+ // 2) screen times out
+ // 3) user hits key to turn screen back on
+ // instead, we reenable the keyguard when we know the screen is off and the call
+ // ends (see the broadcast receiver below)
+ // TODO: clean this up when we have better support at the window manager level
+ // for apps that wish to be on top of the keyguard
+ return;
+ }
+
+ // if the keyguard is already showing, don't bother
+ if (mKeyguardViewManager.isShowing()) {
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
+ return;
+ }
+
+ // if the setup wizard hasn't run yet, don't show
+ final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim",
+ false);
+ final boolean provisioned = mUpdateMonitor.isDeviceProvisioned();
+ final IccCardConstants.State state = mUpdateMonitor.getSimState();
+ final boolean lockedOrMissing = state.isPinLocked()
+ || ((state == IccCardConstants.State.ABSENT
+ || state == IccCardConstants.State.PERM_DISABLED)
+ && requireSim);
+
+ if (!lockedOrMissing && !provisioned) {
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned"
+ + " and the sim is not locked or missing");
+ return;
+ }
+
+ if (mLockPatternUtils.isLockScreenDisabled() && !lockedOrMissing) {
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
+ showLocked();
+ }
+
+ /**
+ * Send message to keyguard telling it to reset its state.
+ * @see #handleReset()
+ */
+ private void resetStateLocked() {
+ if (DEBUG) Log.d(TAG, "resetStateLocked");
+ Message msg = mHandler.obtainMessage(RESET);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Send message to keyguard telling it to verify unlock
+ * @see #handleVerifyUnlock()
+ */
+ private void verifyUnlockLocked() {
+ if (DEBUG) Log.d(TAG, "verifyUnlockLocked");
+ mHandler.sendEmptyMessage(VERIFY_UNLOCK);
+ }
+
+
+ /**
+ * Send a message to keyguard telling it the screen just turned on.
+ * @see #onScreenTurnedOff(int)
+ * @see #handleNotifyScreenOff
+ */
+ private void notifyScreenOffLocked() {
+ if (DEBUG) Log.d(TAG, "notifyScreenOffLocked");
+ mHandler.sendEmptyMessage(NOTIFY_SCREEN_OFF);
+ }
+
+ /**
+ * Send a message to keyguard telling it the screen just turned on.
+ * @see #onScreenTurnedOn()
+ * @see #handleNotifyScreenOn
+ */
+ private void notifyScreenOnLocked(KeyguardViewManager.ShowListener showListener) {
+ if (DEBUG) Log.d(TAG, "notifyScreenOnLocked");
+ Message msg = mHandler.obtainMessage(NOTIFY_SCREEN_ON, showListener);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Send message to keyguard telling it about a wake key so it can adjust
+ * its state accordingly and then poke the wake lock when it is ready.
+ * @param keyCode The wake key.
+ * @see #handleWakeWhenReady
+ * @see #onWakeKeyWhenKeyguardShowingTq(int)
+ */
+ private void wakeWhenReadyLocked(int keyCode) {
+ if (DBG_WAKE) Log.d(TAG, "wakeWhenReadyLocked(" + keyCode + ")");
+
+ /**
+ * acquire the handoff lock that will keep the cpu running. this will
+ * be released once the keyguard has set itself up and poked the other wakelock
+ * in {@link #handleWakeWhenReady(int)}
+ */
+ mWakeAndHandOff.acquire();
+
+ Message msg = mHandler.obtainMessage(WAKE_WHEN_READY, keyCode, 0);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Send message to keyguard telling it to show itself
+ * @see #handleShow()
+ */
+ private void showLocked() {
+ if (DEBUG) Log.d(TAG, "showLocked");
+ // ensure we stay awake until we are finished displaying the keyguard
+ mShowKeyguardWakeLock.acquire();
+ Message msg = mHandler.obtainMessage(SHOW);
+ mHandler.sendMessage(msg);
+ }
+
+ /**
+ * Send message to keyguard telling it to hide itself
+ * @see #handleHide()
+ */
+ private void hideLocked() {
+ if (DEBUG) Log.d(TAG, "hideLocked");
+ Message msg = mHandler.obtainMessage(HIDE);
+ mHandler.sendMessage(msg);
+ }
+
+ public boolean isSecure() {
+ return mLockPatternUtils.isSecure()
+ || KeyguardUpdateMonitor.getInstance(mContext).isSimPinSecure();
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DELAYED_KEYGUARD_ACTION.equals(intent.getAction())) {
+ final int sequence = intent.getIntExtra("seq", 0);
+ if (DEBUG) Log.d(TAG, "received DELAYED_KEYGUARD_ACTION with seq = "
+ + sequence + ", mDelayedShowingSequence = " + mDelayedShowingSequence);
+ synchronized (KeyguardViewMediator.this) {
+ if (mDelayedShowingSequence == sequence) {
+ // Don't play lockscreen SFX if the screen went off due to timeout.
+ mSuppressNextLockSound = true;
+ doKeyguardLocked();
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * When a key is received when the screen is off and the keyguard is showing,
+ * we need to decide whether to actually turn on the screen, and if so, tell
+ * the keyguard to prepare itself and poke the wake lock when it is ready.
+ *
+ * The 'Tq' suffix is per the documentation in {@link WindowManagerPolicy}.
+ * Be sure not to take any action that takes a long time; any significant
+ * action should be posted to a handler.
+ *
+ * @param keyCode The keycode of the key that woke the device
+ * @param isDocked True if the device is in the dock
+ * @return Whether we poked the wake lock (and turned the screen on)
+ */
+ public boolean onWakeKeyWhenKeyguardShowingTq(int keyCode, boolean isDocked) {
+ if (DEBUG) Log.d(TAG, "onWakeKeyWhenKeyguardShowing(" + keyCode + ")");
+
+ if (isWakeKeyWhenKeyguardShowing(keyCode, isDocked)) {
+ // give the keyguard view manager a chance to adjust the state of the
+ // keyguard based on the key that woke the device before poking
+ // the wake lock
+ wakeWhenReadyLocked(keyCode);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * When the keyguard is showing we ignore some keys that might otherwise typically
+ * be considered wake keys. We filter them out here.
+ *
+ * {@link KeyEvent#KEYCODE_POWER} is notably absent from this list because it
+ * is always considered a wake key.
+ */
+ private boolean isWakeKeyWhenKeyguardShowing(int keyCode, boolean isDocked) {
+ switch (keyCode) {
+ // ignore volume keys unless docked
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ return isDocked;
+
+ // ignore media and camera keys
+ 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_CAMERA:
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * When a wake motion such as an external mouse movement is received when the screen
+ * is off and the keyguard is showing, we need to decide whether to actually turn
+ * on the screen, and if so, tell the keyguard to prepare itself and poke the wake
+ * lock when it is ready.
+ *
+ * The 'Tq' suffix is per the documentation in {@link WindowManagerPolicy}.
+ * Be sure not to take any action that takes a long time; any significant
+ * action should be posted to a handler.
+ *
+ * @return Whether we poked the wake lock (and turned the screen on)
+ */
+ public boolean onWakeMotionWhenKeyguardShowingTq() {
+ if (DEBUG) Log.d(TAG, "onWakeMotionWhenKeyguardShowing()");
+
+ // give the keyguard view manager a chance to adjust the state of the
+ // keyguard based on the key that woke the device before poking
+ // the wake lock
+ wakeWhenReadyLocked(KeyEvent.KEYCODE_UNKNOWN);
+ return true;
+ }
+
+ public void keyguardDone(boolean authenticated, boolean wakeup) {
+ synchronized (this) {
+ EventLog.writeEvent(70000, 2);
+ if (DEBUG) Log.d(TAG, "keyguardDone(" + authenticated + ")");
+ Message msg = mHandler.obtainMessage(KEYGUARD_DONE);
+ msg.arg1 = wakeup ? 1 : 0;
+ mHandler.sendMessage(msg);
+
+ if (authenticated) {
+ mUpdateMonitor.clearFailedAttempts();
+ }
+
+ if (mExitSecureCallback != null) {
+ mExitSecureCallback.onKeyguardExitResult(authenticated);
+ mExitSecureCallback = null;
+
+ if (authenticated) {
+ // after succesfully exiting securely, no need to reshow
+ // the keyguard when they've released the lock
+ mExternallyEnabled = true;
+ mNeedToReshowWhenReenabled = false;
+ }
+ }
+ }
+ }
+
+ /**
+ * This handler will be associated with the policy thread, which will also
+ * be the UI thread of the keyguard. Since the apis of the policy, and therefore
+ * this class, can be called by other threads, any action that directly
+ * interacts with the keyguard ui should be posted to this handler, rather
+ * than called directly.
+ */
+ private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case TIMEOUT:
+ handleTimeout(msg.arg1);
+ return ;
+ case SHOW:
+ handleShow();
+ return ;
+ case HIDE:
+ handleHide();
+ return ;
+ case RESET:
+ handleReset();
+ return ;
+ case VERIFY_UNLOCK:
+ handleVerifyUnlock();
+ return;
+ case NOTIFY_SCREEN_OFF:
+ handleNotifyScreenOff();
+ return;
+ case NOTIFY_SCREEN_ON:
+ handleNotifyScreenOn((KeyguardViewManager.ShowListener)msg.obj);
+ return;
+ case WAKE_WHEN_READY:
+ handleWakeWhenReady(msg.arg1);
+ return;
+ case KEYGUARD_DONE:
+ handleKeyguardDone(msg.arg1 != 0);
+ return;
+ case KEYGUARD_DONE_DRAWING:
+ handleKeyguardDoneDrawing();
+ return;
+ case KEYGUARD_DONE_AUTHENTICATING:
+ keyguardDone(true, true);
+ return;
+ case SET_HIDDEN:
+ handleSetHidden(msg.arg1 != 0);
+ break;
+ case KEYGUARD_TIMEOUT:
+ synchronized (KeyguardViewMediator.this) {
+ doKeyguardLocked();
+ }
+ break;
+ }
+ }
+ };
+
+ /**
+ * @see #keyguardDone
+ * @see #KEYGUARD_DONE
+ */
+ private void handleKeyguardDone(boolean wakeup) {
+ if (DEBUG) Log.d(TAG, "handleKeyguardDone");
+ handleHide();
+ if (wakeup) {
+ mPM.wakeUp(SystemClock.uptimeMillis());
+ }
+ mWakeLock.release();
+
+ if (!(mContext instanceof Activity)) {
+ final UserHandle currentUser = new UserHandle(mLockPatternUtils.getCurrentUser());
+ mContext.sendBroadcastAsUser(mUserPresentIntent, currentUser);
+ }
+ }
+
+ /**
+ * @see #keyguardDoneDrawing
+ * @see #KEYGUARD_DONE_DRAWING
+ */
+ private void handleKeyguardDoneDrawing() {
+ synchronized(this) {
+ if (false) Log.d(TAG, "handleKeyguardDoneDrawing");
+ if (mWaitingUntilKeyguardVisible) {
+ if (DEBUG) Log.d(TAG, "handleKeyguardDoneDrawing: notifying mWaitingUntilKeyguardVisible");
+ mWaitingUntilKeyguardVisible = false;
+ notifyAll();
+
+ // there will usually be two of these sent, one as a timeout, and one
+ // as a result of the callback, so remove any remaining messages from
+ // the queue
+ mHandler.removeMessages(KEYGUARD_DONE_DRAWING);
+ }
+ }
+ }
+
+ /**
+ * Handles the message sent by {@link #pokeWakelock}
+ * @param seq used to determine if anything has changed since the message
+ * was sent.
+ * @see #TIMEOUT
+ */
+ private void handleTimeout(int seq) {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleTimeout");
+ if (seq == mWakelockSequence) {
+ mWakeLock.release();
+ }
+ }
+ }
+
+ private void playSounds(boolean locked) {
+ // User feedback for keyguard.
+
+ if (mSuppressNextLockSound) {
+ mSuppressNextLockSound = false;
+ return;
+ }
+
+ final ContentResolver cr = mContext.getContentResolver();
+ if (Settings.System.getInt(cr, Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) == 1) {
+ final int whichSound = locked
+ ? mLockSoundId
+ : mUnlockSoundId;
+ mLockSounds.stop(mLockSoundStreamId);
+ // Init mAudioManager
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ if (mAudioManager == null) return;
+ mMasterStreamType = mAudioManager.getMasterStreamType();
+ }
+ // If the stream is muted, don't play the sound
+ if (mAudioManager.isStreamMute(mMasterStreamType)) return;
+
+ mLockSoundStreamId = mLockSounds.play(whichSound,
+ mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
+ }
+ }
+
+ private void updateActivityLockScreenState() {
+ try {
+ ActivityManagerNative.getDefault().setLockScreenShown(
+ mShowing && !mHidden);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #showLocked}.
+ * @see #SHOW
+ */
+ private void handleShow() {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleShow");
+ if (!mSystemReady) return;
+
+ mKeyguardViewManager.show();
+ mShowing = true;
+ updateActivityLockScreenState();
+ adjustUserActivityLocked();
+ adjustStatusBarLocked();
+ try {
+ ActivityManagerNative.getDefault().closeSystemDialogs("lock");
+ } catch (RemoteException e) {
+ }
+
+ // Do this at the end to not slow down display of the keyguard.
+ playSounds(true);
+
+ mShowKeyguardWakeLock.release();
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #hideLocked()}
+ * @see #HIDE
+ */
+ private void handleHide() {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleHide");
+ if (mWakeAndHandOff.isHeld()) {
+ Log.w(TAG, "attempt to hide the keyguard while waking, ignored");
+ return;
+ }
+
+ // only play "unlock" noises if not on a call (since the incall UI
+ // disables the keyguard)
+ if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) {
+ playSounds(false);
+ }
+
+ mKeyguardViewManager.hide();
+ mShowing = false;
+ updateActivityLockScreenState();
+ adjustUserActivityLocked();
+ adjustStatusBarLocked();
+ }
+ }
+
+ private void adjustUserActivityLocked() {
+ // disable user activity if we are shown and not hidden
+ if (DEBUG) Log.d(TAG, "adjustUserActivityLocked mShowing: " + mShowing + " mHidden: " + mHidden);
+ boolean enabled = !mShowing || mHidden;
+ // FIXME: Replace this with a new timeout control mechanism.
+ //mRealPowerManager.enableUserActivity(enabled);
+ if (!enabled && mScreenOn) {
+ // reinstate our short screen timeout policy
+ pokeWakelock();
+ }
+ }
+
+ private void adjustStatusBarLocked() {
+ if (mStatusBarManager == null) {
+ mStatusBarManager = (StatusBarManager)
+ mContext.getSystemService(Context.STATUS_BAR_SERVICE);
+ }
+ if (mStatusBarManager == null) {
+ Log.w(TAG, "Could not get status bar manager");
+ } else {
+ if (mShowLockIcon) {
+ // Give feedback to user when secure keyguard is active and engaged
+ if (mShowing && isSecure()) {
+ if (!mShowingLockIcon) {
+ String contentDescription = mContext.getString(
+ com.android.internal.R.string.status_bar_device_locked);
+ mStatusBarManager.setIcon("secure",
+ com.android.internal.R.drawable.stat_sys_secure, 0,
+ contentDescription);
+ mShowingLockIcon = true;
+ }
+ } else {
+ if (mShowingLockIcon) {
+ mStatusBarManager.removeIcon("secure");
+ mShowingLockIcon = false;
+ }
+ }
+ }
+
+ // Disable aspects of the system/status/navigation bars that must not be re-enabled by
+ // windows that appear on top, ever
+ int flags = StatusBarManager.DISABLE_NONE;
+ if (mShowing) {
+ // disable navigation status bar components (home, recents) if lock screen is up
+ flags |= StatusBarManager.DISABLE_RECENT;
+ if (isSecure() || !ENABLE_INSECURE_STATUS_BAR_EXPAND) {
+ // showing secure lockscreen; disable expanding.
+ flags |= StatusBarManager.DISABLE_EXPAND;
+ }
+ if (isSecure()) {
+ // showing secure lockscreen; disable ticker.
+ flags |= StatusBarManager.DISABLE_NOTIFICATION_TICKER;
+ }
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mHidden=" + mHidden
+ + " isSecure=" + isSecure() + " --> flags=0x" + Integer.toHexString(flags));
+ }
+
+ mStatusBarManager.disable(flags);
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #wakeWhenReadyLocked(int)}
+ * @param keyCode The key that woke the device.
+ * @see #WAKE_WHEN_READY
+ */
+ private void handleWakeWhenReady(int keyCode) {
+ synchronized (KeyguardViewMediator.this) {
+ if (DBG_WAKE) Log.d(TAG, "handleWakeWhenReady(" + keyCode + ")");
+
+ // this should result in a call to 'poke wakelock' which will set a timeout
+ // on releasing the wakelock
+ if (!mKeyguardViewManager.wakeWhenReadyTq(keyCode)) {
+ // poke wakelock ourselves if keyguard is no longer active
+ Log.w(TAG, "mKeyguardViewManager.wakeWhenReadyTq did not poke wake lock, so poke it ourselves");
+ pokeWakelock();
+ }
+
+ /**
+ * Now that the keyguard is ready and has poked the wake lock, we can
+ * release the handoff wakelock
+ */
+ mWakeAndHandOff.release();
+
+ if (!mWakeLock.isHeld()) {
+ Log.w(TAG, "mWakeLock not held in mKeyguardViewManager.wakeWhenReadyTq");
+ }
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #resetStateLocked()}
+ * @see #RESET
+ */
+ private void handleReset() {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleReset");
+ mKeyguardViewManager.reset();
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #verifyUnlock}
+ * @see #RESET
+ */
+ private void handleVerifyUnlock() {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleVerifyUnlock");
+ mKeyguardViewManager.verifyUnlock();
+ mShowing = true;
+ updateActivityLockScreenState();
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #notifyScreenOffLocked()}
+ * @see #NOTIFY_SCREEN_OFF
+ */
+ private void handleNotifyScreenOff() {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleNotifyScreenOff");
+ mKeyguardViewManager.onScreenTurnedOff();
+ }
+ }
+
+ /**
+ * Handle message sent by {@link #notifyScreenOnLocked()}
+ * @see #NOTIFY_SCREEN_ON
+ */
+ private void handleNotifyScreenOn(KeyguardViewManager.ShowListener showListener) {
+ synchronized (KeyguardViewMediator.this) {
+ if (DEBUG) Log.d(TAG, "handleNotifyScreenOn");
+ mKeyguardViewManager.onScreenTurnedOn(showListener);
+ }
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetView.java
new file mode 100644
index 0000000..120f8f8
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetView.java
@@ -0,0 +1,140 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+public class KeyguardWidgetView extends PagedView {
+
+ public KeyguardWidgetView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeyguardWidgetView(Context context) {
+ this(null, null, 0);
+ }
+
+ public KeyguardWidgetView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ }
+
+ ZInterpolator mZInterpolator = new ZInterpolator(0.5f);
+ private static float CAMERA_DISTANCE = 1500;
+ private static float TRANSITION_SCALE_FACTOR = 0.74f;
+ private static float TRANSITION_PIVOT = 0.65f;
+ private static float TRANSITION_MAX_ROTATION = 30;
+ private static final boolean PERFORM_OVERSCROLL_ROTATION = true;
+ private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f);
+ private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4);
+ /*
+ * This interpolator emulates the rate at which the perceived scale of an object changes
+ * as its distance from a camera increases. When this interpolator is applied to a scale
+ * animation on a view, it evokes the sense that the object is shrinking due to moving away
+ * from the camera.
+ */
+ static class ZInterpolator implements TimeInterpolator {
+ private float focalLength;
+
+ public ZInterpolator(float foc) {
+ focalLength = foc;
+ }
+
+ public float getInterpolation(float input) {
+ return (1.0f - focalLength / (focalLength + input)) /
+ (1.0f - focalLength / (focalLength + 1.0f));
+ }
+ }
+
+ // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack.
+ @Override
+ protected void screenScrolled(int screenCenter) {
+ super.screenScrolled(screenCenter);
+
+ for (int i = 0; i < getChildCount(); i++) {
+ View v = getPageAt(i);
+ if (v != null) {
+ float scrollProgress = getScrollProgress(screenCenter, v, i);
+
+ float interpolatedProgress =
+ mZInterpolator.getInterpolation(Math.abs(Math.min(scrollProgress, 0)));
+ float scale = (1 - interpolatedProgress) +
+ interpolatedProgress * TRANSITION_SCALE_FACTOR;
+ float translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth();
+
+ float alpha;
+
+ if (scrollProgress < 0) {
+ alpha = scrollProgress < 0 ? mAlphaInterpolator.getInterpolation(
+ 1 - Math.abs(scrollProgress)) : 1.0f;
+ } else {
+ // On large screens we need to fade the page as it nears its leftmost position
+ alpha = mLeftScreenAlphaInterpolator.getInterpolation(1 - scrollProgress);
+ }
+
+ v.setCameraDistance(mDensity * CAMERA_DISTANCE);
+ int pageWidth = v.getMeasuredWidth();
+ int pageHeight = v.getMeasuredHeight();
+
+ if (PERFORM_OVERSCROLL_ROTATION) {
+ if (i == 0 && scrollProgress < 0) {
+ // Overscroll to the left
+ v.setPivotX(TRANSITION_PIVOT * pageWidth);
+ v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
+ scale = 1.0f;
+ alpha = 1.0f;
+ // On the first page, we don't want the page to have any lateral motion
+ translationX = 0;
+ } else if (i == getChildCount() - 1 && scrollProgress > 0) {
+ // Overscroll to the right
+ v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth);
+ v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
+ scale = 1.0f;
+ alpha = 1.0f;
+ // On the last page, we don't want the page to have any lateral motion.
+ translationX = 0;
+ } else {
+ v.setPivotY(pageHeight / 2.0f);
+ v.setPivotX(pageWidth / 2.0f);
+ v.setRotationY(0f);
+ }
+ }
+
+ v.setTranslationX(translationX);
+ v.setScaleX(scale);
+ v.setScaleY(scale);
+ v.setAlpha(alpha);
+
+ // If the view has 0 alpha, we set it to be invisible so as to prevent
+ // it from accepting touches
+ if (alpha == 0) {
+ v.setVisibility(INVISIBLE);
+ } else if (v.getVisibility() != VISIBLE) {
+ v.setVisibility(VISIBLE);
+ }
+ }
+ }
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java b/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java
new file mode 100644
index 0000000..1b46efa
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java
@@ -0,0 +1,1704 @@
+/*
+ * 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.internal.policy.impl.keyguard;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Interpolator;
+import android.widget.Scroller;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+
+/**
+ * An abstraction of the original Workspace which supports browsing through a
+ * sequential list of "pages"
+ */
+public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
+ private static final String TAG = "WidgetPagedView";
+ private static final boolean DEBUG = false;
+ protected static final int INVALID_PAGE = -1;
+
+ // the min drag distance for a fling to register, to prevent random page shifts
+ private static final int MIN_LENGTH_FOR_FLING = 25;
+
+ protected static final int PAGE_SNAP_ANIMATION_DURATION = 550;
+ protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
+ protected static final float NANOTIME_DIV = 1000000000.0f;
+
+ private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
+ private static final float OVERSCROLL_DAMP_FACTOR = 0.14f;
+
+ private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
+ // The page is moved more than halfway, automatically move to the next page on touch up.
+ private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
+
+ // The following constants need to be scaled based on density. The scaled versions will be
+ // assigned to the corresponding member variables below.
+ private static final int FLING_THRESHOLD_VELOCITY = 500;
+ private static final int MIN_SNAP_VELOCITY = 1500;
+ private static final int MIN_FLING_VELOCITY = 250;
+
+ static final int AUTOMATIC_PAGE_SPACING = -1;
+
+ protected int mFlingThresholdVelocity;
+ protected int mMinFlingVelocity;
+ protected int mMinSnapVelocity;
+
+ protected float mDensity;
+ protected float mSmoothingTime;
+ protected float mTouchX;
+
+ protected boolean mFirstLayout = true;
+
+ protected int mCurrentPage;
+ protected int mNextPage = INVALID_PAGE;
+ protected int mMaxScrollX;
+ protected Scroller mScroller;
+ private VelocityTracker mVelocityTracker;
+
+ private float mDownMotionX;
+ protected float mLastMotionX;
+ protected float mLastMotionXRemainder;
+ protected float mLastMotionY;
+ protected float mTotalMotionX;
+ private int mLastScreenCenter = -1;
+ private int[] mChildOffsets;
+ private int[] mChildRelativeOffsets;
+ private int[] mChildOffsetsWithLayoutScale;
+
+ protected final static int TOUCH_STATE_REST = 0;
+ protected final static int TOUCH_STATE_SCROLLING = 1;
+ protected final static int TOUCH_STATE_PREV_PAGE = 2;
+ protected final static int TOUCH_STATE_NEXT_PAGE = 3;
+ protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
+
+ protected int mTouchState = TOUCH_STATE_REST;
+ protected boolean mForceScreenScrolled = false;
+
+ protected OnLongClickListener mLongClickListener;
+
+ protected boolean mAllowLongPress = true;
+
+ protected int mTouchSlop;
+ private int mPagingTouchSlop;
+ private int mMaximumVelocity;
+ private int mMinimumWidth;
+ protected int mPageSpacing;
+ protected int mPageLayoutPaddingTop;
+ protected int mPageLayoutPaddingBottom;
+ protected int mPageLayoutPaddingLeft;
+ protected int mPageLayoutPaddingRight;
+ protected int mPageLayoutWidthGap;
+ protected int mPageLayoutHeightGap;
+ protected int mCellCountX = 0;
+ protected int mCellCountY = 0;
+ protected boolean mCenterPagesVertically;
+ protected boolean mAllowOverScroll = true;
+ protected int mUnboundedScrollX;
+ protected int[] mTempVisiblePagesRange = new int[2];
+ protected boolean mForceDrawAllChildrenNextFrame;
+
+ // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
+ // it is equal to the scaled overscroll position. We use a separate value so as to prevent
+ // the screens from continuing to translate beyond the normal bounds.
+ protected int mOverScrollX;
+
+ // parameter that adjusts the layout to be optimized for pages with that scale factor
+ protected float mLayoutScale = 1.0f;
+
+ protected static final int INVALID_POINTER = -1;
+
+ protected int mActivePointerId = INVALID_POINTER;
+
+ private PageSwitchListener mPageSwitchListener;
+
+ protected ArrayList<Boolean> mDirtyPageContent;
+
+ // If true, syncPages and syncPageItems will be called to refresh pages
+ protected boolean mContentIsRefreshable = true;
+
+ // If true, modify alpha of neighboring pages as user scrolls left/right
+ protected boolean mFadeInAdjacentScreens = true;
+
+ // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
+ // to switch to a new page
+ protected boolean mUsePagingTouchSlop = true;
+
+ // If true, the subclass should directly update scrollX itself in its computeScroll method
+ // (SmoothPagedView does this)
+ protected boolean mDeferScrollUpdate = false;
+
+ protected boolean mIsPageMoving = false;
+
+ // All syncs and layout passes are deferred until data is ready.
+ protected boolean mIsDataReady = true;
+
+ // Scrolling indicator
+ private ValueAnimator mScrollIndicatorAnimator;
+ private View mScrollIndicator;
+ private int mScrollIndicatorPaddingLeft;
+ private int mScrollIndicatorPaddingRight;
+ private boolean mShouldShowScrollIndicator = false;
+ private boolean mShouldShowScrollIndicatorImmediately = false;
+ protected static final int sScrollIndicatorFadeInDuration = 150;
+ protected static final int sScrollIndicatorFadeOutDuration = 650;
+ protected static final int sScrollIndicatorFlashDuration = 650;
+
+ public interface PageSwitchListener {
+ void onPageSwitch(View newPage, int newPageIndex);
+ }
+
+ public PagedView(Context context) {
+ this(context, null);
+ }
+
+ public PagedView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PagedView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.PagedView, defStyle, 0);
+ setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0));
+ mPageLayoutPaddingTop = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingTop, 0);
+ mPageLayoutPaddingBottom = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingBottom, 0);
+ mPageLayoutPaddingLeft = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingLeft, 0);
+ mPageLayoutPaddingRight = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutPaddingRight, 0);
+ mPageLayoutWidthGap = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutWidthGap, 0);
+ mPageLayoutHeightGap = a.getDimensionPixelSize(
+ R.styleable.PagedView_pageLayoutHeightGap, 0);
+ mScrollIndicatorPaddingLeft =
+ a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingLeft, 0);
+ mScrollIndicatorPaddingRight =
+ a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0);
+ a.recycle();
+
+ setHapticFeedbackEnabled(false);
+ init();
+ }
+
+ /**
+ * Initializes various states for this workspace.
+ */
+ protected void init() {
+ mDirtyPageContent = new ArrayList<Boolean>();
+ mDirtyPageContent.ensureCapacity(32);
+ mScroller = new Scroller(getContext(), new ScrollInterpolator());
+ mCurrentPage = 0;
+ mCenterPagesVertically = true;
+
+ final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mDensity = getResources().getDisplayMetrics().density;
+
+ mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
+ mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
+ mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
+ setOnHierarchyChangeListener(this);
+ }
+
+ public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
+ mPageSwitchListener = pageSwitchListener;
+ if (mPageSwitchListener != null) {
+ mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
+ }
+ }
+
+ /**
+ * Called by subclasses to mark that data is ready, and that we can begin loading and laying
+ * out pages.
+ */
+ protected void setDataIsReady() {
+ mIsDataReady = true;
+ }
+
+ protected boolean isDataReady() {
+ return mIsDataReady;
+ }
+
+ /**
+ * Returns the index of the currently displayed page.
+ *
+ * @return The index of the currently displayed page.
+ */
+ int getCurrentPage() {
+ return mCurrentPage;
+ }
+
+ int getNextPage() {
+ return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+ }
+
+ int getPageCount() {
+ return getChildCount();
+ }
+
+ View getPageAt(int index) {
+ return getChildAt(index);
+ }
+
+ protected int indexToPage(int index) {
+ return index;
+ }
+
+ /**
+ * Updates the scroll of the current page immediately to its final scroll position. We use this
+ * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
+ * the previous tab page.
+ */
+ protected void updateCurrentPageScroll() {
+ int offset = getChildOffset(mCurrentPage);
+ int relOffset = getRelativeChildOffset(mCurrentPage);
+ int newX = offset - relOffset;
+ scrollTo(newX, 0);
+ mScroller.setFinalX(newX);
+ mScroller.forceFinished(true);
+ }
+
+ /**
+ * Sets the current page.
+ */
+ void setCurrentPage(int currentPage) {
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+ // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
+ // the default
+ if (getChildCount() == 0) {
+ return;
+ }
+
+ mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1));
+ updateCurrentPageScroll();
+ updateScrollingIndicator();
+ notifyPageSwitchListener();
+ invalidate();
+ }
+
+ protected void notifyPageSwitchListener() {
+ if (mPageSwitchListener != null) {
+ mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
+ }
+ }
+
+ protected void pageBeginMoving() {
+ if (!mIsPageMoving) {
+ mIsPageMoving = true;
+ onPageBeginMoving();
+ }
+ }
+
+ protected void pageEndMoving() {
+ if (mIsPageMoving) {
+ mIsPageMoving = false;
+ onPageEndMoving();
+ }
+ }
+
+ protected boolean isPageMoving() {
+ return mIsPageMoving;
+ }
+
+ // a method that subclasses can override to add behavior
+ protected void onPageBeginMoving() {
+ }
+
+ // a method that subclasses can override to add behavior
+ protected void onPageEndMoving() {
+ }
+
+ /**
+ * Registers the specified listener on each page contained in this workspace.
+ *
+ * @param l The listener used to respond to long clicks.
+ */
+ @Override
+ public void setOnLongClickListener(OnLongClickListener l) {
+ mLongClickListener = l;
+ final int count = getPageCount();
+ for (int i = 0; i < count; i++) {
+ getPageAt(i).setOnLongClickListener(l);
+ }
+ }
+
+ @Override
+ public void scrollBy(int x, int y) {
+ scrollTo(mUnboundedScrollX + x, getScrollY() + y);
+ }
+
+ @Override
+ public void scrollTo(int x, int y) {
+ mUnboundedScrollX = x;
+
+ if (x < 0) {
+ super.scrollTo(0, y);
+ if (mAllowOverScroll) {
+ overScroll(x);
+ }
+ } else if (x > mMaxScrollX) {
+ super.scrollTo(mMaxScrollX, y);
+ if (mAllowOverScroll) {
+ overScroll(x - mMaxScrollX);
+ }
+ } else {
+ mOverScrollX = x;
+ super.scrollTo(x, y);
+ }
+
+ mTouchX = x;
+ mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+ }
+
+ // we moved this functionality to a helper function so SmoothPagedView can reuse it
+ protected boolean computeScrollHelper() {
+ if (mScroller.computeScrollOffset()) {
+ // Don't bother scrolling if the page does not need to be moved
+ if (getScrollX() != mScroller.getCurrX()
+ || getScrollY() != mScroller.getCurrY()
+ || mOverScrollX != mScroller.getCurrX()) {
+ scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
+ }
+ invalidate();
+ return true;
+ } else if (mNextPage != INVALID_PAGE) {
+ mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
+ mNextPage = INVALID_PAGE;
+ notifyPageSwitchListener();
+
+ // We don't want to trigger a page end moving unless the page has settled
+ // and the user has stopped scrolling
+ if (mTouchState == TOUCH_STATE_REST) {
+ pageEndMoving();
+ }
+
+ // Notify the user when the page changes
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (accessibilityManager.isEnabled()) {
+ AccessibilityEvent ev =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+ ev.getText().add(getCurrentPageDescription());
+ sendAccessibilityEventUnchecked(ev);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public String getCurrentPageDescription() {
+ return "";
+ }
+
+ @Override
+ public void computeScroll() {
+ computeScrollHelper();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (!mIsDataReady || getChildCount() == 0) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+
+ // Return early if we aren't given a proper dimension
+ if (widthSize <= 0 || heightSize <= 0) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+
+ /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
+ * of the All apps view on XLarge displays to not take up more space then it needs. Width
+ * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
+ * each page to have the same width.
+ */
+ final int verticalPadding = getPaddingTop() + getPaddingBottom();
+ final int horizontalPadding = getPaddingLeft() + getPaddingRight();
+
+ // The children are given the same width and height as the workspace
+ // unless they were set to WRAP_CONTENT
+ if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ // disallowing padding in paged view (just pass 0)
+ final View child = getPageAt(i);
+
+ int childWidthMode = MeasureSpec.EXACTLY;
+ int childHeightMode = MeasureSpec.EXACTLY;
+
+ final int childWidthMeasureSpec =
+ MeasureSpec.makeMeasureSpec(widthSize - horizontalPadding, childWidthMode);
+ final int childHeightMeasureSpec =
+ MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ setMeasuredDimension(widthSize, heightSize);
+
+ // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions.
+ // We also wait until we set the measured dimensions before flushing the cache as well, to
+ // ensure that the cache is filled with good values.
+ invalidateCachedOffsets();
+
+ if (childCount > 0) {
+ if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMeasuredWidth() + ", "
+ + getChildWidth(0));
+
+ // Calculate the variable page spacing if necessary
+ if (mPageSpacing == AUTOMATIC_PAGE_SPACING) {
+ // The gap between pages in the PagedView should be equal to the gap from the page
+ // to the edge of the screen (so it is not visible in the current screen). To
+ // account for unequal padding on each side of the paged view, we take the maximum
+ // of the left/right gap and use that as the gap between each page.
+ int offset = getRelativeChildOffset(0);
+ int spacing = Math.max(offset, widthSize - offset -
+ getChildAt(0).getMeasuredWidth());
+ setPageSpacing(spacing);
+ }
+ }
+
+ updateScrollingIndicatorPosition();
+
+ if (childCount > 0) {
+ mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1);
+ } else {
+ mMaxScrollX = 0;
+ }
+ }
+
+ public void setPageSpacing(int pageSpacing) {
+ mPageSpacing = pageSpacing;
+ invalidateCachedOffsets();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (!mIsDataReady || getChildCount() == 0) {
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
+ final int verticalPadding = getPaddingTop() + getPaddingBottom();
+ final int childCount = getChildCount();
+ int childLeft = getRelativeChildOffset(0);
+
+ for (int i = 0; i < childCount; i++) {
+ final View child = getPageAt(i);
+ if (child.getVisibility() != View.GONE) {
+ final int childWidth = getScaledMeasuredWidth(child);
+ final int childHeight = child.getMeasuredHeight();
+ int childTop = getPaddingTop();
+ if (mCenterPagesVertically) {
+ childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2;
+ }
+
+ if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
+ child.layout(childLeft, childTop,
+ childLeft + child.getMeasuredWidth(), childTop + childHeight);
+ childLeft += childWidth + mPageSpacing;
+ }
+ }
+
+ if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
+ setHorizontalScrollBarEnabled(false);
+ updateCurrentPageScroll();
+ setHorizontalScrollBarEnabled(true);
+ mFirstLayout = false;
+ }
+ }
+
+ protected void screenScrolled(int screenCenter) {
+ if (isScrollingIndicatorEnabled()) {
+ updateScrollingIndicator();
+ }
+ boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
+
+ if (mFadeInAdjacentScreens && !isInOverscroll) {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child != null) {
+ float scrollProgress = getScrollProgress(screenCenter, child, i);
+ float alpha = 1 - Math.abs(scrollProgress);
+ child.setAlpha(alpha);
+ }
+ }
+ invalidate();
+ }
+ }
+
+ @Override
+ public void onChildViewAdded(View parent, View child) {
+ // This ensures that when children are added, they get the correct transforms / alphas
+ // in accordance with any scroll effects.
+ mForceScreenScrolled = true;
+ invalidate();
+ invalidateCachedOffsets();
+ }
+
+ @Override
+ public void onChildViewRemoved(View parent, View child) {
+ }
+
+ protected void invalidateCachedOffsets() {
+ int count = getChildCount();
+ if (count == 0) {
+ mChildOffsets = null;
+ mChildRelativeOffsets = null;
+ mChildOffsetsWithLayoutScale = null;
+ return;
+ }
+
+ mChildOffsets = new int[count];
+ mChildRelativeOffsets = new int[count];
+ mChildOffsetsWithLayoutScale = new int[count];
+ for (int i = 0; i < count; i++) {
+ mChildOffsets[i] = -1;
+ mChildRelativeOffsets[i] = -1;
+ mChildOffsetsWithLayoutScale[i] = -1;
+ }
+ }
+
+ protected int getChildOffset(int index) {
+ int[] childOffsets = Float.compare(mLayoutScale, 1f) == 0 ?
+ mChildOffsets : mChildOffsetsWithLayoutScale;
+
+ if (childOffsets != null && childOffsets[index] != -1) {
+ return childOffsets[index];
+ } else {
+ if (getChildCount() == 0)
+ return 0;
+
+ int offset = getRelativeChildOffset(0);
+ for (int i = 0; i < index; ++i) {
+ offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing;
+ }
+ if (childOffsets != null) {
+ childOffsets[index] = offset;
+ }
+ return offset;
+ }
+ }
+
+ protected int getRelativeChildOffset(int index) {
+ if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) {
+ return mChildRelativeOffsets[index];
+ } else {
+ final int padding = getPaddingLeft() + getPaddingRight();
+ final int offset = getPaddingLeft() +
+ (getMeasuredWidth() - padding - getChildWidth(index)) / 2;
+ if (mChildRelativeOffsets != null) {
+ mChildRelativeOffsets[index] = offset;
+ }
+ return offset;
+ }
+ }
+
+ protected int getScaledMeasuredWidth(View child) {
+ // This functions are called enough times that it actually makes a difference in the
+ // profiler -- so just inline the max() here
+ final int measuredWidth = child.getMeasuredWidth();
+ final int minWidth = mMinimumWidth;
+ final int maxWidth = (minWidth > measuredWidth) ? minWidth : measuredWidth;
+ return (int) (maxWidth * mLayoutScale + 0.5f);
+ }
+
+ protected void getVisiblePages(int[] range) {
+ final int pageCount = getChildCount();
+
+ if (pageCount > 0) {
+ final int screenWidth = getMeasuredWidth();
+ int leftScreen = 0;
+ int rightScreen = 0;
+ View currPage = getPageAt(leftScreen);
+ while (leftScreen < pageCount - 1 &&
+ currPage.getX() + currPage.getWidth() -
+ currPage.getPaddingRight() < getScrollX()) {
+ leftScreen++;
+ currPage = getPageAt(leftScreen);
+ }
+ rightScreen = leftScreen;
+ currPage = getPageAt(rightScreen + 1);
+ while (rightScreen < pageCount - 1 &&
+ currPage.getX() - currPage.getPaddingLeft() < getScrollX() + screenWidth) {
+ rightScreen++;
+ currPage = getPageAt(rightScreen + 1);
+ }
+ range[0] = leftScreen;
+ range[1] = rightScreen;
+ } else {
+ range[0] = -1;
+ range[1] = -1;
+ }
+ }
+
+ protected boolean shouldDrawChild(View child) {
+ return child.getAlpha() > 0;
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ int halfScreenSize = getMeasuredWidth() / 2;
+ // mOverScrollX is equal to getScrollX() when we're within the normal scroll range.
+ // Otherwise it is equal to the scaled overscroll position.
+ int screenCenter = mOverScrollX + halfScreenSize;
+
+ if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
+ // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
+ // set it for the next frame
+ mForceScreenScrolled = false;
+ screenScrolled(screenCenter);
+ mLastScreenCenter = screenCenter;
+ }
+
+ // Find out which screens are visible; as an optimization we only call draw on them
+ final int pageCount = getChildCount();
+ if (pageCount > 0) {
+ getVisiblePages(mTempVisiblePagesRange);
+ final int leftScreen = mTempVisiblePagesRange[0];
+ final int rightScreen = mTempVisiblePagesRange[1];
+ if (leftScreen != -1 && rightScreen != -1) {
+ final long drawingTime = getDrawingTime();
+ // Clip to the bounds
+ canvas.save();
+ canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
+ getScrollY() + getBottom() - getTop());
+
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ final View v = getPageAt(i);
+ if (mForceDrawAllChildrenNextFrame ||
+ (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) {
+ drawChild(canvas, v, drawingTime);
+ }
+ }
+ mForceDrawAllChildrenNextFrame = false;
+ canvas.restore();
+ }
+ }
+ }
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+ int page = indexToPage(indexOfChild(child));
+ if (page != mCurrentPage || !mScroller.isFinished()) {
+ snapToPage(page);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ int focusablePage;
+ if (mNextPage != INVALID_PAGE) {
+ focusablePage = mNextPage;
+ } else {
+ focusablePage = mCurrentPage;
+ }
+ View v = getPageAt(focusablePage);
+ if (v != null) {
+ return v.requestFocus(direction, previouslyFocusedRect);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchUnhandledMove(View focused, int direction) {
+ if (direction == View.FOCUS_LEFT) {
+ if (getCurrentPage() > 0) {
+ snapToPage(getCurrentPage() - 1);
+ return true;
+ }
+ } else if (direction == View.FOCUS_RIGHT) {
+ if (getCurrentPage() < getPageCount() - 1) {
+ snapToPage(getCurrentPage() + 1);
+ return true;
+ }
+ }
+ return super.dispatchUnhandledMove(focused, direction);
+ }
+
+ @Override
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
+ getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
+ }
+ if (direction == View.FOCUS_LEFT) {
+ if (mCurrentPage > 0) {
+ getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
+ }
+ } else if (direction == View.FOCUS_RIGHT){
+ if (mCurrentPage < getPageCount() - 1) {
+ getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
+ }
+ }
+ }
+
+ /**
+ * If one of our descendant views decides that it could be focused now, only
+ * pass that along if it's on the current page.
+ *
+ * This happens when live folders requery, and if they're off page, they
+ * end up calling requestFocus, which pulls it on page.
+ */
+ @Override
+ public void focusableViewAvailable(View focused) {
+ View current = getPageAt(mCurrentPage);
+ View v = focused;
+ while (true) {
+ if (v == current) {
+ super.focusableViewAvailable(focused);
+ return;
+ }
+ if (v == this) {
+ return;
+ }
+ ViewParent parent = v.getParent();
+ if (parent instanceof View) {
+ v = (View)v.getParent();
+ } else {
+ return;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ if (disallowIntercept) {
+ // We need to make sure to cancel our long press if
+ // a scrollable widget takes over touch events
+ final View currentPage = getPageAt(mCurrentPage);
+ currentPage.cancelLongPress();
+ }
+ super.requestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+
+ /**
+ * Return true if a tap at (x, y) should trigger a flip to the previous page.
+ */
+ protected boolean hitsPreviousPage(float x, float y) {
+ return (x < getRelativeChildOffset(mCurrentPage) - mPageSpacing);
+ }
+
+ /**
+ * Return true if a tap at (x, y) should trigger a flip to the next page.
+ */
+ protected boolean hitsNextPage(float x, float y) {
+ return (x > (getMeasuredWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing));
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ /*
+ * This method JUST determines whether we want to intercept the motion.
+ * If we return true, onTouchEvent will be called and we do the actual
+ * scrolling there.
+ */
+ acquireVelocityTrackerAndAddMovement(ev);
+
+ // Skip touch handling if there are no pages to swipe
+ if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
+
+ /*
+ * Shortcut the most recurring case: the user is in the dragging
+ * state and he is moving his finger. We want to intercept this
+ * motion.
+ */
+ final int action = ev.getAction();
+ if ((action == MotionEvent.ACTION_MOVE) &&
+ (mTouchState == TOUCH_STATE_SCROLLING)) {
+ return true;
+ }
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_MOVE: {
+ /*
+ * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+ * whether the user has moved far enough from his original down touch.
+ */
+ if (mActivePointerId != INVALID_POINTER) {
+ determineScrollingStart(ev);
+ break;
+ }
+ // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
+ // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
+ // i.e. fall through to the next case (don't break)
+ // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
+ // while it's small- this was causing a crash before we checked for INVALID_POINTER)
+ }
+
+ case MotionEvent.ACTION_DOWN: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+ // Remember location of down touch
+ mDownMotionX = x;
+ mLastMotionX = x;
+ mLastMotionY = y;
+ mLastMotionXRemainder = 0;
+ mTotalMotionX = 0;
+ mActivePointerId = ev.getPointerId(0);
+ mAllowLongPress = true;
+
+ /*
+ * If being flinged and user touches the screen, initiate drag;
+ * otherwise don't. mScroller.isFinished should be false when
+ * being flinged.
+ */
+ final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
+ final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
+ if (finishedScrolling) {
+ mTouchState = TOUCH_STATE_REST;
+ mScroller.abortAnimation();
+ } else {
+ mTouchState = TOUCH_STATE_SCROLLING;
+ }
+
+ // check if this can be the beginning of a tap on the side of the pages
+ // to scroll the current page
+ if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) {
+ if (getChildCount() > 0) {
+ if (hitsPreviousPage(x, y)) {
+ mTouchState = TOUCH_STATE_PREV_PAGE;
+ } else if (hitsNextPage(x, y)) {
+ mTouchState = TOUCH_STATE_NEXT_PAGE;
+ }
+ }
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mTouchState = TOUCH_STATE_REST;
+ mAllowLongPress = false;
+ mActivePointerId = INVALID_POINTER;
+ releaseVelocityTracker();
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ releaseVelocityTracker();
+ break;
+ }
+
+ /*
+ * The only time we want to intercept motion events is if we are in the
+ * drag mode.
+ */
+ return mTouchState != TOUCH_STATE_REST;
+ }
+
+ protected void determineScrollingStart(MotionEvent ev) {
+ determineScrollingStart(ev, 1.0f);
+ }
+
+ /*
+ * Determines if we should change the touch state to start scrolling after the
+ * user moves their touch point too far.
+ */
+ protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
+ /*
+ * Locally do absolute value. mLastMotionX is set to the y value
+ * of the down event.
+ */
+ final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ if (pointerIndex == -1) {
+ return;
+ }
+ final float x = ev.getX(pointerIndex);
+ final float y = ev.getY(pointerIndex);
+ final int xDiff = (int) Math.abs(x - mLastMotionX);
+ final int yDiff = (int) Math.abs(y - mLastMotionY);
+
+ final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
+ boolean xPaged = xDiff > mPagingTouchSlop;
+ boolean xMoved = xDiff > touchSlop;
+ boolean yMoved = yDiff > touchSlop;
+
+ if (xMoved || xPaged || yMoved) {
+ if (mUsePagingTouchSlop ? xPaged : xMoved) {
+ // Scroll if the user moved far enough along the X axis
+ mTouchState = TOUCH_STATE_SCROLLING;
+ mTotalMotionX += Math.abs(mLastMotionX - x);
+ mLastMotionX = x;
+ mLastMotionXRemainder = 0;
+ mTouchX = getScrollX();
+ mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+ pageBeginMoving();
+ }
+ // Either way, cancel any pending longpress
+ cancelCurrentPageLongPress();
+ }
+ }
+
+ protected void cancelCurrentPageLongPress() {
+ if (mAllowLongPress) {
+ mAllowLongPress = false;
+ // Try canceling the long press. It could also have been scheduled
+ // by a distant descendant, so use the mAllowLongPress flag to block
+ // everything
+ final View currentPage = getPageAt(mCurrentPage);
+ if (currentPage != null) {
+ currentPage.cancelLongPress();
+ }
+ }
+ }
+
+ protected float getScrollProgress(int screenCenter, View v, int page) {
+ final int halfScreenSize = getMeasuredWidth() / 2;
+
+ int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing;
+ int delta = screenCenter - (getChildOffset(page) -
+ getRelativeChildOffset(page) + halfScreenSize);
+
+ float scrollProgress = delta / (totalDistance * 1.0f);
+ scrollProgress = Math.min(scrollProgress, 1.0f);
+ scrollProgress = Math.max(scrollProgress, -1.0f);
+ return scrollProgress;
+ }
+
+ // This curve determines how the effect of scrolling over the limits of the page dimishes
+ // as the user pulls further and further from the bounds
+ private float overScrollInfluenceCurve(float f) {
+ f -= 1.0f;
+ return f * f * f + 1.0f;
+ }
+
+ protected void acceleratedOverScroll(float amount) {
+ int screenSize = getMeasuredWidth();
+
+ // We want to reach the max over scroll effect when the user has
+ // over scrolled half the size of the screen
+ float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
+
+ if (f == 0) return;
+
+ // Clamp this factor, f, to -1 < f < 1
+ if (Math.abs(f) >= 1) {
+ f /= Math.abs(f);
+ }
+
+ int overScrollAmount = (int) Math.round(f * screenSize);
+ if (amount < 0) {
+ mOverScrollX = overScrollAmount;
+ super.scrollTo(0, getScrollY());
+ } else {
+ mOverScrollX = mMaxScrollX + overScrollAmount;
+ super.scrollTo(mMaxScrollX, getScrollY());
+ }
+ invalidate();
+ }
+
+ protected void dampedOverScroll(float amount) {
+ int screenSize = getMeasuredWidth();
+
+ float f = (amount / screenSize);
+
+ if (f == 0) return;
+ f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+ // Clamp this factor, f, to -1 < f < 1
+ if (Math.abs(f) >= 1) {
+ f /= Math.abs(f);
+ }
+
+ int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
+ if (amount < 0) {
+ mOverScrollX = overScrollAmount;
+ super.scrollTo(0, getScrollY());
+ } else {
+ mOverScrollX = mMaxScrollX + overScrollAmount;
+ super.scrollTo(mMaxScrollX, getScrollY());
+ }
+ invalidate();
+ }
+
+ protected void overScroll(float amount) {
+ dampedOverScroll(amount);
+ }
+
+ protected float maxOverScroll() {
+ // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
+ // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
+ float f = 1.0f;
+ f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+ return OVERSCROLL_DAMP_FACTOR * f;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ // Skip touch handling if there are no pages to swipe
+ if (getChildCount() <= 0) return super.onTouchEvent(ev);
+
+ acquireVelocityTrackerAndAddMovement(ev);
+
+ final int action = ev.getAction();
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ /*
+ * If being flinged and user touches, stop the fling. isFinished
+ * will be false if being flinged.
+ */
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+
+ // Remember where the motion event started
+ mDownMotionX = mLastMotionX = ev.getX();
+ mLastMotionXRemainder = 0;
+ mTotalMotionX = 0;
+ mActivePointerId = ev.getPointerId(0);
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ pageBeginMoving();
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ // Scroll to follow the motion event
+ final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ final float x = ev.getX(pointerIndex);
+ final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
+
+ mTotalMotionX += Math.abs(deltaX);
+
+ // Only scroll and update mLastMotionX if we have moved some discrete amount. We
+ // keep the remainder because we are actually testing if we've moved from the last
+ // scrolled position (which is discrete).
+ if (Math.abs(deltaX) >= 1.0f) {
+ mTouchX += deltaX;
+ mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+ if (!mDeferScrollUpdate) {
+ scrollBy((int) deltaX, 0);
+ if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);
+ } else {
+ invalidate();
+ }
+ mLastMotionX = x;
+ mLastMotionXRemainder = deltaX - (int) deltaX;
+ } else {
+ awakenScrollBars();
+ }
+ } else {
+ determineScrollingStart(ev);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ final int activePointerId = mActivePointerId;
+ final int pointerIndex = ev.findPointerIndex(activePointerId);
+ final float x = ev.getX(pointerIndex);
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
+ final int deltaX = (int) (x - mDownMotionX);
+ final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage));
+ boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
+ SIGNIFICANT_MOVE_THRESHOLD;
+
+ mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
+
+ boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
+ Math.abs(velocityX) > mFlingThresholdVelocity;
+
+ // In the case that the page is moved far to one direction and then is flung
+ // in the opposite direction, we use a threshold to determine whether we should
+ // just return to the starting page, or if we should skip one further.
+ boolean returnToOriginalPage = false;
+ if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
+ Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
+ returnToOriginalPage = true;
+ }
+
+ int finalPage;
+ // We give flings precedence over large moves, which is why we short-circuit our
+ // test for a large move if a fling has been registered. That is, a large
+ // move to the left and fling to the right will register as a fling to the right.
+ if (((isSignificantMove && deltaX > 0 && !isFling) ||
+ (isFling && velocityX > 0)) && mCurrentPage > 0) {
+ finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
+ snapToPageWithVelocity(finalPage, velocityX);
+ } else if (((isSignificantMove && deltaX < 0 && !isFling) ||
+ (isFling && velocityX < 0)) &&
+ mCurrentPage < getChildCount() - 1) {
+ finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
+ snapToPageWithVelocity(finalPage, velocityX);
+ } else {
+ snapToDestination();
+ }
+ } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
+ // at this point we have not moved beyond the touch slop
+ // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
+ // we can just page
+ int nextPage = Math.max(0, mCurrentPage - 1);
+ if (nextPage != mCurrentPage) {
+ snapToPage(nextPage);
+ } else {
+ snapToDestination();
+ }
+ } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
+ // at this point we have not moved beyond the touch slop
+ // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
+ // we can just page
+ int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
+ if (nextPage != mCurrentPage) {
+ snapToPage(nextPage);
+ } else {
+ snapToDestination();
+ }
+ } else {
+ onUnhandledTap(ev);
+ }
+ mTouchState = TOUCH_STATE_REST;
+ mActivePointerId = INVALID_POINTER;
+ releaseVelocityTracker();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (mTouchState == TOUCH_STATE_SCROLLING) {
+ snapToDestination();
+ }
+ mTouchState = TOUCH_STATE_REST;
+ mActivePointerId = INVALID_POINTER;
+ releaseVelocityTracker();
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ break;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_SCROLL: {
+ // Handle mouse (or ext. device) by shifting the page depending on the scroll
+ final float vscroll;
+ final float hscroll;
+ if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
+ vscroll = 0;
+ hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+ } else {
+ vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+ hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
+ }
+ if (hscroll != 0 || vscroll != 0) {
+ if (hscroll > 0 || vscroll > 0) {
+ scrollRight();
+ } else {
+ scrollLeft();
+ }
+ return true;
+ }
+ }
+ }
+ }
+ return super.onGenericMotionEvent(event);
+ }
+
+ private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+ }
+
+ private void releaseVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private void onSecondaryPointerUp(MotionEvent ev) {
+ final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
+ MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ // TODO: Make this decision more intelligent.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
+ mLastMotionY = ev.getY(newPointerIndex);
+ mLastMotionXRemainder = 0;
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ }
+ }
+
+ protected void onUnhandledTap(MotionEvent ev) {}
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ super.requestChildFocus(child, focused);
+ int page = indexToPage(indexOfChild(child));
+ if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
+ snapToPage(page);
+ }
+ }
+
+ protected int getChildIndexForRelativeOffset(int relativeOffset) {
+ final int childCount = getChildCount();
+ int left;
+ int right;
+ for (int i = 0; i < childCount; ++i) {
+ left = getRelativeChildOffset(i);
+ right = (left + getScaledMeasuredWidth(getPageAt(i)));
+ if (left <= relativeOffset && relativeOffset <= right) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ protected int getChildWidth(int index) {
+ // This functions are called enough times that it actually makes a difference in the
+ // profiler -- so just inline the max() here
+ final int measuredWidth = getPageAt(index).getMeasuredWidth();
+ final int minWidth = mMinimumWidth;
+ return (minWidth > measuredWidth) ? minWidth : measuredWidth;
+ }
+
+ int getPageNearestToCenterOfScreen() {
+ int minDistanceFromScreenCenter = Integer.MAX_VALUE;
+ int minDistanceFromScreenCenterIndex = -1;
+ int screenCenter = getScrollX() + (getMeasuredWidth() / 2);
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; ++i) {
+ View layout = (View) getPageAt(i);
+ int childWidth = getScaledMeasuredWidth(layout);
+ int halfChildWidth = (childWidth / 2);
+ int childCenter = getChildOffset(i) + halfChildWidth;
+ int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
+ if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
+ minDistanceFromScreenCenter = distanceFromScreenCenter;
+ minDistanceFromScreenCenterIndex = i;
+ }
+ }
+ return minDistanceFromScreenCenterIndex;
+ }
+
+ protected void snapToDestination() {
+ snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
+ }
+
+ private static class ScrollInterpolator implements Interpolator {
+ public ScrollInterpolator() {
+ }
+
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t*t*t*t*t + 1;
+ }
+ }
+
+ // We want the duration of the page snap animation to be influenced by the distance that
+ // the screen has to travel, however, we don't want this duration to be effected in a
+ // purely linear fashion. Instead, we use this method to moderate the effect that the distance
+ // of travel has on the overall snap duration.
+ float distanceInfluenceForSnapDuration(float f) {
+ f -= 0.5f; // center the values about 0.
+ f *= 0.3f * Math.PI / 2.0f;
+ return (float) Math.sin(f);
+ }
+
+ protected void snapToPageWithVelocity(int whichPage, int velocity) {
+ whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
+ int halfScreenSize = getMeasuredWidth() / 2;
+
+ if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
+ if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): "
+ + getMeasuredWidth() + ", " + getChildWidth(whichPage));
+ final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
+ int delta = newX - mUnboundedScrollX;
+ int duration = 0;
+
+ if (Math.abs(velocity) < mMinFlingVelocity) {
+ // If the velocity is low enough, then treat this more as an automatic page advance
+ // as opposed to an apparent physical response to flinging
+ snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+ return;
+ }
+
+ // Here we compute a "distance" that will be used in the computation of the overall
+ // snap duration. This is a function of the actual distance that needs to be traveled;
+ // we keep this value close to half screen size in order to reduce the variance in snap
+ // duration as a function of the distance the page needs to travel.
+ float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize));
+ float distance = halfScreenSize + halfScreenSize *
+ distanceInfluenceForSnapDuration(distanceRatio);
+
+ velocity = Math.abs(velocity);
+ velocity = Math.max(mMinSnapVelocity, velocity);
+
+ // we want the page's snap velocity to approximately match the velocity at which the
+ // user flings, so we scale the duration by a value near to the derivative of the scroll
+ // interpolator at zero, ie. 5. We use 4 to make it a little slower.
+ duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
+
+ snapToPage(whichPage, delta, duration);
+ }
+
+ protected void snapToPage(int whichPage) {
+ snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
+ }
+
+ protected void snapToPage(int whichPage, int duration) {
+ whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
+
+ if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
+ if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getMeasuredWidth() + ", "
+ + getChildWidth(whichPage));
+ int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
+ final int sX = mUnboundedScrollX;
+ final int delta = newX - sX;
+ snapToPage(whichPage, delta, duration);
+ }
+
+ protected void snapToPage(int whichPage, int delta, int duration) {
+ mNextPage = whichPage;
+
+ View focusedChild = getFocusedChild();
+ if (focusedChild != null && whichPage != mCurrentPage &&
+ focusedChild == getPageAt(mCurrentPage)) {
+ focusedChild.clearFocus();
+ }
+
+ pageBeginMoving();
+ awakenScrollBars(duration);
+ if (duration == 0) {
+ duration = Math.abs(delta);
+ }
+
+ if (!mScroller.isFinished()) mScroller.abortAnimation();
+ mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
+
+ notifyPageSwitchListener();
+ invalidate();
+ }
+
+ public void scrollLeft() {
+ if (mScroller.isFinished()) {
+ if (mCurrentPage > 0) snapToPage(mCurrentPage - 1);
+ } else {
+ if (mNextPage > 0) snapToPage(mNextPage - 1);
+ }
+ }
+
+ public void scrollRight() {
+ if (mScroller.isFinished()) {
+ if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1);
+ } else {
+ if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1);
+ }
+ }
+
+ public int getPageForView(View v) {
+ int result = -1;
+ if (v != null) {
+ ViewParent vp = v.getParent();
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ if (vp == getPageAt(i)) {
+ return i;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @return True is long presses are still allowed for the current touch
+ */
+ public boolean allowLongPress() {
+ return mAllowLongPress;
+ }
+
+ /**
+ * Set true to allow long-press events to be triggered, usually checked by
+ * {@link Launcher} to accept or block dpad-initiated long-presses.
+ */
+ public void setAllowLongPress(boolean allowLongPress) {
+ mAllowLongPress = allowLongPress;
+ }
+
+ public static class SavedState extends BaseSavedState {
+ int currentPage = -1;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ currentPage = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(currentPage);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ protected View getScrollingIndicator() {
+ return null;
+ }
+
+ protected boolean isScrollingIndicatorEnabled() {
+ return false;
+ }
+
+ Runnable hideScrollingIndicatorRunnable = new Runnable() {
+ @Override
+ public void run() {
+ hideScrollingIndicator(false);
+ }
+ };
+
+ protected void flashScrollingIndicator(boolean animated) {
+ removeCallbacks(hideScrollingIndicatorRunnable);
+ showScrollingIndicator(!animated);
+ postDelayed(hideScrollingIndicatorRunnable, sScrollIndicatorFlashDuration);
+ }
+
+ protected void showScrollingIndicator(boolean immediately) {
+ mShouldShowScrollIndicator = true;
+ mShouldShowScrollIndicatorImmediately = true;
+ if (getChildCount() <= 1) return;
+ if (!isScrollingIndicatorEnabled()) return;
+
+ mShouldShowScrollIndicator = false;
+ getScrollingIndicator();
+ if (mScrollIndicator != null) {
+ // Fade the indicator in
+ updateScrollingIndicatorPosition();
+ mScrollIndicator.setVisibility(View.VISIBLE);
+ cancelScrollingIndicatorAnimations();
+ if (immediately) {
+ mScrollIndicator.setAlpha(1f);
+ } else {
+ mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 1f);
+ mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeInDuration);
+ mScrollIndicatorAnimator.start();
+ }
+ }
+ }
+
+ protected void cancelScrollingIndicatorAnimations() {
+ if (mScrollIndicatorAnimator != null) {
+ mScrollIndicatorAnimator.cancel();
+ }
+ }
+
+ protected void hideScrollingIndicator(boolean immediately) {
+ if (getChildCount() <= 1) return;
+ if (!isScrollingIndicatorEnabled()) return;
+
+ getScrollingIndicator();
+ if (mScrollIndicator != null) {
+ // Fade the indicator out
+ updateScrollingIndicatorPosition();
+ cancelScrollingIndicatorAnimations();
+ if (immediately) {
+ mScrollIndicator.setVisibility(View.INVISIBLE);
+ mScrollIndicator.setAlpha(0f);
+ } else {
+ mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 0f);
+ mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeOutDuration);
+ mScrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() {
+ private boolean cancelled = false;
+ @Override
+ public void onAnimationCancel(android.animation.Animator animation) {
+ cancelled = true;
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!cancelled) {
+ mScrollIndicator.setVisibility(View.INVISIBLE);
+ }
+ }
+ });
+ mScrollIndicatorAnimator.start();
+ }
+ }
+ }
+
+ /**
+ * To be overridden by subclasses to determine whether the scroll indicator should stretch to
+ * fill its space on the track or not.
+ */
+ protected boolean hasElasticScrollIndicator() {
+ return true;
+ }
+
+ private void updateScrollingIndicator() {
+ if (getChildCount() <= 1) return;
+ if (!isScrollingIndicatorEnabled()) return;
+
+ getScrollingIndicator();
+ if (mScrollIndicator != null) {
+ updateScrollingIndicatorPosition();
+ }
+ if (mShouldShowScrollIndicator) {
+ showScrollingIndicator(mShouldShowScrollIndicatorImmediately);
+ }
+ }
+
+ private void updateScrollingIndicatorPosition() {
+ if (!isScrollingIndicatorEnabled()) return;
+ if (mScrollIndicator == null) return;
+ int numPages = getChildCount();
+ int pageWidth = getMeasuredWidth();
+ int lastChildIndex = Math.max(0, getChildCount() - 1);
+ int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex);
+ int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight;
+ int indicatorWidth = mScrollIndicator.getMeasuredWidth() -
+ mScrollIndicator.getPaddingLeft() - mScrollIndicator.getPaddingRight();
+
+ float offset = Math.max(0f, Math.min(1f, (float) getScrollX() / maxScrollX));
+ int indicatorSpace = trackWidth / numPages;
+ int indicatorPos = (int) (offset * (trackWidth - indicatorSpace)) + mScrollIndicatorPaddingLeft;
+ if (hasElasticScrollIndicator()) {
+ if (mScrollIndicator.getMeasuredWidth() != indicatorSpace) {
+ mScrollIndicator.getLayoutParams().width = indicatorSpace;
+ mScrollIndicator.requestLayout();
+ }
+ } else {
+ int indicatorCenterOffset = indicatorSpace / 2 - indicatorWidth / 2;
+ indicatorPos += indicatorCenterOffset;
+ }
+ mScrollIndicator.setTranslationX(indicatorPos);
+ }
+
+ /* Accessibility */
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setScrollable(getPageCount() > 1);
+ if (getCurrentPage() < getPageCount() - 1) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ }
+ if (getCurrentPage() > 0) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setScrollable(true);
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+ event.setFromIndex(mCurrentPage);
+ event.setToIndex(mCurrentPage);
+ event.setItemCount(getChildCount());
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (super.performAccessibilityAction(action, arguments)) {
+ return true;
+ }
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
+ if (getCurrentPage() < getPageCount() - 1) {
+ scrollRight();
+ return true;
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
+ if (getCurrentPage() > 0) {
+ scrollLeft();
+ return true;
+ }
+ } break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onHoverEvent(android.view.MotionEvent event) {
+ return true;
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/AccountUnlockScreen.java
index a4baeed..d6a31b8 100644
--- a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/AccountUnlockScreen.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import com.android.internal.R;
import com.android.internal.widget.LockPatternUtils;
diff --git a/policy/src/com/android/internal/policy/impl/keyguard_obsolete/BiometricSensorUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/BiometricSensorUnlock.java
new file mode 100644
index 0000000..c38525e
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/BiometricSensorUnlock.java
@@ -0,0 +1,80 @@
+/*
+ * 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.internal.policy.impl.keyguard_obsolete;
+
+import android.view.View;
+
+interface BiometricSensorUnlock {
+ /**
+ * Initializes the view provided for the biometric unlock UI to work within. The provided area
+ * completely covers the backup unlock mechanism.
+ * @param biometricUnlockView View provided for the biometric unlock UI.
+ */
+ public void initializeView(View biometricUnlockView);
+
+ /**
+ * Indicates whether the biometric unlock is running. Before
+ * {@link BiometricSensorUnlock#start} is called, isRunning() returns false. After a successful
+ * call to {@link BiometricSensorUnlock#start}, isRunning() returns true until the biometric
+ * unlock completes, {@link BiometricSensorUnlock#stop} has been called, or an error has
+ * forced the biometric unlock to stop.
+ * @return whether the biometric unlock is currently running.
+ */
+ public boolean isRunning();
+
+ /**
+ * Covers the backup unlock mechanism by showing the contents of the view initialized in
+ * {@link BiometricSensorUnlock#initializeView(View)}. The view should disappear after the
+ * specified timeout. If the timeout is 0, the interface shows until another event, such as
+ * calling {@link BiometricSensorUnlock#hide()}, causes it to disappear. Called on the UI
+ * thread.
+ * @param timeoutMilliseconds Amount of time in milliseconds to display the view before
+ * disappearing. A value of 0 means the view should remain visible.
+ */
+ public void show(long timeoutMilliseconds);
+
+ /**
+ * Uncovers the backup unlock mechanism by hiding the contents of the view initialized in
+ * {@link BiometricSensorUnlock#initializeView(View)}.
+ */
+ public void hide();
+
+ /**
+ * Binds to the biometric unlock service and starts the unlock procedure. Called on the UI
+ * thread.
+ * @return false if it can't be started or the backup should be used.
+ */
+ public boolean start();
+
+ /**
+ * Stops the biometric unlock procedure and unbinds from the service. Called on the UI thread.
+ * @return whether the biometric unlock was running when called.
+ */
+ public boolean stop();
+
+ /**
+ * Cleans up any resources used by the biometric unlock.
+ */
+ public void cleanUp();
+
+ /**
+ * Gets the Device Policy Manager quality of the biometric unlock sensor
+ * (e.g., PASSWORD_QUALITY_BIOMETRIC_WEAK).
+ * @return biometric unlock sensor quality, as defined by Device Policy Manager.
+ */
+ public int getQuality();
+}
diff --git a/policy/src/com/android/internal/policy/impl/FaceUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/FaceUnlock.java
index fda3c9d..e4d9215 100644
--- a/policy/src/com/android/internal/policy/impl/FaceUnlock.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/FaceUnlock.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import com.android.internal.R;
import com.android.internal.policy.IFaceLockCallback;
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreen.java
index bbb6875..ba5b7ff 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardScreen.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreen.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
/**
* Common interface of each {@link android.view.View} that is a screen of
@@ -27,7 +27,7 @@ public interface KeyguardScreen {
* keyboard to be displayed.
*/
boolean needsInput();
-
+
/**
* This screen is no longer in front of the user.
*/
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardScreenCallback.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreenCallback.java
index a843603..be505a1 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardScreenCallback.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardScreenCallback.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.content.res.Configuration;
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardStatusViewManager.java
index bb07a1d..409f87b 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardStatusViewManager.java
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import com.android.internal.R;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.widget.DigitalClock;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.TransportControlView;
-import com.android.internal.policy.impl.KeyguardUpdateMonitor.BatteryStatus;
import java.util.ArrayList;
import java.util.Date;
@@ -621,8 +620,7 @@ class KeyguardStatusViewManager implements OnClickListener {
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onRefreshBatteryInfo(BatteryStatus status) {
+ public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow();
mPluggedIn = status.isPluggedIn();
mBatteryLevel = status.level;
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitor.java
index 5c0cd4f..d990f5f 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
diff --git a/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitorCallback.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitorCallback.java
new file mode 100644
index 0000000..79233e8
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardUpdateMonitorCallback.java
@@ -0,0 +1,96 @@
+/*
+ * 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.internal.policy.impl.keyguard_obsolete;
+
+import android.app.admin.DevicePolicyManager;
+import android.media.AudioManager;
+
+import com.android.internal.telephony.IccCardConstants;
+
+/**
+ * Callback for general information relevant to lock screen.
+ */
+class KeyguardUpdateMonitorCallback {
+ /**
+ * Called when the battery status changes, e.g. when plugged in or unplugged, charge
+ * level, etc. changes.
+ *
+ * @param status current battery status
+ */
+ void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { }
+
+ /**
+ * Called once per minute or when the time changes.
+ */
+ void onTimeChanged() { }
+
+ /**
+ * Called when the carrier PLMN or SPN changes.
+ *
+ * @param plmn The operator name of the registered network. May be null if it shouldn't
+ * be displayed.
+ * @param spn The service provider name. May be null if it shouldn't be displayed.
+ */
+ void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { }
+
+ /**
+ * Called when the ringer mode changes.
+ * @param state the current ringer state, as defined in
+ * {@link AudioManager#RINGER_MODE_CHANGED_ACTION}
+ */
+ 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
+ */
+ void onPhoneStateChanged(int phoneState) { }
+
+ /**
+ * Called when visibility of lockscreen clock changes, such as when
+ * obscured by a widget.
+ */
+ void onClockVisibilityChanged() { }
+
+ /**
+ * Called when the device becomes provisioned
+ */
+ void onDeviceProvisioned() { }
+
+ /**
+ * Called when the device policy changes.
+ * See {@link DevicePolicyManager#ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED}
+ */
+ void onDevicePolicyManagerStateChanged() { }
+
+ /**
+ * Called when the user changes.
+ */
+ void onUserSwitched(int userId) { }
+
+ /**
+ * Called when the SIM state changes.
+ * @param simState
+ */
+ void onSimStateChanged(IccCardConstants.State simState) { }
+
+ /**
+ * Called when a user is removed.
+ */
+ void onUserRemoved(int userId) { }
+}
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewBase.java
index 29a5573..f9fe797 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewBase.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewBase.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.content.Context;
import android.content.Intent;
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewCallback.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewCallback.java
index b376d65..4cc0f30 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewCallback.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewCallback.java
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
/**
- * The callback used by the keyguard view to tell the {@link KeyguardViewMediator}
+ * The callback used by the keyguard view to tell the {@link KeyguardViewMediator}
* various things.
*/
public interface KeyguardViewCallback {
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewManager.java
index d521c05..5dbef48 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewManager.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import com.android.internal.R;
@@ -231,7 +231,7 @@ public class KeyguardViewManager implements KeyguardWindowController {
// Keyguard may be in the process of being shown, but not yet
// updated with the window manager... give it a chance to do so.
mKeyguardHost.post(new Runnable() {
- @Override public void run() {
+ public void run() {
if (mKeyguardHost.getVisibility() == View.VISIBLE) {
showListener.onShown(mKeyguardHost.getWindowToken());
} else {
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewMediator.java
index 236a4ea..641ee88 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewMediator.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+import com.android.internal.policy.impl.PhoneWindowManager;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.widget.LockPatternUtils;
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewProperties.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewProperties.java
index 51b7f1e..676574d 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewProperties.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardViewProperties.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.content.Context;
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardWindowController.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardWindowController.java
index 4ad48fb..98e3209 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardWindowController.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/KeyguardWindowController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
/**
* Interface passed to the keyguard view, for it to call up to control
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardView.java
index 32aec10..0ce87b1 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardView.java
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import com.android.internal.R;
-import com.android.internal.policy.impl.KeyguardUpdateMonitor.BatteryStatus;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockScreenWidgetCallback;
@@ -689,7 +688,7 @@ public class LockPatternKeyguardView extends KeyguardViewBase {
KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@Override
- public void onRefreshBatteryInfo(BatteryStatus status) {
+ public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
// When someone plugs in or unplugs the device, we hide the biometric sensor area and
// suppress its startup for the next onScreenTurnedOn(). Since plugging/unplugging
// causes the screen to turn on, the biometric unlock would start if it wasn't
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewProperties.java
index 5066e3c..5d9cc8e 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardViewProperties.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewProperties.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import com.android.internal.widget.LockPatternUtils;
diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockScreen.java
index 91b5ca1..4e9a1f7 100644
--- a/policy/src/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/LockScreen.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import com.android.internal.R;
import com.android.internal.telephony.IccCardConstants;
diff --git a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PasswordUnlockScreen.java
index 203f9db..87a7371 100644
--- a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PasswordUnlockScreen.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import java.util.List;
diff --git a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PatternUnlockScreen.java
index 9a6d2cc..6d5706b 100644
--- a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/PatternUnlockScreen.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.content.Context;
import android.content.res.Configuration;
diff --git a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimPukUnlockScreen.java
index 3b2a473..3c1703a 100644
--- a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimPukUnlockScreen.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.app.Dialog;
import android.app.ProgressDialog;
diff --git a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimUnlockScreen.java
index 80407f5..13c040c 100644
--- a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard_obsolete/SimUnlockScreen.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.app.Dialog;
import android.app.ProgressDialog;
diff --git a/policy/tests/src/com/android/internal/policy/impl/LockPatternKeyguardViewTest.java b/policy/tests/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewTest.java
index 862e683..97c5672 100644
--- a/policy/tests/src/com/android/internal/policy/impl/LockPatternKeyguardViewTest.java
+++ b/policy/tests/src/com/android/internal/policy/impl/keyguard_obsolete/LockPatternKeyguardViewTest.java
@@ -14,10 +14,15 @@
* limitations under the License.
*/
-package com.android.internal.policy.impl;
+package com.android.internal.policy.impl.keyguard_obsolete;
import android.content.Context;
-import com.android.internal.policy.impl.KeyguardViewCallback;
+
+import com.android.internal.policy.impl.keyguard_obsolete.KeyguardScreen;
+import com.android.internal.policy.impl.keyguard_obsolete.KeyguardUpdateMonitor;
+import com.android.internal.policy.impl.keyguard_obsolete.KeyguardViewCallback;
+import com.android.internal.policy.impl.keyguard_obsolete.KeyguardWindowController;
+import com.android.internal.policy.impl.keyguard_obsolete.LockPatternKeyguardView;
import com.android.internal.telephony.IccCardConstants;
import android.content.res.Configuration;
import android.test.AndroidTestCase;