OSDN Git Service

am 6757572b: Merge "Add throwing InvalidDisplayException from addView." into jb-mr1-dev
authorCraig Mautner <cmautner@google.com>
Tue, 23 Oct 2012 23:56:17 +0000 (16:56 -0700)
committerAndroid Git Automerger <android-git-automerger@android.com>
Tue, 23 Oct 2012 23:56:17 +0000 (16:56 -0700)
* commit '6757572b39d3802c4d7b69467b5ebf69a96c208b':
  Add throwing InvalidDisplayException from addView.

57 files changed:
core/res/res/anim/keyguard_security_fade_out.xml
core/res/res/drawable-hdpi/add_widget.png [new file with mode: 0644]
core/res/res/drawable-hdpi/camera_widget.png [new file with mode: 0644]
core/res/res/drawable-hdpi/security_frame.9.png [new file with mode: 0644]
core/res/res/drawable-hdpi/security_handle.png [new file with mode: 0644]
core/res/res/drawable-mdpi/add_widget.png [new file with mode: 0644]
core/res/res/drawable-mdpi/camera_widget.png [new file with mode: 0644]
core/res/res/drawable-mdpi/security_frame.9.png [new file with mode: 0644]
core/res/res/drawable-mdpi/security_handle.png [new file with mode: 0644]
core/res/res/drawable-xhdpi/add_widget.png [new file with mode: 0644]
core/res/res/drawable-xhdpi/camera_widget.png [new file with mode: 0644]
core/res/res/drawable-xhdpi/security_frame.9.png [new file with mode: 0644]
core/res/res/drawable-xhdpi/security_handle.png [new file with mode: 0644]
core/res/res/layout-land/keyguard_host_view.xml
core/res/res/layout-port/keyguard_host_view.xml
core/res/res/layout-sw600dp-port/keyguard_host_view.xml [deleted file]
core/res/res/layout/keyguard_add_widget.xml [new file with mode: 0644]
core/res/res/layout/keyguard_camera_widget.xml [new file with mode: 0644]
core/res/res/layout/keyguard_multi_user_avatar.xml
core/res/res/layout/keyguard_multi_user_selector.xml
core/res/res/layout/keyguard_pin_view.xml [new file with mode: 0644]
core/res/res/layout/keyguard_status_view.xml
core/res/res/layout/keyguard_widget_pager.xml [new file with mode: 0644]
core/res/res/layout/keyguard_widget_region.xml [deleted file]
core/res/res/values-sw720dp/dimens.xml
core/res/res/values/attrs.xml
core/res/res/values/colors.xml
core/res/res/values/dimens.xml
core/res/res/values/styles.xml
core/res/res/values/symbols.xml
policy/src/com/android/internal/policy/impl/keyguard/KeyguardAbsKeyInputView.java [new file with mode: 0644]
policy/src/com/android/internal/policy/impl/keyguard/KeyguardCircleFramedDrawable.java [new file with mode: 0644]
policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardLinearLayout.java [new file with mode: 0644]
policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserAvatar.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserSelectorView.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java [new file with mode: 0644]
policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityContainer.java [new file with mode: 0644]
policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardSubdivisionLayout.java [deleted file]
policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewStateManager.java [new file with mode: 0644]
policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java
policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetRegion.java [deleted file]
policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java [new file with mode: 0644]
policy/src/com/android/internal/policy/impl/keyguard/PagedView.java
policy/src/com/android/internal/policy/impl/keyguard/SecureCamera.java [new file with mode: 0644]
policy/src/com/android/internal/policy/impl/keyguard/SlidingChallengeLayout.java [new file with mode: 0644]

index 08c8b2b..512fc1d 100644 (file)
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<alpha xmlns:android="http://schemas.android.com/apk/res/android" 
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
     android:interpolator="@interpolator/accelerate_quad"
     android:fromAlpha="1.0"
     android:toAlpha="0.0"
diff --git a/core/res/res/drawable-hdpi/add_widget.png b/core/res/res/drawable-hdpi/add_widget.png
new file mode 100644 (file)
index 0000000..9cf9b60
Binary files /dev/null and b/core/res/res/drawable-hdpi/add_widget.png differ
diff --git a/core/res/res/drawable-hdpi/camera_widget.png b/core/res/res/drawable-hdpi/camera_widget.png
new file mode 100644 (file)
index 0000000..0274388
Binary files /dev/null and b/core/res/res/drawable-hdpi/camera_widget.png differ
diff --git a/core/res/res/drawable-hdpi/security_frame.9.png b/core/res/res/drawable-hdpi/security_frame.9.png
new file mode 100644 (file)
index 0000000..9eeadc4
Binary files /dev/null and b/core/res/res/drawable-hdpi/security_frame.9.png differ
diff --git a/core/res/res/drawable-hdpi/security_handle.png b/core/res/res/drawable-hdpi/security_handle.png
new file mode 100644 (file)
index 0000000..bd4640f
Binary files /dev/null and b/core/res/res/drawable-hdpi/security_handle.png differ
diff --git a/core/res/res/drawable-mdpi/add_widget.png b/core/res/res/drawable-mdpi/add_widget.png
new file mode 100644 (file)
index 0000000..9cf9b60
Binary files /dev/null and b/core/res/res/drawable-mdpi/add_widget.png differ
diff --git a/core/res/res/drawable-mdpi/camera_widget.png b/core/res/res/drawable-mdpi/camera_widget.png
new file mode 100644 (file)
index 0000000..0274388
Binary files /dev/null and b/core/res/res/drawable-mdpi/camera_widget.png differ
diff --git a/core/res/res/drawable-mdpi/security_frame.9.png b/core/res/res/drawable-mdpi/security_frame.9.png
new file mode 100644 (file)
index 0000000..9eeadc4
Binary files /dev/null and b/core/res/res/drawable-mdpi/security_frame.9.png differ
diff --git a/core/res/res/drawable-mdpi/security_handle.png b/core/res/res/drawable-mdpi/security_handle.png
new file mode 100644 (file)
index 0000000..bd4640f
Binary files /dev/null and b/core/res/res/drawable-mdpi/security_handle.png differ
diff --git a/core/res/res/drawable-xhdpi/add_widget.png b/core/res/res/drawable-xhdpi/add_widget.png
new file mode 100644 (file)
index 0000000..9cf9b60
Binary files /dev/null and b/core/res/res/drawable-xhdpi/add_widget.png differ
diff --git a/core/res/res/drawable-xhdpi/camera_widget.png b/core/res/res/drawable-xhdpi/camera_widget.png
new file mode 100644 (file)
index 0000000..0274388
Binary files /dev/null and b/core/res/res/drawable-xhdpi/camera_widget.png differ
diff --git a/core/res/res/drawable-xhdpi/security_frame.9.png b/core/res/res/drawable-xhdpi/security_frame.9.png
new file mode 100644 (file)
index 0000000..9eeadc4
Binary files /dev/null and b/core/res/res/drawable-xhdpi/security_frame.9.png differ
diff --git a/core/res/res/drawable-xhdpi/security_handle.png b/core/res/res/drawable-xhdpi/security_handle.png
new file mode 100644 (file)
index 0000000..bd4640f
Binary files /dev/null and b/core/res/res/drawable-xhdpi/security_handle.png differ
index 521853f..e605f95 100644 (file)
     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:gravity="center_vertical"
+    android:gravity="center_horizontal"
     android:orientation="horizontal">
 
-    <include layout="@layout/keyguard_widget_region"
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="@integer/kg_widget_region_weight" />
-
-    <com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper
-        android:id="@+id/view_flipper"
-        android:layout_width="0dp"
+    <com.android.internal.policy.impl.keyguard.SlidingChallengeLayout
+        android:id="@+id/sliding_layout"
+        android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_weight="@integer/kg_security_flipper_weight"
-        android:clipChildren="false"
-        android:clipToPadding="false"
-        android:paddingLeft="@dimen/keyguard_security_view_margin"
-        android:paddingTop="@dimen/keyguard_security_view_margin"
-        android:paddingRight="@dimen/keyguard_security_view_margin"
-        android:paddingBottom="@dimen/keyguard_security_view_margin"
-        android:gravity="center">
+        android:dragHandle="@drawable/security_handle">
 
-    </com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper>
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+            <include layout="@layout/keyguard_widget_pager"
+                     android:layout_width="match_parent"
+                     android:layout_height="match_parent"
+                     android:layout_gravity="center"/>
+        </FrameLayout>
 
+        <com.android.internal.policy.impl.keyguard.KeyguardSecurityContainer
+            android:id="@+id/keyguard_security_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_isChallenge="true"
+            android:gravity="bottom|center_horizontal"
+            android:background="@drawable/security_frame">
+            <com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper
+                android:id="@+id/view_flipper"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:clipChildren="false"
+                android:clipToPadding="false"
+                android:paddingLeft="@dimen/keyguard_security_view_margin"
+                android:paddingTop="@dimen/keyguard_security_view_margin"
+                android:paddingRight="@dimen/keyguard_security_view_margin"
+                android:paddingBottom="@dimen/keyguard_security_view_margin"
+                android:gravity="center">
+            </com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper>
+        </com.android.internal.policy.impl.keyguard.KeyguardSecurityContainer>
+    </com.android.internal.policy.impl.keyguard.SlidingChallengeLayout>
 </com.android.internal.policy.impl.keyguard.KeyguardHostView>
+
index 981fe6d..6e97102 100644 (file)
     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:gravity="center_horizontal"
     android:orientation="vertical">
 
-    <include layout="@layout/keyguard_widget_region"
+    <com.android.internal.policy.impl.keyguard.SlidingChallengeLayout
+        android:id="@+id/sliding_layout"
         android:layout_width="match_parent"
-        android:layout_height="153dp" />
+        android:layout_height="match_parent"
+        android:dragHandle="@drawable/security_handle">
 
-    <com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper
-        android:id="@+id/view_flipper"
-        android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:clipChildren="false"
-        android:clipToPadding="false"
-        android:layout_weight="1"
-        android:paddingLeft="@dimen/keyguard_security_view_margin"
-        android:paddingTop="@dimen/keyguard_security_view_margin"
-        android:paddingRight="@dimen/keyguard_security_view_margin"
-        android:paddingBottom="@dimen/keyguard_security_view_margin"
-        android:gravity="center">
-    </com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper>
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+            <include layout="@layout/keyguard_widget_pager"
+                     android:layout_width="match_parent"
+                     android:layout_height="match_parent"
+                     android:layout_gravity="center"/>
+        </FrameLayout>
+
+        <com.android.internal.policy.impl.keyguard.KeyguardSecurityContainer
+            android:id="@+id/keyguard_security_container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_isChallenge="true"
+            android:gravity="bottom|center_horizontal"
+            android:background="@drawable/security_frame">
+            <com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper
+                android:id="@+id/view_flipper"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:clipChildren="false"
+                android:clipToPadding="false"
+                android:paddingLeft="@dimen/keyguard_security_view_margin"
+                android:paddingTop="@dimen/keyguard_security_view_margin"
+                android:paddingRight="@dimen/keyguard_security_view_margin"
+                android:paddingBottom="@dimen/keyguard_security_view_margin"
+                android:gravity="center">
+            </com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper>
+        </com.android.internal.policy.impl.keyguard.KeyguardSecurityContainer>
+    </com.android.internal.policy.impl.keyguard.SlidingChallengeLayout>
 </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
deleted file mode 100644 (file)
index 23c1e9c..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-<?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:gravity="center_horizontal"
-    android:orientation="vertical">
-
-    <include layout="@layout/keyguard_widget_region"
-        android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:layout_weight="@integer/kg_widget_region_weight" />
-
-    <com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper
-        android:id="@+id/view_flipper"
-        android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:layout_weight="@integer/kg_security_flipper_weight"
-        android:clipChildren="false"
-        android:clipToPadding="false"
-        android:paddingLeft="@dimen/keyguard_security_view_margin"
-        android:paddingTop="@dimen/keyguard_security_view_margin"
-        android:paddingRight="@dimen/keyguard_security_view_margin"
-        android:paddingBottom="@dimen/keyguard_security_view_margin"
-        android:layout_gravity="center">
-
-
-
-    </com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper>
-
-</com.android.internal.policy.impl.keyguard.KeyguardHostView>
-
diff --git a/core/res/res/layout/keyguard_add_widget.xml b/core/res/res/layout/keyguard_add_widget.xml
new file mode 100644 (file)
index 0000000..6345e89
--- /dev/null
@@ -0,0 +1,33 @@
+<?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.KeyguardWidgetFrame
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/keyguard_add_widget"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_horizontal">
+    <ImageView
+        android:id="@+id/keyguard_add_widget_view"
+        android:clickable="true"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:src="@drawable/add_widget" />
+</com.android.internal.policy.impl.keyguard.KeyguardWidgetFrame>
\ No newline at end of file
diff --git a/core/res/res/layout/keyguard_camera_widget.xml b/core/res/res/layout/keyguard_camera_widget.xml
new file mode 100644 (file)
index 0000000..f1f5817
--- /dev/null
@@ -0,0 +1,33 @@
+<?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.KeyguardWidgetFrame
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/keyguard_camera_widget"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_horizontal">
+
+    <ImageView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:src="@drawable/camera_widget" />
+    
+</com.android.internal.policy.impl.keyguard.KeyguardWidgetFrame>
\ No newline at end of file
index 0e851e3..2d8f02d 100644 (file)
 <!-- This is a view that shows general status information in Keyguard. -->
 <com.android.internal.policy.impl.keyguard.KeyguardMultiUserAvatar
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="125dp"
-    android:layout_height="125dp"
-    android:background="#000000"
+    android:layout_width="@dimen/keyguard_avatar_size"
+    android:layout_height="@dimen/keyguard_avatar_size"
+    android:background="#00000000"
     android:gravity="center_horizontal">
     <ImageView
         android:id="@+id/keyguard_user_avatar"
-        android:scaleType="centerCrop"
+        android:scaleType="center"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_gravity="center"/>
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
-        <Space
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="0.78" />
-        <TextView
-            android:id="@+id/keyguard_user_name"
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="0.22"
-            android:paddingLeft="6dp"
-            android:layout_gravity="center_vertical|left"
-            android:textSize="16sp"
-            android:textColor="#ffffff"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:background="#808080" />
-    </LinearLayout>
-</com.android.internal.policy.impl.keyguard.KeyguardMultiUserAvatar>
\ No newline at end of file
+    <TextView
+       android:id="@+id/keyguard_user_name"
+       android:layout_width="match_parent"
+       android:layout_height="wrap_content"
+       android:layout_gravity="bottom"
+       android:gravity="center"
+       android:textSize="@dimen/keyguard_avatar_name_size"
+       android:textColor="#ffffff"
+       android:singleLine="true"
+       android:ellipsize="end"
+       android:paddingLeft="2dp"
+       android:paddingRight="2dp" />
+</com.android.internal.policy.impl.keyguard.KeyguardMultiUserAvatar>
index 5a6e998..6742e49 100644 (file)
     android:layout_gravity="center"
     android:contentDescription="@string/keyguard_accessibility_user_selector">
 
-    <com.android.internal.policy.impl.keyguard.KeyguardSubdivisionLayout
+    <com.android.internal.policy.impl.keyguard.KeyguardLinearLayout
         android:id="@+id/keyguard_users_grid"
         android:orientation="horizontal"
-        android:layout_width="300dp"
-        android:layout_height="300dp"
-        android:layout_gravity="center" />
+        android:layout_width="wrap_content"
+        android:layout_marginBottom="40dp"
+        android:layout_height="@dimen/keyguard_avatar_size"
+        android:layout_gravity="center|bottom" />
 
-</com.android.internal.policy.impl.keyguard.KeyguardMultiUserSelectorView>
\ No newline at end of file
+</com.android.internal.policy.impl.keyguard.KeyguardMultiUserSelectorView>
diff --git a/core/res/res/layout/keyguard_pin_view.xml b/core/res/res/layout/keyguard_pin_view.xml
new file mode 100644 (file)
index 0000000..6a4ba71
--- /dev/null
@@ -0,0 +1,193 @@
+<?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.KeyguardPINView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/keyguard_pin_view"
+    android:layout_width="350dp"
+    android:layout_height="350dp"
+    android:orientation="vertical"
+    >
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:orientation="horizontal"
+        android:layout_weight="1"
+        >
+        <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:cursorVisible="false"
+            android:textSize="36sp"
+            android:background="@null"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="#ffffffff"
+            android:imeOptions="flagForceAscii|actionDone"
+            />
+
+        <ImageButton android:id="@+id/delete_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:src="@*android:drawable/ic_input_delete"
+            android:clickable="true"
+            android:padding="8dip"
+            android:background="?android:attr/selectableItemBackground"
+            />
+    </LinearLayout>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:orientation="horizontal"
+        >
+        <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+            android:id="@+id/key1"
+            style="@android:style/Widget.Button.NumPadKey"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:textView="@+id/passwordEntry"
+            android:digit="1"
+            />
+        <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+            android:id="@+id/key2"
+            style="@android:style/Widget.Button.NumPadKey"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:textView="@+id/passwordEntry"
+            android:digit="2"
+            />
+        <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+            android:id="@+id/key3"
+            style="@android:style/Widget.Button.NumPadKey"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:textView="@+id/passwordEntry"
+            android:digit="3"
+            />
+    </LinearLayout>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:orientation="horizontal"
+        >
+        <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+            android:id="@+id/key4"
+            style="@android:style/Widget.Button.NumPadKey"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:textView="@+id/passwordEntry"
+            android:digit="4"
+            />
+        <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+            android:id="@+id/key5"
+            style="@android:style/Widget.Button.NumPadKey"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:textView="@+id/passwordEntry"
+            android:digit="5"
+            />
+        <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+            android:id="@+id/key6"
+            style="@android:style/Widget.Button.NumPadKey"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:textView="@+id/passwordEntry"
+            android:digit="6"
+            />
+    </LinearLayout>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:orientation="horizontal"
+        android:layout_weight="1"
+        >
+        <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+            android:id="@+id/key7"
+            style="@android:style/Widget.Button.NumPadKey"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:textView="@+id/passwordEntry"
+            android:digit="7"
+            />
+        <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+            android:id="@+id/key8"
+            style="@android:style/Widget.Button.NumPadKey"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:textView="@+id/passwordEntry"
+            android:digit="8"
+            />
+        <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+            android:id="@+id/key9"
+            style="@android:style/Widget.Button.NumPadKey"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:textView="@+id/passwordEntry"
+            android:digit="9"
+            />
+    </LinearLayout>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:orientation="horizontal"
+        >
+        <Space
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            />
+        <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+            android:id="@+id/key0"
+            style="@android:style/Widget.Button.NumPadKey"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:textView="@+id/passwordEntry"
+            android:digit="0"
+            />
+        <Button
+            android:id="@+id/key_enter"
+            style="@android:style/Widget.Button.NumPadKey"
+            android:gravity="center"
+            android:layout_width="0px"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:text="@android:string/ok"
+            />
+    </LinearLayout>
+</com.android.internal.policy.impl.keyguard.KeyguardPINView>
index 1de0f6c..9532a88 100644 (file)
@@ -35,7 +35,7 @@
 
         <LinearLayout android:layout_width="match_parent"
                       android:layout_height="wrap_content"
-                      android:layout_gravity="center_vertical"
+                      android:layout_gravity="center_horizontal|top"
                       android:orientation="vertical">
             <com.android.internal.policy.impl.keyguard.ClockView
                 android:id="@+id/clock_view"
diff --git a/core/res/res/layout/keyguard_widget_pager.xml b/core/res/res/layout/keyguard_widget_pager.xml
new file mode 100644 (file)
index 0000000..b884a18
--- /dev/null
@@ -0,0 +1,33 @@
+<?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.KeyguardWidgetPager
+
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/app_widget_container"
+    android:paddingLeft="25dp"
+    android:paddingRight="25dp"
+    android:paddingTop="25dp"
+    android:paddingBottom="25dp"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:pageSpacing="10dp"
+    android:edgeSwipeRegionSize="8dp">
+</com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
\ No newline at end of file
diff --git a/core/res/res/layout/keyguard_widget_region.xml b/core/res/res/layout/keyguard_widget_region.xml
deleted file mode 100644 (file)
index ed10c2b..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-<?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.KeyguardWidgetRegion
-    xmlns:prvandroid="http://schemas.android.com/apk/prv/res/android"
-    xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/kg_widget_region"
-        android:visibility="gone"
-        android:gravity="center"
-        android:orientation="vertical">
-    <com.android.internal.policy.impl.keyguard.KeyguardWidgetPager
-        android:id="@+id/app_widget_container"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1"
-        android:clipChildren="false"
-        android:clipToPadding="false">
-    </com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="10dp"
-        android:orientation="horizontal"
-        android:paddingLeft="@dimen/kg_widget_pager_horizontal_padding"
-        android:paddingRight="@dimen/kg_widget_pager_horizontal_padding"
-        android:layout_marginTop="@dimen/kg_runway_lights_top_margin"
-        android:visibility="gone">
-        <com.android.internal.policy.impl.keyguard.KeyguardGlowStripView
-            android:id="@+id/left_strip"
-            android:paddingTop="@dimen/kg_runway_lights_vertical_padding"
-            android:paddingBottom="@dimen/kg_runway_lights_vertical_padding"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            prvandroid:numDots="5"
-            prvandroid:dotSize="@dimen/kg_runway_lights_height"
-            prvandroid:leftToRight="false"
-            prvandroid:glowDot="@*android:drawable/ic_lockscreen_glowdot" />
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1.6"/>
-        <com.android.internal.policy.impl.keyguard.KeyguardGlowStripView
-            android:id="@+id/right_strip"
-            android:paddingTop="@dimen/kg_runway_lights_vertical_padding"
-            android:paddingBottom="@dimen/kg_runway_lights_vertical_padding"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            prvandroid:numDots="5"
-            prvandroid:dotSize="@dimen/kg_runway_lights_height"
-            prvandroid:leftToRight="true"
-            prvandroid:glowDot="@*android:drawable/ic_lockscreen_glowdot" />
-    </LinearLayout>
-</com.android.internal.policy.impl.keyguard.KeyguardWidgetRegion>
index 6144961..350da04 100644 (file)
 
     <!-- Margin around the various security views -->
     <dimen name="keyguard_security_view_margin">100dp</dimen>
+
+    <!-- Stroke width of the frame for the circular avatars. -->
+    <dimen name="keyguard_avatar_frame_stroke_width">3dp</dimen>
+
+    <!-- Size of the avator on the multiuser lockscreen. -->
+    <dimen name="keyguard_avatar_size">88dp</dimen>
+
+    <!-- Size of the text under the avator on the multiuser lockscreen. -->
+    <dimen name="keyguard_avatar_name_size">12sp</dimen>
+
 </resources>
index 3550df9..6656de0 100755 (executable)
     <!-- 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" />
+        <attr name="edgeSwipeRegionSize" format="dimension" />
     </declare-styleable>
 
     <declare-styleable name="KeyguardGlowStripView">
         <attr name="leftToRight" format="boolean" />
     </declare-styleable>
 
+    <declare-styleable name="SlidingChallengeLayout">
+        <attr name="dragHandle" format="reference" />
+    </declare-styleable>
+
+    <declare-styleable name="SlidingChallengeLayout_Layout">
+        <attr name="layout_isChallenge" format="boolean" />
+    </declare-styleable>
+
     <!-- Attributes that can be used with <code>&lt;FragmentBreadCrumbs&gt;</code>
     tags. -->
     <declare-styleable name="FragmentBreadCrumbs">
         <attr name="gravity" />
     </declare-styleable>
 
+    <!-- Keyguard PIN entry -->
+    <declare-styleable name="NumPadKey">
+        <attr name="digit" format="integer" />
+        <attr name="textView" format="reference" />
+    </declare-styleable>
 </resources>
index 6a93f30..b19e23d 100644 (file)
     <drawable name="notification_template_icon_bg">#3333B5E5</drawable>
     <drawable name="notification_template_icon_low_bg">#0cffffff</drawable>
 
+    <!-- Keyguard colors -->
+    <color name="keyguard_avatar_frame_color">#ffffffff</color>
+    <color name="keyguard_avatar_frame_shadow_color">#80000000</color>
+    <color name="keyguard_avatar_nick_color">#ffffffff</color>
+    <color name="keyguard_avatar_frame_pressed_color">#ff35b5e5</color>
 </resources>
 
index 948a3d3..1bc8de7 100644 (file)
     <dimen name="notification_subtext_size">12dp</dimen>
 
     <!-- Keyguard dimensions -->
+    <!-- TEMP -->
+    <dimen name="kg_security_panel_height">600dp</dimen>
+
     <!-- Width of security view in keyguard. -->
     <dimen name="kg_glow_pad_size">500dp</dimen>
 
 
     <!-- Margin around the various security views -->
     <dimen name="keyguard_security_view_margin">8dp</dimen>
+
+    <!-- Stroke width of the frame for the circular avatars. -->
+    <dimen name="keyguard_avatar_frame_stroke_width">2dp</dimen>
+
+    <!-- Shadow radius under the frame for the circular avatars. -->
+    <dimen name="keyguard_avatar_frame_shadow_radius">1dp</dimen>
+
+    <!-- Size of the avator on hte multiuser lockscreen. -->
+    <dimen name="keyguard_avatar_size">66dp</dimen>
+
+    <!-- Size of the text under the avator on the multiuser lockscreen. -->
+    <dimen name="keyguard_avatar_name_size">10sp</dimen>
 </resources>
index 4371aec..1dfb6e0 100644 (file)
@@ -2478,4 +2478,27 @@ please see styles_device_defaults.xml.
         <item name="android:contentDescription">@android:string/media_route_button_content_description</item>
     </style>
 
+    <!-- Keyguard PIN pad styles -->
+    <style name="Widget.Button.NumPadKey">
+        <item name="android:singleLine">true</item>
+        <item name="android:padding">6dip</item>
+        <item name="android:gravity">left|center_vertical</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
+        <item name="android:textSize">34dp</item>
+        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">#ffffff</item>
+    </style>
+    <style name="TextAppearance.NumPadKey">
+        <item name="android:textSize">34dp</item>
+        <item name="android:fontFamily">sans-serif</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">#ffffff</item>
+    </style>
+    <style name="TextAppearance.NumPadKey.Klondike">
+        <item name="android:textSize">20dp</item>
+        <item name="android:fontFamily">sans-serif-condensed</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">#80ffffff</item>
+    </style>
 </resources>
index 8ef91df..94ca080 100644 (file)
   <java-symbol type="layout" name="notification_template_inbox" />
   <java-symbol type="layout" name="keyguard_multi_user_avatar" />
   <java-symbol type="layout" name="keyguard_multi_user_selector_widget" />
-  <java-symbol type="layout" name="keyguard_widget_region" />
   <java-symbol type="layout" name="sms_short_code_confirmation_dialog" />
+  <java-symbol type="layout" name="keyguard_add_widget" />
+  <java-symbol type="layout" name="keyguard_camera_widget" />
 
   <java-symbol type="anim" name="slide_in_child_bottom" />
   <java-symbol type="anim" name="slide_in_right" />
   <java-symbol type="bool" name="config_reverseDefaultRotation" />
   <java-symbol type="bool" name="config_showNavigationBar" />
   <java-symbol type="bool" name="kg_share_status_area" />
-  <java-symbol type="bool" name="kg_sim_puk_account_full_screen" />  
+  <java-symbol type="bool" name="kg_sim_puk_account_full_screen" />
   <java-symbol type="bool" name="target_honeycomb_needs_options_menu" />
   <java-symbol type="color" name="kg_multi_user_text_active" />
   <java-symbol type="color" name="kg_multi_user_text_inactive" />
   <java-symbol type="color" name="kg_widget_pager_gradient" />
+  <java-symbol type="color" name="keyguard_avatar_frame_color" />
+  <java-symbol type="color" name="keyguard_avatar_frame_pressed_color" />
+  <java-symbol type="color" name="keyguard_avatar_frame_shadow_color" />
+  <java-symbol type="color" name="keyguard_avatar_nick_color" />
   <java-symbol type="dimen" name="navigation_bar_height" />
   <java-symbol type="dimen" name="navigation_bar_height_landscape" />
   <java-symbol type="dimen" name="navigation_bar_width" />
   <java-symbol type="dimen" name="kg_widget_pager_horizontal_padding" />
   <java-symbol type="dimen" name="kg_widget_pager_top_padding" />
   <java-symbol type="dimen" name="kg_widget_pager_bottom_padding" />
+  <java-symbol type="dimen" name="keyguard_avatar_size" />
+  <java-symbol type="dimen" name="keyguard_avatar_frame_stroke_width" />
+  <java-symbol type="dimen" name="keyguard_avatar_frame_shadow_radius" />
   <java-symbol type="drawable" name="ic_jog_dial_sound_off" />
   <java-symbol type="drawable" name="ic_jog_dial_sound_on" />
   <java-symbol type="drawable" name="ic_jog_dial_unlock" />
   <java-symbol type="drawable" name="magnified_region_frame" />
   <java-symbol type="drawable" name="menu_background" />
   <java-symbol type="drawable" name="stat_sys_secure" />
+  <java-symbol type="drawable" name="security_frame" />
   <java-symbol type="id" name="action_mode_bar_stub" />
   <java-symbol type="id" name="alarm_status" />
   <java-symbol type="id" name="backspace" />
   <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_pin_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_users_grid" />
   <java-symbol type="id" name="clock_text" />
   <java-symbol type="id" name="clock_view" />
-  <java-symbol type="id" name="kg_widget_region" />
-  <java-symbol type="id" name="left_strip" />
-  <java-symbol type="id" name="right_strip" />
   <java-symbol type="id" name="keyguard_multi_user_selector" />
   <java-symbol type="id" name="status_security_message" />
-
+  <java-symbol type="id" name="sliding_layout" />
+  <java-symbol type="id" name="keyguard_add_widget" />
+  <java-symbol type="id" name="keyguard_add_widget_view" />
+  <java-symbol type="id" name="sliding_layout" />
+  <java-symbol type="id" name="key_enter" />
   <java-symbol type="integer" name="config_carDockRotation" />
   <java-symbol type="integer" name="config_defaultUiModeType" />
   <java-symbol type="integer" name="config_deskDockRotation" />
   <java-symbol type="layout" name="keyguard_selector_view" />
   <java-symbol type="layout" name="keyguard_pattern_view" />
   <java-symbol type="layout" name="keyguard_password_view" />
+  <java-symbol type="layout" name="keyguard_pin_view" />
   <java-symbol type="layout" name="keyguard_face_unlock_view" />
   <java-symbol type="layout" name="keyguard_sim_pin_view" />
   <java-symbol type="layout" name="keyguard_sim_puk_view" />
   <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="style" name="Widget.Button.NumPadKey" />
+  <java-symbol type="style" name="TextAppearance.NumPadKey.Klondike" />
   <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" />
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAbsKeyInputView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAbsKeyInputView.java
new file mode 100644 (file)
index 0000000..6b9abeb
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * 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 android.view.ViewParent;
+
+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.text.Editable;
+import android.text.InputType;
+import android.text.SpannableStringBuilder;
+import android.text.TextWatcher;
+import android.text.method.DigitsKeyListener;
+import android.text.method.TextKeyListener;
+import android.text.style.TextAppearanceSpan;
+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.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import com.android.internal.R;
+
+/**
+ * Base class for PIN and password unlock screens.
+ */
+public abstract class KeyguardAbsKeyInputView extends LinearLayout
+        implements KeyguardSecurityView, OnEditorActionListener, TextWatcher {
+    protected KeyguardSecurityCallback mCallback;
+    protected EditText mPasswordEntry;
+    protected LockPatternUtils mLockPatternUtils;
+    protected SecurityMessageDisplay mSecurityMessageDisplay;
+
+    // To avoid accidental lockout due to events while the device in in the pocket, ignore
+    // any passwords with length less than or equal to this length.
+    protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
+
+    // Enable this if we want to hide the on-screen PIN keyboard when a physical one is showing
+    protected static final boolean ENABLE_HIDE_KEYBOARD = false;
+
+    public KeyguardAbsKeyInputView(Context context) {
+        this(context, null);
+    }
+
+    public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+        mCallback = callback;
+    }
+
+    public void setLockPatternUtils(LockPatternUtils utils) {
+        mLockPatternUtils = utils;
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        if (hasWindowFocus) {
+            reset();
+        }
+    }
+
+    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 {
+            resetState();
+        }
+    }
+
+    protected abstract void resetState();
+
+    @Override
+    protected void onFinishInflate() {
+        // We always set a dummy NavigationManager to avoid null checks
+        mSecurityMessageDisplay = new KeyguardNavigationManager(null);
+
+        mLockPatternUtils = new LockPatternUtils(mContext); // TODO: use common one
+
+        mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
+        mPasswordEntry.setOnEditorActionListener(this);
+        mPasswordEntry.addTextChangedListener(this);
+
+        // 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) {
+                if (mCallback != null) {
+                    mCallback.userActivity(0);
+                }
+            }
+        });
+    }
+
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+        // send focus to the password field
+        return mPasswordEntry.requestFocus(direction, previouslyFocusedRect);
+    }
+
+    protected void verifyPasswordAndUnlock() {
+        String entry = mPasswordEntry.getText().toString();
+        if (mLockPatternUtils.checkPassword(entry)) {
+            mCallback.reportSuccessfulUnlockAttempt();
+            mCallback.dismiss(true);
+        } 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);
+            }
+            mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pin, true);
+        }
+        mPasswordEntry.setText("");
+    }
+
+    // Prevent user from using the PIN/Password entry until scheduled deadline.
+    protected void handleAttemptLockout(long elapsedRealtimeDeadline) {
+        mPasswordEntry.setEnabled(false);
+        long elapsedRealtime = SystemClock.elapsedRealtime();
+        new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
+
+            @Override
+            public void onTick(long millisUntilFinished) {
+                int secondsRemaining = (int) (millisUntilFinished / 1000);
+                mSecurityMessageDisplay.setMessage(
+                        R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining);
+            }
+
+            @Override
+            public void onFinish() {
+                resetState();
+            }
+        }.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 false;
+    }
+
+    @Override
+    public void onPause() {
+
+    }
+
+    @Override
+    public void onResume() {
+        reset();
+    }
+
+    @Override
+    public KeyguardSecurityCallback getCallback() {
+        return mCallback;
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+        if (mCallback != null) {
+            mCallback.userActivity(KeyguardViewManager.DIGIT_PRESS_WAKE_MILLIS);
+        }
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+    }
+
+    @Override
+    public void setSecurityMessageDisplay(SecurityMessageDisplay display) {
+        mSecurityMessageDisplay = display;
+        reset();
+    }
+}
+
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardCircleFramedDrawable.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardCircleFramedDrawable.java
new file mode 100644 (file)
index 0000000..79d0d12
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * 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.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import android.util.Log;
+
+class KeyguardCircleFramedDrawable extends Drawable {
+
+    private final Bitmap mBitmap;
+    private final int mSize;
+    private final Paint mPaint;
+    private final float mShadowRadius;
+    private final float mStrokeWidth;
+    private final int mFrameColor;
+    private final int mHighlightColor;
+    private final int mFrameShadowColor;
+
+    private float mScale;
+    private Path mFramePath;
+    private Rect mSrcRect;
+    private RectF mDstRect;
+    private RectF mFrameRect;
+    private boolean mPressed;
+
+    public KeyguardCircleFramedDrawable(Bitmap bitmap, int size,
+            int frameColor, float strokeWidth,
+            int frameShadowColor, float shadowRadius,
+            int highlightColor) {
+        super();
+        mSize = size;
+        mShadowRadius = shadowRadius;
+        mFrameColor = frameColor;
+        mFrameShadowColor = frameShadowColor;
+        mStrokeWidth = strokeWidth;
+        mHighlightColor = highlightColor;
+
+        mBitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(mBitmap);
+
+        final int width = bitmap.getWidth();
+        final int height = bitmap.getHeight();
+        final int square = Math.min(width, height);
+
+        final Rect cropRect = new Rect((width - square) / 2, (height - square) / 2, square, square);
+        final RectF circleRect = new RectF(0f, 0f, mSize, mSize);
+        circleRect.inset(mStrokeWidth / 2f, mStrokeWidth / 2f);
+        circleRect.inset(mShadowRadius, mShadowRadius);
+
+        final Path fillPath = new Path();
+        fillPath.addArc(circleRect, 0f, 360f);
+
+        canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+
+        // opaque circle matte
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setColor(Color.BLACK);
+        mPaint.setStyle(Paint.Style.FILL);
+        canvas.drawPath(fillPath, mPaint);
+
+        // mask in the icon where the bitmap is opaque
+        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
+        canvas.drawBitmap(bitmap, cropRect, circleRect, mPaint);
+
+        // prepare paint for frame drawing
+        mPaint.setXfermode(null);
+
+        mScale = 1f;
+
+        mSrcRect = new Rect(0, 0, mSize, mSize);
+        mDstRect = new RectF(0, 0, mSize, mSize);
+        mFrameRect = new RectF(mDstRect);
+        mFramePath = new Path();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        // clear background
+        final float outside = Math.min(canvas.getWidth(), canvas.getHeight());
+        final float inside = mScale * outside;
+        final float pad = (outside - inside) / 2f;
+
+        mDstRect.set(pad, pad, outside - pad, outside - pad);
+        canvas.drawBitmap(mBitmap, mSrcRect, mDstRect, null);
+
+        mFrameRect.set(mDstRect);
+        mFrameRect.inset(mStrokeWidth / 2f, mStrokeWidth / 2f);
+        mFrameRect.inset(mShadowRadius, mShadowRadius);
+
+        mFramePath.reset();
+        mFramePath.addArc(mFrameRect, 0f, 360f);
+
+        // white frame
+        if (mPressed) {
+            mPaint.setStyle(Paint.Style.FILL);
+            mPaint.setColor(Color.argb((int) (0.33f * 255),
+                            Color.red(mHighlightColor),
+                            Color.green(mHighlightColor),
+                            Color.blue(mHighlightColor)));
+            canvas.drawPath(mFramePath, mPaint);
+        }
+        mPaint.setStrokeWidth(mStrokeWidth);
+        mPaint.setStyle(Paint.Style.STROKE);
+        mPaint.setColor(mPressed ? mHighlightColor : mFrameColor);
+        mPaint.setShadowLayer(mShadowRadius, 0f, 0f, mFrameShadowColor);
+        canvas.drawPath(mFramePath, mPaint);
+    }
+
+    public void setScale(float scale) {
+        Log.i("KFD", "scale: " + scale);
+        mScale = scale;
+    }
+
+    public float getScale() {
+        return mScale;
+    }
+
+    public void setPressed(boolean pressed) {
+        mPressed = pressed;
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+    }
+}
index 04ab871..c82a736 100644 (file)
@@ -39,6 +39,9 @@ public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecu
     private View mFaceUnlockAreaView;
     private ImageButton mCancelButton;
 
+    private boolean mIsShowing = false;
+    private final Object mIsShowingLock = new Object();
+
     public KeyguardFaceUnlockView(Context context) {
         this(context, null);
     }
@@ -112,6 +115,7 @@ public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecu
     }
 
     private void initializeBiometricUnlockView() {
+        if (DEBUG) Log.d(TAG, "initializeBiometricUnlockView()");
         mFaceUnlockAreaView = findViewById(R.id.face_unlock_area_view);
         if (mFaceUnlockAreaView != null) {
             mBiometricUnlock = new FaceUnlock(mContext);
@@ -129,9 +133,9 @@ public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecu
     }
 
     /**
-     * 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.
+     * Starts the biometric unlock if it should be started based on a number of factors.  If it
+     * should not be started, it either goes to the back up, or remains showing to prepare for
+     * it being started later.
      */
     private void maybeStartBiometricUnlock() {
         if (DEBUG) Log.d(TAG, "maybeStartBiometricUnlock()");
@@ -142,12 +146,25 @@ public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecu
                     LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT);
             PowerManager powerManager = (PowerManager) mContext.getSystemService(
                     Context.POWER_SERVICE);
+
+            boolean isShowing;
+            synchronized(mIsShowingLock) {
+                isShowing = mIsShowing;
+            }
+
+            // Don't start it if the screen is off or if it's not showing, but keep this view up
+            // because we want it here and ready for when the screen turns on or when it does start
+            // showing.
+            if (!powerManager.isScreenOn() || !isShowing) {
+                mBiometricUnlock.stop(); // It shouldn't be running but calling this can't hurt.
+                return;
+            }
+
             // TODO: Some of these conditions are handled in KeyguardSecurityModel and may not be
             // necessary here.
             if (monitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE
                     && !monitor.getMaxBiometricUnlockAttemptsReached()
-                    && !backupIsTimedOut
-                    && powerManager.isScreenOn()) {
+                    && !backupIsTimedOut) {
                 mBiometricUnlock.start();
             } else {
                 mBiometricUnlock.stopAndShowBackup();
@@ -161,7 +178,9 @@ public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecu
         public void onPhoneStateChanged(int phoneState) {
             if (DEBUG) Log.d(TAG, "onPhoneStateChanged(" + phoneState + ")");
             if (phoneState == TelephonyManager.CALL_STATE_RINGING) {
-                mBiometricUnlock.stopAndShowBackup();
+                if (mBiometricUnlock != null) {
+                    mBiometricUnlock.stopAndShowBackup();
+                }
             }
         }
 
@@ -174,10 +193,29 @@ public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecu
             // No longer required; static value set by KeyguardViewMediator
             // mLockPatternUtils.setCurrentUser(userId);
         }
+
+        @Override
+        public void onKeyguardVisibilityChanged(boolean showing) {
+            if (DEBUG) Log.d(TAG, "onKeyguardVisibilityChanged(" + showing + ")");
+            boolean wasShowing = false;
+            synchronized(mIsShowingLock) {
+                wasShowing = mIsShowing;
+                mIsShowing = showing;
+            }
+            PowerManager powerManager = (PowerManager) mContext.getSystemService(
+                    Context.POWER_SERVICE);
+            if (mBiometricUnlock != null) {
+                if (!showing && wasShowing) {
+                    mBiometricUnlock.stop();
+                } else if (showing && powerManager.isScreenOn() && !wasShowing) {
+                    maybeStartBiometricUnlock();
+                }
+            }
+        }
     };
 
     @Override
     public void setSecurityMessageDisplay(SecurityMessageDisplay display) {
-        mSecurityMessageDisplay = display;    
+        mSecurityMessageDisplay = display;
     }
 }
index b86e5b8..c6fb099 100644 (file)
@@ -35,7 +35,9 @@ import android.graphics.Rect;
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Slog;
@@ -71,7 +73,6 @@ public class KeyguardHostView extends KeyguardViewBase {
     private static final int TRANSPORT_VISIBLE = 2;
 
     private AppWidgetHost mAppWidgetHost;
-    private KeyguardWidgetRegion mAppWidgetRegion;
     private KeyguardWidgetPager mAppWidgetContainer;
     private ViewFlipper mSecurityViewContainer;
     private KeyguardSelectorView mKeyguardSelectorView;
@@ -87,6 +88,7 @@ public class KeyguardHostView extends KeyguardViewBase {
     private LockPatternUtils mLockPatternUtils;
 
     private KeyguardSecurityModel mSecurityModel;
+    private KeyguardViewStateManager mViewStateManager;
 
     private Rect mTempRect = new Rect();
     private int mTransportState = TRANSPORT_GONE;
@@ -151,17 +153,33 @@ public class KeyguardHostView extends KeyguardViewBase {
 
     @Override
     protected void onFinishInflate() {
-        mAppWidgetRegion = (KeyguardWidgetRegion) findViewById(R.id.kg_widget_region);
-        mAppWidgetRegion.setVisibility(VISIBLE);
-        mAppWidgetRegion.setCallbacks(mWidgetCallbacks);
-
+        // Grab instances of and make any necessary changes to the main layouts. Create
+        // view state manager and wire up necessary listeners / callbacks.
         mAppWidgetContainer = (KeyguardWidgetPager) findViewById(R.id.app_widget_container);
+        mAppWidgetContainer.setVisibility(VISIBLE);
+        mAppWidgetContainer.setCallbacks(mWidgetCallbacks);
+        mAppWidgetContainer.setMinScale(0.5f);
+
+        addDefaultWidgets();
+        maybePopulateWidgets();
+
+        mViewStateManager = new KeyguardViewStateManager();
+        SlidingChallengeLayout challengeLayout =
+                (SlidingChallengeLayout) findViewById(R.id.sliding_layout);
+        challengeLayout.setOnChallengeScrolledListener(mViewStateManager);
+        mAppWidgetContainer.setViewStateManager(mViewStateManager);
+
+        mViewStateManager.setPagedView(mAppWidgetContainer);
+        mViewStateManager.setSlidingChallenge(challengeLayout);
+
         mSecurityViewContainer = (ViewFlipper) findViewById(R.id.view_flipper);
         mKeyguardSelectorView = (KeyguardSelectorView) findViewById(R.id.keyguard_selector_view);
 
-        addDefaultWidgets();
+
         updateSecurityViews();
-        setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_BACK);
+        if (!(mContext instanceof Activity)) {
+            setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_BACK);
+        }
 
         if (KeyguardUpdateMonitor.getInstance(mContext).getIsFirstBoot()) {
             showPrimarySecurityScreen(false);
@@ -195,8 +213,6 @@ public class KeyguardHostView extends KeyguardViewBase {
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mAppWidgetHost.startListening();
-        // TODO: Re-enable when we have layouts that can support a better variety of widgets.
-        // maybePopulateWidgets();
         disableStatusViewInteraction();
         post(mSwitchPageRunnable);
     }
@@ -221,12 +237,12 @@ public class KeyguardHostView extends KeyguardViewBase {
         return mAppWidgetHost;
     }
 
-    void addWidget(AppWidgetHostView view) {
-        mAppWidgetContainer.addWidget(view);
+    void addWidget(AppWidgetHostView view, int pageIndex) {
+        mAppWidgetContainer.addWidget(view, pageIndex);
     }
 
-    private KeyguardWidgetRegion.Callbacks mWidgetCallbacks
-            = new KeyguardWidgetRegion.Callbacks() {
+    private KeyguardWidgetPager.Callbacks mWidgetCallbacks
+            = new KeyguardWidgetPager.Callbacks() {
         @Override
         public void userActivity() {
             if (mViewMediatorCallback != null) {
@@ -246,8 +262,8 @@ public class KeyguardHostView extends KeyguardViewBase {
     public long getUserActivityTimeout() {
         // Currently only considering user activity timeouts needed by widgets.
         // Could also take into account longer timeouts for certain security views.
-        if (mAppWidgetRegion != null) {
-            return mAppWidgetRegion.getUserActivityTimeout();
+        if (mAppWidgetContainer != null) {
+            return mAppWidgetContainer.getUserActivityTimeout();
         }
         return -1;
     }
@@ -317,13 +333,11 @@ public class KeyguardHostView extends KeyguardViewBase {
             case Pattern:
                 messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message;
                 break;
-
-            case Password: {
-                    final boolean isPin = mLockPatternUtils.getKeyguardStoredPasswordQuality() ==
-                        DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-                    messageId = isPin ? R.string.kg_too_many_failed_pin_attempts_dialog_message
-                            : R.string.kg_too_many_failed_password_attempts_dialog_message;
-                }
+            case PIN:
+                messageId = R.string.kg_too_many_failed_pin_attempts_dialog_message;
+                break;
+            case Password:
+                messageId = R.string.kg_too_many_failed_password_attempts_dialog_message;
                 break;
         }
 
@@ -465,6 +479,7 @@ public class KeyguardHostView extends KeyguardViewBase {
             switch (mCurrentSecuritySelection) {
                 case Pattern:
                 case Password:
+                case PIN:
                 case Account:
                 case Biometric:
                     finish = true;
@@ -504,6 +519,8 @@ public class KeyguardHostView extends KeyguardViewBase {
             if (mViewMediatorCallback != null) {
                 mViewMediatorCallback.keyguardDone(true);
             }
+        } else {
+            mViewStateManager.showBouncer(true);
         }
     }
 
@@ -607,7 +624,8 @@ public class KeyguardHostView extends KeyguardViewBase {
                 break;
             }
         }
-        boolean simPukFullScreen = getResources().getBoolean(R.bool.kg_sim_puk_account_full_screen);
+        boolean simPukFullScreen = getResources().getBoolean(
+                com.android.internal.R.bool.kg_sim_puk_account_full_screen);
         int layoutId = getLayoutIdFor(securityMode);
         if (view == null && layoutId != 0) {
             final LayoutInflater inflater = LayoutInflater.from(mContext);
@@ -631,7 +649,7 @@ public class KeyguardHostView extends KeyguardViewBase {
         if (securityMode == SecurityMode.SimPin || securityMode == SecurityMode.SimPuk ||
             securityMode == SecurityMode.Account) {
             if (simPukFullScreen) {
-                mAppWidgetRegion.setVisibility(View.GONE);
+                mAppWidgetContainer.setVisibility(View.GONE);
             }
         }
 
@@ -717,7 +735,8 @@ public class KeyguardHostView extends KeyguardViewBase {
 
     @Override
     public void show() {
-        onScreenTurnedOn();
+        if (DEBUG) Log.d(TAG, "show()");
+        showPrimarySecurityScreen(false);
     }
 
     private boolean isSecure() {
@@ -726,6 +745,7 @@ public class KeyguardHostView extends KeyguardViewBase {
             case Pattern:
                 return mLockPatternUtils.isLockPatternEnabled();
             case Password:
+            case PIN:
                 return mLockPatternUtils.isLockPasswordEnabled();
             case SimPin:
             case SimPuk:
@@ -760,6 +780,7 @@ public class KeyguardHostView extends KeyguardViewBase {
                 mViewMediatorCallback.keyguardDone(true);
             }
         } else if (securityMode != KeyguardSecurityModel.SecurityMode.Pattern
+                && securityMode != KeyguardSecurityModel.SecurityMode.PIN
                 && securityMode != KeyguardSecurityModel.SecurityMode.Password) {
             // can only verify unlock when in pattern/password mode
             if (mViewMediatorCallback != null) {
@@ -776,6 +797,7 @@ public class KeyguardHostView extends KeyguardViewBase {
         switch (securityMode) {
             case None: return R.id.keyguard_selector_view;
             case Pattern: return R.id.keyguard_pattern_view;
+            case PIN: return R.id.keyguard_pin_view;
             case Password: return R.id.keyguard_password_view;
             case Biometric: return R.id.keyguard_face_unlock_view;
             case Account: return R.id.keyguard_account_view;
@@ -789,6 +811,7 @@ public class KeyguardHostView extends KeyguardViewBase {
         switch (securityMode) {
             case None: return R.layout.keyguard_selector_view;
             case Pattern: return R.layout.keyguard_pattern_view;
+            case PIN: return R.layout.keyguard_pin_view;
             case Password: return R.layout.keyguard_password_view;
             case Biometric: return R.layout.keyguard_face_unlock_view;
             case Account: return R.layout.keyguard_account_view;
@@ -799,12 +822,12 @@ public class KeyguardHostView extends KeyguardViewBase {
         }
     }
 
-    private void addWidget(int appId) {
+    private void addWidget(int appId, int pageIndex) {
         AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
         AppWidgetProviderInfo appWidgetInfo = appWidgetManager.getAppWidgetInfo(appId);
         if (appWidgetInfo != null) {
             AppWidgetHostView view = getAppWidgetHost().createView(mContext, appId, appWidgetInfo);
-            addWidget(view);
+            addWidget(view, pageIndex);
         } else {
             Log.w(TAG, "AppWidgetInfo was null; not adding widget id " + appId);
         }
@@ -812,9 +835,37 @@ public class KeyguardHostView extends KeyguardViewBase {
 
     private void addDefaultWidgets() {
         LayoutInflater inflater = LayoutInflater.from(mContext);
-        inflater.inflate(R.layout.keyguard_status_view, mAppWidgetContainer, true);
         inflater.inflate(R.layout.keyguard_transport_control_view, this, true);
 
+        View addWidget = inflater.inflate(R.layout.keyguard_add_widget, null, true);
+        mAppWidgetContainer.addWidget(addWidget);
+        View statusWidget = inflater.inflate(R.layout.keyguard_status_view, null, true);
+        mAppWidgetContainer.addWidget(statusWidget);
+        View cameraWidget = inflater.inflate(R.layout.keyguard_camera_widget, null, true);
+        mAppWidgetContainer.addWidget(cameraWidget);
+
+        View addWidgetButton = addWidget.findViewById(R.id.keyguard_add_widget_view);
+        addWidgetButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mCallback.setOnDismissRunnable(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        Intent intent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
+                        intent.addFlags(
+                                Intent.FLAG_ACTIVITY_NEW_TASK
+                                | Intent.FLAG_ACTIVITY_SINGLE_TOP
+                                | Intent.FLAG_ACTIVITY_CLEAR_TOP
+                                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                        mContext.startActivityAsUser(intent,
+                                new UserHandle(UserHandle.USER_CURRENT));
+                    }
+                });
+                mCallback.dismiss(false);
+            }
+        });
+
         inflateAndAddUserSelectorWidgetIfNecessary();
         initializeTransportControl();
     }
@@ -874,27 +925,21 @@ public class KeyguardHostView extends KeyguardViewBase {
             }
         }
 
-        // Replace status widget if selected by user in Settings
-        int statusWidgetId = mLockPatternUtils.getStatusWidget();
-        if (statusWidgetId != -1) {
-            addWidget(statusWidgetId);
-            View newStatusWidget = mAppWidgetContainer.getChildAt(
-                    mAppWidgetContainer.getChildCount() - 1);
-
-            int oldStatusWidgetPosition = getWidgetPosition(R.id.keyguard_status_view);
-            mAppWidgetContainer.removeViewAt(oldStatusWidgetPosition);
-
-            // Re-add new status widget at position of old one
-            mAppWidgetContainer.removeView(newStatusWidget);
-            newStatusWidget.setId(R.id.keyguard_status_view);
-            mAppWidgetContainer.addView(newStatusWidget, oldStatusWidgetPosition);
+        View addWidget = mAppWidgetContainer.findViewById(R.id.keyguard_add_widget);
+        int addPageIndex = mAppWidgetContainer.indexOfChild(addWidget);
+        // This shouldn't happen, but just to be safe!
+        if (addPageIndex < 0) {
+            addPageIndex = 0;
         }
 
         // Add user-selected widget
         final int[] widgets = mLockPatternUtils.getUserDefinedWidgets();
-        for (int i = 0; i < widgets.length; i++) {
+        for (int i = widgets.length -1; i >= 0; i--) {
             if (widgets[i] != -1) {
-                addWidget(widgets[i]);
+                // We add the widgets from left to right, starting after the first page after
+                // the add page. We count down, since the order will be persisted from right
+                // to left, starting after camera.
+                addWidget(widgets[i], addPageIndex + 1);
             }
         }
     }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardLinearLayout.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardLinearLayout.java
new file mode 100644 (file)
index 0000000..0fc54cd
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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 android.widget.LinearLayout;
+
+/**
+ * A layout that arranges its children into a special type of grid.
+ */
+public class KeyguardLinearLayout extends LinearLayout {
+    int mTopChild = 0;
+
+    public KeyguardLinearLayout(Context context) {
+        this(context, null, 0);
+    }
+
+    public KeyguardLinearLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public KeyguardLinearLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setTopChild(View child) {
+        int top = indexOfChild(child);
+        mTopChild = top;
+        invalidate();
+    }
+}
index 3c972bc..b025079 100644 (file)
@@ -18,15 +18,19 @@ package com.android.internal.policy.impl.keyguard;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -39,18 +43,31 @@ class KeyguardMultiUserAvatar extends FrameLayout {
     private TextView mUserName;
     private UserInfo mUserInfo;
     private static final float ACTIVE_ALPHA = 1.0f;
-    private static final float INACTIVE_ALPHA = 0.5f;
-    private static final float ACTIVE_SCALE = 1.2f;
-    private static final float ACTIVE_TEXT_BACGROUND_ALPHA = 0.5f;
-    private static final float INACTIVE_TEXT_BACGROUND_ALPHA = 0f;
-    private static int mActiveTextColor;
-    private static int mInactiveTextColor;
+    private static final float INACTIVE_ALPHA = 1.0f;
+    private static final float ACTIVE_SCALE = 1.5f;
+    private static final float ACTIVE_TEXT_ALPHA = 0f;
+    private static final float INACTIVE_TEXT_ALPHA = 0.5f;
+    private static final int SWITCH_ANIMATION_DURATION = 150;
+
+    private final float mActiveAlpha;
+    private final float mActiveScale;
+    private final float mActiveTextAlpha;
+    private final float mInactiveAlpha;
+    private final float mInactiveTextAlpha;
+    private final float mShadowRadius;
+    private final float mStroke;
+    private final float mIconSize;
+    private final int mFrameColor;
+    private final int mFrameShadowColor;
+    private final int mTextColor;
+    private final int mHighlightColor;
+
+    private boolean mTouched;
+
     private boolean mActive;
     private boolean mInit = true;
     private KeyguardMultiUserSelectorView mUserSelector;
-
-    boolean mPressedStateLocked = false;
-    boolean mTempPressedStateHolder = false;
+    private KeyguardCircleFramedDrawable mFramed;
 
     public static KeyguardMultiUserAvatar fromXml(int resId, Context context,
             KeyguardMultiUserSelectorView userSelector, UserInfo info) {
@@ -73,8 +90,29 @@ class KeyguardMultiUserAvatar extends FrameLayout {
         super(context, attrs, defStyle);
 
         Resources res = mContext.getResources();
-        mActiveTextColor = res.getColor(R.color.kg_multi_user_text_active);
-        mInactiveTextColor = res.getColor(R.color.kg_multi_user_text_inactive);
+        mTextColor = res.getColor(R.color.keyguard_avatar_nick_color);
+        mIconSize = res.getDimension(R.dimen.keyguard_avatar_size);
+        mStroke = res.getDimension(R.dimen.keyguard_avatar_frame_stroke_width);
+        mShadowRadius = res.getDimension(R.dimen.keyguard_avatar_frame_shadow_radius);
+        mFrameColor = res.getColor(R.color.keyguard_avatar_frame_color);
+        mFrameShadowColor = res.getColor(R.color.keyguard_avatar_frame_shadow_color);
+        mHighlightColor = res.getColor(R.color.keyguard_avatar_frame_pressed_color);
+        mActiveTextAlpha = ACTIVE_TEXT_ALPHA;
+        mInactiveTextAlpha = INACTIVE_TEXT_ALPHA;
+        mActiveScale = ACTIVE_SCALE;
+        mActiveAlpha = ACTIVE_ALPHA;
+        mInactiveAlpha = INACTIVE_ALPHA;
+
+        mTouched = false;
+
+        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+    }
+
+    protected String rewriteIconPath(String path) {
+        if (!this.getClass().getName().contains("internal")) {
+            return path.replace("system", "data");
+        }
+        return path;
     }
 
     public void init(UserInfo user, KeyguardMultiUserSelectorView userSelector) {
@@ -84,39 +122,41 @@ class KeyguardMultiUserAvatar extends FrameLayout {
         mUserImage = (ImageView) findViewById(R.id.keyguard_user_avatar);
         mUserName = (TextView) findViewById(R.id.keyguard_user_name);
 
-        mUserImage.setImageDrawable(Drawable.createFromPath(mUserInfo.iconPath));
+        mFramed = new KeyguardCircleFramedDrawable(
+                BitmapFactory.decodeFile(rewriteIconPath(user.iconPath)), (int) mIconSize,
+                mFrameColor, mStroke, mFrameShadowColor, mShadowRadius, mHighlightColor);
+        mUserImage.setImageDrawable(mFramed);
         mUserName.setText(mUserInfo.name);
         setOnClickListener(mUserSelector);
-        setActive(false, false, 0, null);
         mInit = false;
     }
 
-    public void setActive(boolean active, boolean animate, int duration, final Runnable onComplete) {
+    public void setActive(boolean active, boolean animate, final Runnable onComplete) {
         if (mActive != active || mInit) {
             mActive = active;
 
             if (active) {
-                KeyguardSubdivisionLayout parent = (KeyguardSubdivisionLayout) getParent();
-                parent.setTopChild(parent.indexOfChild(this));
+                KeyguardLinearLayout parent = (KeyguardLinearLayout) getParent();
+                parent.setTopChild(this);
             }
         }
-        updateVisualsForActive(mActive, animate, duration, true, onComplete);
+        updateVisualsForActive(mActive, animate, SWITCH_ANIMATION_DURATION, onComplete);
     }
 
-    void updateVisualsForActive(boolean active, boolean animate, int duration, boolean scale,
+    void updateVisualsForActive(boolean active, boolean animate, int duration,
             final Runnable onComplete) {
-        final float finalAlpha = active ? ACTIVE_ALPHA : INACTIVE_ALPHA;
-        final float initAlpha = active ? INACTIVE_ALPHA : ACTIVE_ALPHA;
-        final float finalScale = active && scale ? ACTIVE_SCALE : 1.0f;
-        final float initScale = active ? 1.0f : ACTIVE_SCALE;
-        final int finalTextBgAlpha = active ? (int) (ACTIVE_TEXT_BACGROUND_ALPHA * 255) :
-            (int) (INACTIVE_TEXT_BACGROUND_ALPHA * 255);
-        final int initTextBgAlpha = active ? (int) (INACTIVE_TEXT_BACGROUND_ALPHA * 255) :
-            (int) (ACTIVE_TEXT_BACGROUND_ALPHA * 255);
-        int textColor = active ? mActiveTextColor : mInactiveTextColor;
+        final float finalAlpha = active ? mActiveAlpha : mInactiveAlpha;
+        final float initAlpha = active ? mInactiveAlpha : mActiveAlpha;
+        final float finalScale = active ? 1f : 1f / mActiveScale;
+        final float initScale = mFramed.getScale();
+        final int finalTextAlpha = active ? (int) (mActiveTextAlpha * 255) :
+                (int) (mInactiveTextAlpha * 255);
+        final int initTextAlpha = active ? (int) (mInactiveTextAlpha * 255) :
+                (int) (mActiveTextAlpha * 255);
+        int textColor = mTextColor;
         mUserName.setTextColor(textColor);
 
-        if (animate) {
+        if (animate && mTouched) {
             ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
             va.addUpdateListener(new AnimatorUpdateListener() {
                 @Override
@@ -124,12 +164,11 @@ class KeyguardMultiUserAvatar extends FrameLayout {
                     float r = animation.getAnimatedFraction();
                     float scale = (1 - r) * initScale + r * finalScale;
                     float alpha = (1 - r) * initAlpha + r * finalAlpha;
-                    int textBgAlpha = (int) ((1 - r) * initTextBgAlpha + r * finalTextBgAlpha);
-                    setScaleX(scale);
-                    setScaleY(scale);
+                    int textAlpha = (int) ((1 - r) * initTextAlpha + r * finalTextAlpha);
+                    mFramed.setScale(scale);
                     mUserImage.setAlpha(alpha);
-                    mUserName.setBackgroundColor(Color.argb(textBgAlpha, 0, 0, 0));
-                    mUserSelector.invalidate();
+                    mUserName.setTextColor(Color.argb(textAlpha, 255, 255, 255));
+                    mUserImage.invalidate();
                 }
             });
             va.addListener(new AnimatorListenerAdapter() {
@@ -143,38 +182,22 @@ class KeyguardMultiUserAvatar extends FrameLayout {
             va.setDuration(duration);
             va.start();
         } else {
-            setScaleX(finalScale);
-            setScaleY(finalScale);
+            mFramed.setScale(finalScale);
             mUserImage.setAlpha(finalAlpha);
-            mUserName.setBackgroundColor(Color.argb(finalTextBgAlpha, 0, 0, 0));
+            mUserName.setTextColor(Color.argb(finalTextAlpha, 255, 255, 255));
             if (onComplete != null) {
                 post(onComplete);
             }
         }
-    }
-
-    public void lockPressedState() {
-        mPressedStateLocked = true;
-    }
 
-    public void resetPressedState() {
-        mPressedStateLocked = false;
-        post(new Runnable() {
-            @Override
-            public void run() {
-                KeyguardMultiUserAvatar.this.setPressed(mTempPressedStateHolder);
-            }
-        });
+        mTouched = true;
     }
 
     @Override
     public void setPressed(boolean pressed) {
-        if (!mPressedStateLocked) {
-            super.setPressed(pressed);
-            updateVisualsForActive(pressed || mActive, false, 0, mActive, null);
-        } else {
-            mTempPressedStateHolder = pressed;
-        }
+        super.setPressed(pressed);
+        mFramed.setPressed(pressed);
+        mUserImage.invalidate();
     }
 
     public UserInfo getUserInfo() {
index 246c255..d4ccca6 100644 (file)
@@ -24,6 +24,7 @@ import android.os.UserManager;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.WindowManagerGlobal;
 import android.widget.FrameLayout;
 
@@ -36,10 +37,9 @@ import java.util.Comparator;
 public class KeyguardMultiUserSelectorView extends FrameLayout implements View.OnClickListener {
     private static final String TAG = "KeyguardMultiUserSelectorView";
 
-    private KeyguardSubdivisionLayout mUsersGrid;
+    private ViewGroup mUsersGrid;
     private KeyguardMultiUserAvatar mActiveUserAvatar;
     private KeyguardHostView.UserSwitcherCallback mCallback;
-    private static final int SWITCH_ANIMATION_DURATION = 150;
     private static final int FADE_OUT_ANIMATION_DURATION = 100;
 
     public KeyguardMultiUserSelectorView(Context context) {
@@ -63,7 +63,7 @@ public class KeyguardMultiUserSelectorView extends FrameLayout implements View.O
     }
 
     public void init() {
-        mUsersGrid = (KeyguardSubdivisionLayout) findViewById(R.id.keyguard_users_grid);
+        mUsersGrid = (ViewGroup) findViewById(R.id.keyguard_users_grid);
         mUsersGrid.removeAllViews();
         setClipChildren(false);
         setClipToPadding(false);
@@ -83,9 +83,11 @@ public class KeyguardMultiUserSelectorView extends FrameLayout implements View.O
             KeyguardMultiUserAvatar uv = createAndAddUser(user);
             if (user.id == activeUser.id) {
                 mActiveUserAvatar = uv;
+                mActiveUserAvatar.setActive(true, false, null);
+            } else {
+                uv.setActive(false, false, null);
             }
         }
-        mActiveUserAvatar.setActive(true, false, 0, null);
     }
 
     Comparator<UserInfo> mOrderAddedComparator = new Comparator<UserInfo>() {
@@ -112,16 +114,23 @@ public class KeyguardMultiUserSelectorView extends FrameLayout implements View.O
             return;
         } else {
             // Reset the previously active user to appear inactive
-            avatar.lockPressedState();
             mCallback.hideSecurityView(FADE_OUT_ANIMATION_DURATION);
-            mActiveUserAvatar.setActive(false, true,  SWITCH_ANIMATION_DURATION, new Runnable() {
+            mActiveUserAvatar.setActive(false, true, new Runnable() {
                 @Override
                 public void run() {
-                    try {
-                        ActivityManagerNative.getDefault().switchUser(avatar.getUserInfo().id);
-                    } catch (RemoteException re) {
-                        Log.e(TAG, "Couldn't switch user " + re);
-                    }
+                    mActiveUserAvatar = avatar;
+                    mActiveUserAvatar.setActive(true, true, new Runnable() {
+                        @Override
+                        public void run() {
+                            if (this.getClass().getName().contains("internal")) {
+                                try {
+                                    ActivityManagerNative.getDefault().switchUser(avatar.getUserInfo().id);
+                                } catch (RemoteException re) {
+                                    Log.e(TAG, "Couldn't switch user " + re);
+                                }
+                            }
+                        }
+                    });
                 }
             });
         }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java
new file mode 100644 (file)
index 0000000..809186d
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * 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 android.view.ViewParent;
+
+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.text.Editable;
+import android.text.InputType;
+import android.text.SpannableStringBuilder;
+import android.text.TextWatcher;
+import android.text.method.DigitsKeyListener;
+import android.text.method.TextKeyListener;
+import android.text.style.TextAppearanceSpan;
+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.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import com.android.internal.R;
+
+/**
+ * Displays a PIN pad for unlocking.
+ */
+public class KeyguardPINView extends KeyguardAbsKeyInputView
+        implements KeyguardSecurityView, OnEditorActionListener, TextWatcher {
+
+    // 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;
+
+    // Enable this if we want to hide the on-screen PIN keyboard when a physical one is showing
+    private static final boolean ENABLE_HIDE_KEYBOARD = false;
+
+    public KeyguardPINView(Context context) {
+        this(context, null);
+    }
+
+    public KeyguardPINView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    protected void resetState() {
+        mSecurityMessageDisplay.setMessage(R.string.kg_pin_instructions, false);
+        mPasswordEntry.setEnabled(true);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        final View ok = findViewById(R.id.key_enter);
+        if (ok != null) {
+            ok.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    verifyPasswordAndUnlock();
+                }
+            });
+        }
+
+        // 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);
+            pinDelete.setOnClickListener(new OnClickListener() {
+                public void onClick(View v) {
+                    Editable str = mPasswordEntry.getText();
+                    if (str.length() > 0) {
+                        mPasswordEntry.setText(str.subSequence(0, str.length()-1));
+                    }
+                }
+            });
+        }
+
+        mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance());
+        mPasswordEntry.setInputType(InputType.TYPE_CLASS_NUMBER
+                | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+
+        mPasswordEntry.requestFocus();
+    }
+}
+
index 1868507..635cadc 100644 (file)
@@ -53,22 +53,12 @@ import com.android.internal.widget.PasswordEntryKeyboardHelper;
  * an unlock password
  */
 
-public class KeyguardPasswordView extends LinearLayout
+public class KeyguardPasswordView extends KeyguardAbsKeyInputView
         implements KeyguardSecurityView, OnEditorActionListener, TextWatcher {
-    private KeyguardSecurityCallback mCallback;
-    private EditText mPasswordEntry;
-    private LockPatternUtils mLockPatternUtils;
+
     private PasswordEntryKeyboardView mKeyboardView;
     private PasswordEntryKeyboardHelper mKeyboardHelper;
     private boolean mIsAlpha;
-    private SecurityMessageDisplay mSecurityMessageDisplay;
-
-    // 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;
-
-    // Enable this if we want to hide the on-screen PIN keyboard when a physical one is showing
-    private static final boolean ENABLE_HIDE_KEYBOARD = false;
 
     public KeyguardPasswordView(Context context) {
         super(context);
@@ -78,36 +68,7 @@ public class KeyguardPasswordView extends LinearLayout
         super(context, attrs);
     }
 
-    public void setKeyguardCallback(KeyguardSecurityCallback callback) {
-        mCallback = callback;
-    }
-
-    public void setLockPatternUtils(LockPatternUtils utils) {
-        mLockPatternUtils = utils;
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasWindowFocus) {
-        if (hasWindowFocus) {
-            reset();
-        }
-    }
-
-    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 {
-            resetState();
-        }
-    }
-
-    private void resetState() {
+    protected void resetState() {
         mSecurityMessageDisplay.setMessage(
                 mIsAlpha ? R.string.kg_password_instructions : R.string.kg_pin_instructions, false);
         mPasswordEntry.setEnabled(true);
@@ -116,10 +77,7 @@ public class KeyguardPasswordView extends LinearLayout
 
     @Override
     protected void onFinishInflate() {
-        // We always set a dummy NavigationManager to avoid null checks
-        mSecurityMessageDisplay = new KeyguardNavigationManager(null);
-
-        mLockPatternUtils = new LockPatternUtils(mContext); // TODO: use common one
+        super.onFinishInflate();
 
         final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality();
         mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == quality
@@ -127,9 +85,6 @@ public class KeyguardPasswordView extends LinearLayout
                 || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == quality;
 
         mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
-        mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
-        mPasswordEntry.setOnEditorActionListener(this);
-        mPasswordEntry.addTextChangedListener(this);
 
         mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false,
                 new int[] {
@@ -170,8 +125,6 @@ public class KeyguardPasswordView extends LinearLayout
             }
         }
 
-        mPasswordEntry.requestFocus();
-
         // This allows keyboards with overlapping qwerty/numeric keys to choose just numeric keys.
         if (mIsAlpha) {
             mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
@@ -204,6 +157,8 @@ public class KeyguardPasswordView extends LinearLayout
             }
         });
 
+        mPasswordEntry.requestFocus();
+
         // 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(
@@ -278,110 +233,5 @@ public class KeyguardPasswordView extends LinearLayout
         // 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();
-        if (mLockPatternUtils.checkPassword(entry)) {
-            mCallback.reportSuccessfulUnlockAttempt();
-            mCallback.dismiss(true);
-        } 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);
-            }
-            mSecurityMessageDisplay.setMessage(
-                    mIsAlpha ? R.string.kg_wrong_password : R.string.kg_wrong_pin, true);
-        }
-        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);
-                mSecurityMessageDisplay.setMessage(
-                        R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining);
-            }
-
-            @Override
-            public void onFinish() {
-                resetState();
-            }
-        }.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;
-    }
-
-    @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-        if (mCallback != null) {
-            mCallback.userActivity(KeyguardViewManager.DIGIT_PRESS_WAKE_MILLIS);
-        }
-    }
-
-    @Override
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-    }
-
-    @Override
-    public void afterTextChanged(Editable s) {
-    }
-
-    @Override
-    public void setSecurityMessageDisplay(SecurityMessageDisplay display) {
-        mSecurityMessageDisplay = display;
-        reset();
-    }
 }
 
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityContainer.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityContainer.java
new file mode 100644 (file)
index 0000000..f6a90c5
--- /dev/null
@@ -0,0 +1,20 @@
+package com.android.internal.policy.impl.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+public class KeyguardSecurityContainer extends FrameLayout {
+
+    public KeyguardSecurityContainer(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public KeyguardSecurityContainer(Context context) {
+        this(null, null, 0);
+    }
+
+    public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+}
index 59e2ca9..7a69586 100644 (file)
@@ -31,7 +31,8 @@ public class KeyguardSecurityModel {
         Invalid, // NULL state
         None, // No security enabled
         Pattern, // Unlock by drawing a pattern.
-        Password, // Unlock by entering a password or PIN
+        Password, // Unlock by entering an alphanumeric password
+        PIN, // Strictly numeric password
         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.
@@ -85,6 +86,9 @@ public class KeyguardSecurityModel {
             final int security = mLockPatternUtils.getKeyguardStoredPasswordQuality();
             switch (security) {
                 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+                    mode = mLockPatternUtils.isLockPasswordEnabled() ?
+                            SecurityMode.PIN : SecurityMode.None;
+                    break;
                 case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
                 case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
                 case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
@@ -117,7 +121,9 @@ public class KeyguardSecurityModel {
      */
     SecurityMode getAlternateFor(SecurityMode mode) {
         if (isBiometricUnlockEnabled() && !isBiometricUnlockSuppressed()
-                && (mode == SecurityMode.Password || mode == SecurityMode.Pattern)) {
+                && (mode == SecurityMode.Password
+                        || mode == SecurityMode.PIN
+                        || mode == SecurityMode.Pattern)) {
             return SecurityMode.Biometric;
         }
         return mode; // no alternate, return what was given
index 562d893..320cdc8 100644 (file)
@@ -91,7 +91,8 @@ public class KeyguardSimPukView extends LinearLayout implements View.OnClickList
             } else if (state == CONFIRM_PIN) {
                 if (confirmPin()) {
                     state = DONE;
-                    msg = R.string.lockscreen_sim_unlock_progress_dialog_message;
+                    msg =
+                        com.android.internal.R.string.lockscreen_sim_unlock_progress_dialog_message;
                     updateSim();
                 } else {
                     msg = R.string.kg_invalid_confirm_pin_hint;
index 5b85064..b6985bd 100644 (file)
@@ -78,7 +78,7 @@ class KeyguardStatusViewManager implements SecurityMessageDisplay {
     // Whether to use the last line as a combined line to either display owner info / charging.
     // If false, each item will be given a dedicated space.
     private boolean mShareStatusRegion = false;
-    
+
     // last known battery level
     private int mBatteryLevel = 100;
 
@@ -121,9 +121,9 @@ class KeyguardStatusViewManager implements SecurityMessageDisplay {
         if (DEBUG) Log.v(TAG, "KeyguardStatusViewManager()");
         mContainer = view;
         Resources res = getContext().getResources();
-        mDateFormatString = 
+        mDateFormatString =
                 res.getText(com.android.internal.R.string.abbrev_wday_month_day_no_year);
-        mShareStatusRegion = res.getBoolean(R.bool.kg_share_status_area);
+        mShareStatusRegion = res.getBoolean(com.android.internal.R.bool.kg_share_status_area);
         mLockPatternUtils = new LockPatternUtils(view.getContext());
         mUpdateMonitor = KeyguardUpdateMonitor.getInstance(view.getContext());
 
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSubdivisionLayout.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSubdivisionLayout.java
deleted file mode 100644 (file)
index 1cd796c..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * 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.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.ArrayList;
-
-/**
- * A layout that arranges its children into a special type of grid.
- */
-public class KeyguardSubdivisionLayout extends ViewGroup {
-    ArrayList<BiTree> mCells = new ArrayList<BiTree>();
-    int mNumChildren = -1;
-    int mWidth = -1;
-    int mHeight = -1;
-    int mTopChild = 0;
-
-    public KeyguardSubdivisionLayout(Context context) {
-        this(context, null, 0);
-    }
-
-    public KeyguardSubdivisionLayout(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public KeyguardSubdivisionLayout(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        setClipChildren(false);
-        setClipToPadding(false);
-        setChildrenDrawingOrderEnabled(true);
-    }
-
-    private class BiTree {
-        Rect rect;
-        BiTree left;
-        BiTree right;
-        int nodeDepth;
-        ArrayList<BiTree> leafs;
-
-        public BiTree(Rect r) {
-            rect = r;
-        }
-
-        public BiTree() {
-        }
-
-        boolean isLeaf() {
-            return (left == null) && (right == null);
-        }
-
-        int depth() {
-            if (left != null && right != null) {
-                return Math.max(left.depth(), right.depth()) + 1;
-            } else if (left != null) {
-                return left.depth() + 1;
-            } else if (right != null) {
-                return right.depth() + 1;
-            } else {
-                return 1;
-            }
-        }
-
-        int numLeafs() {
-            if (left != null && right != null) {
-                return left.numLeafs() + right.numLeafs();
-            } else if (left != null) {
-                return left.numLeafs();
-            } else if (right != null) {
-                return right.numLeafs();
-            } else {
-                return 1;
-            }
-        }
-
-        BiTree getNextNodeToBranch() {
-            if (leafs == null) {
-                leafs = new ArrayList<BiTree>();
-            }
-            leafs.clear();
-            getLeafs(leafs, 1);
-
-            // If the tree is complete, then we start a new level at the rightmost side.
-            double r = log2(leafs.size());
-            if (Math.ceil(r) == Math.floor(r)) {
-                return leafs.get(leafs.size() - 1);
-            }
-
-            // Tree is not complete, find the first leaf who's depth is less than the depth of
-            // the tree.
-            int treeDepth = depth();
-            for (int i = leafs.size() - 1; i >= 0; i--) {
-                BiTree n = leafs.get(i);
-                if (n.nodeDepth < treeDepth) {
-                    return n;
-                }
-            }
-            return null;
-        }
-
-        // Gets leafs in left to right order
-        void getLeafs(ArrayList<BiTree> leafs, int depth) {
-            if (isLeaf()) {
-                this.nodeDepth = depth;
-                leafs.add(this);
-            } else {
-                if (left != null) {
-                    left.getLeafs(leafs, depth + 1);
-                }
-                if (right != null) {
-                    right.getLeafs(leafs, depth + 1);
-                }
-            }
-        }
-    }
-
-    double log2(double d) {
-        return Math.log(d) / Math.log(2);
-    }
-
-    private void addCell(BiTree tree) {
-        BiTree branch = tree.getNextNodeToBranch();
-        Rect r = branch.rect;
-        branch.left = new BiTree();
-        branch.right = new BiTree();
-        int newDepth = tree.depth();
-
-        // For each level of the tree, we alternate between horizontal and vertical division
-        if (newDepth % 2 == 0) {
-            // Divide the cell vertically
-            branch.left.rect = new Rect(r.left, r.top, r.right, r.top + r.height() / 2);
-            branch.right.rect = new Rect(r.left, r.top + r.height() / 2, r.right, r.bottom);
-        } else {
-            // Divide the cell horizontally
-            branch.left.rect = new Rect(r.left, r.top, r.left + r.width() / 2, r.bottom);
-            branch.right.rect = new Rect(r.left + r.width() / 2, r.top, r.right, r.bottom);
-        }
-    }
-
-    private void constructGrid(int width, int height, int numChildren) {
-        mCells.clear();
-        BiTree root = new BiTree(new Rect(0, 0, width, height));
-
-        // We add nodes systematically until the number of leafs matches the number of children
-        while (root.numLeafs() < numChildren) {
-            addCell(root);
-        }
-
-        // Spit out the final list of cells
-        root.getLeafs(mCells, 1);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int width = MeasureSpec.getSize(widthMeasureSpec);
-        int height =  MeasureSpec.getSize(heightMeasureSpec);
-        int childCount = getChildCount();
-
-        if (mNumChildren != childCount || width != getMeasuredWidth() ||
-                height != getMeasuredHeight()) {
-            constructGrid(width, height, childCount);
-        }
-
-        for (int i = 0; i < childCount; i++) {
-            final View child = getChildAt(i);
-            Rect rect = mCells.get(i).rect;
-            child.measure(MeasureSpec.makeMeasureSpec(rect.width(), MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(rect.height(), MeasureSpec.EXACTLY));
-        }
-        setMeasuredDimension(width, height);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final View child = getChildAt(i);
-            Rect rect = mCells.get(i).rect;
-            child.layout(rect.left, rect.top, rect.right, rect.bottom);
-        }
-    }
-
-    public void setTopChild(int top) {
-        mTopChild = top;
-        invalidate();
-    }
-
-    protected int getChildDrawingOrder(int childCount, int i) {
-        int ret = i;
-        if (i == childCount - 1) {
-            ret = mTopChild;
-        } else if (i >= mTopChild){
-            ret = i + 1;
-        }
-        return ret;
-    }
-}
\ No newline at end of file
index d8e1c1a..316825a 100644 (file)
@@ -80,6 +80,7 @@ public class KeyguardUpdateMonitor {
     private static final int MSG_DPM_STATE_CHANGED = 309;
     private static final int MSG_USER_SWITCHED = 310;
     private static final int MSG_USER_REMOVED = 311;
+    private static final int MSG_KEYGUARD_VISIBILITY_CHANGED = 312;
 
     private static KeyguardUpdateMonitor sInstance;
 
@@ -147,6 +148,10 @@ public class KeyguardUpdateMonitor {
                 case MSG_USER_REMOVED:
                     handleUserRemoved(msg.arg1);
                     break;
+                case MSG_KEYGUARD_VISIBILITY_CHANGED:
+                    handleKeyguardVisibilityChanged(msg.arg1);
+                    break;
+
             }
         }
     };
@@ -557,6 +562,19 @@ public class KeyguardUpdateMonitor {
         }
     }
 
+    /**
+     * Handle {@link #MSG_KEYGUARD_VISIBILITY_CHANGED}
+     */
+    private void handleKeyguardVisibilityChanged(int showing) {
+        if (DEBUG) Log.d(TAG, "handleKeyguardVisibilityChanged(" + showing + ")");
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onKeyguardVisibilityChanged(showing == 1);
+            }
+        }
+    }
+
     private static boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) {
         final boolean nowPluggedIn = current.isPluggedIn();
         final boolean wasPluggedIn = old.isPluggedIn();
@@ -659,6 +677,13 @@ public class KeyguardUpdateMonitor {
         callback.onSimStateChanged(mSimState);
     }
 
+    public void sendKeyguardVisibilityChanged(boolean showing) {
+        if (DEBUG) Log.d(TAG, "sendKeyguardVisibilityChanged(" + showing + ")");
+        Message message = mHandler.obtainMessage(MSG_KEYGUARD_VISIBILITY_CHANGED);
+        message.arg1 = showing ? 1 : 0;
+        message.sendToTarget();
+    }
+
     public void reportClockVisible(boolean visible) {
         mClockVisible = visible;
         mHandler.obtainMessage(MSG_CLOCK_VISIBILITY_CHANGED).sendToTarget();
index 3d65e68..8c9ac8b 100644 (file)
@@ -62,6 +62,12 @@ class KeyguardUpdateMonitorCallback {
     void onPhoneStateChanged(int phoneState) { }
 
     /**
+     * Called when the visibility of the keyguard changes.
+     * @param showing Indicates if the keyguard is now visible.
+     */
+    void onKeyguardVisibilityChanged(boolean showing) { }
+
+    /**
      * Called when visibility of lockscreen clock changes, such as when
      * obscured by a widget.
      */
index 3191f4a..9e3424d 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.android.internal.policy.impl.keyguard;
 
+import android.app.Activity;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
@@ -27,11 +28,11 @@ 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;
+import android.view.KeyEvent;
+import android.widget.FrameLayout;
 
 /**
  * Base class for keyguard view.  {@link #reset} is where you should
@@ -42,7 +43,7 @@ import android.util.Slog;
  * Handles intercepting of media keys that still work when the keyguard is
  * showing.
  */
-public abstract class KeyguardViewBase extends LinearLayout {
+public abstract class KeyguardViewBase extends FrameLayout {
 
     private static final int BACKGROUND_COLOR = 0x70000000;
     private AudioManager mAudioManager;
@@ -249,7 +250,10 @@ public abstract class KeyguardViewBase extends LinearLayout {
     @Override
     public void dispatchSystemUiVisibilityChanged(int visibility) {
         super.dispatchSystemUiVisibilityChanged(visibility);
-        setSystemUiVisibility(STATUS_BAR_DISABLE_BACK);
+
+        if (!(mContext instanceof Activity)) {
+            setSystemUiVisibility(STATUS_BAR_DISABLE_BACK);
+        }
     }
 
     public void setViewMediatorCallback(
index b66c883..452fbca 100644 (file)
@@ -233,7 +233,6 @@ public class KeyguardViewManager {
 
         if (mScreenOn) {
             mKeyguardView.show();
-            mKeyguardView.requestFocus();
         }
     }
 
index cb70922..f82ee9e 100644 (file)
@@ -520,11 +520,11 @@ public class KeyguardViewMediator {
             if (DEBUG) Log.d(TAG, "onSystemReady");
             mSystemReady = true;
             mUpdateMonitor.registerCallback(mUpdateCallback);
-            
+
             // Disable alternate unlock right after boot until things have settled.
             mUpdateMonitor.setAlternateUnlockEnabled(false);
             mUpdateMonitor.setIsFirstBoot(true);
-            
+
             doKeyguardLocked();
         }
         // Most services aren't available until the system reaches the ready state, so we
@@ -629,7 +629,9 @@ public class KeyguardViewMediator {
             mScreenOn = true;
             cancelDoKeyguardLaterLocked();
             if (DEBUG) Log.d(TAG, "onScreenTurnedOn, seq = " + mDelayedShowingSequence);
-            notifyScreenOnLocked(showListener);
+            if (showListener != null) {
+                notifyScreenOnLocked(showListener);
+            }
         }
         maybeSendUserPresentBroadcast();
     }
@@ -769,6 +771,7 @@ public class KeyguardViewMediator {
      */
     public void setHidden(boolean isHidden) {
         if (DEBUG) Log.d(TAG, "setHidden " + isHidden);
+        mUpdateMonitor.sendKeyguardVisibilityChanged(!isHidden);
         mHandler.removeMessages(SET_HIDDEN);
         Message msg = mHandler.obtainMessage(SET_HIDDEN, (isHidden ? 1 : 0), 0);
         mHandler.sendMessage(msg);
@@ -1328,7 +1331,9 @@ public class KeyguardViewMediator {
                         + " isSecure=" + isSecure() + " --> flags=0x" + Integer.toHexString(flags));
             }
 
-            mStatusBarManager.disable(flags);
+            if (!(mContext instanceof Activity)) {
+                mStatusBarManager.disable(flags);
+            }
         }
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewStateManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewStateManager.java
new file mode 100644 (file)
index 0000000..c7acb71
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * 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;
+
+public class KeyguardViewStateManager implements SlidingChallengeLayout.OnChallengeScrolledListener {
+
+    private KeyguardWidgetPager mPagedView;
+    private int mCurrentPageIndex;
+    private SlidingChallengeLayout mSlidingChallengeLayout;
+    private int[] mTmpPoint = new int[2];
+
+    int mChallengeTop = 0;
+
+    public KeyguardViewStateManager() {
+    }
+
+    public void setPagedView(KeyguardWidgetPager pagedView) {
+        mPagedView = pagedView;
+    }
+
+    public void setSlidingChallenge(SlidingChallengeLayout layout) {
+        mSlidingChallengeLayout = layout;
+    }
+
+    public void onPageBeginMoving() {
+        if (mSlidingChallengeLayout.isChallengeShowing()) {
+            mSlidingChallengeLayout.showChallenge(false);
+        }
+    }
+
+    public void onPageEndMoving() {
+    }
+
+    public void showBouncer(boolean show) {
+        mSlidingChallengeLayout.showChallenge(show);
+    }
+
+    public void onPageSwitch(View newPage, int newPageIndex) {
+        // Reset the previous page size and ensure the current page is sized appropriately
+        if (mPagedView != null) {
+            KeyguardWidgetFrame oldPage = mPagedView.getWidgetPageAt(mCurrentPageIndex);
+            // Reset the old widget page to full size
+            if (oldPage != null) {
+                oldPage.resetSize();
+            }
+
+            KeyguardWidgetFrame newCurPage = mPagedView.getWidgetPageAt(newPageIndex);
+            if (mSlidingChallengeLayout.isChallengeShowing()) {
+                sizeWidgetFrameToChallengeTop(newCurPage);
+            }
+        }
+        mCurrentPageIndex = newPageIndex;
+    }
+
+    private void sizeWidgetFrameToChallengeTop(KeyguardWidgetFrame frame) {
+        if (frame == null) return;
+        mTmpPoint[0] = 0;
+        mTmpPoint[1] = mChallengeTop;
+        mapPoint(mSlidingChallengeLayout, frame, mTmpPoint);
+        frame.setChallengeTop(mTmpPoint[1]);
+    }
+
+    /**
+     * Simple method to map a point from one view's coordinates to another's. Note: this method
+     * doesn't account for transforms, so if the views will be transformed, this should not be used.
+     *
+     * @param fromView The view to which the point is relative
+     * @param toView The view into which the point should be mapped
+     * @param pt The point
+     */
+    public void mapPoint(View fromView, View toView, int pt[]) {
+        int[] loc = new int[2];
+        fromView.getLocationInWindow(loc);
+        int x = loc[0];
+        int y = loc[1];
+
+        toView.getLocationInWindow(loc);
+        int vX = loc[0];
+        int vY = loc[1];
+
+        pt[0] += x - vX;
+        pt[1] += y - vY;
+    }
+
+    @Override
+    public void onScrollStateChanged(int scrollState) {
+        if (scrollState == SlidingChallengeLayout.SCROLL_STATE_IDLE) {
+            if (mPagedView == null) return;
+
+            boolean challengeShowing = mSlidingChallengeLayout.isChallengeShowing();
+            int curPage = mPagedView.getCurrentPage();
+            KeyguardWidgetFrame frame = mPagedView.getWidgetPageAt(curPage);
+
+            if (frame != null) {
+                if (!challengeShowing) {
+                    frame.resetSize();
+                } else {
+                    sizeWidgetFrameToChallengeTop(frame);
+                }
+            }
+
+            if (challengeShowing) {
+                mPagedView.setOnlyAllowEdgeSwipes(true);
+            } else {
+                mPagedView.setOnlyAllowEdgeSwipes(false);
+            }
+
+        }
+    }
+
+    @Override
+    public void onScrollPositionChanged(float scrollPosition, int challengeTop) {
+        mChallengeTop = challengeTop;
+    }
+
+}
index 311eec6..9671672 100644 (file)
@@ -19,17 +19,18 @@ package com.android.internal.policy.impl.keyguard;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.LinearGradient;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.View;
 import android.widget.FrameLayout;
 
 import com.android.internal.R;
@@ -51,6 +52,11 @@ public class KeyguardWidgetFrame extends FrameLayout {
     private PowerManager mPowerManager;
     private boolean mDisableInteraction;
 
+    private float mBackgroundAlpha;
+    private float mBackgroundAlphaMultiplier;
+    private Drawable mBackgroundDrawable;
+    private Rect mBackgroundRect = new Rect();
+
     public KeyguardWidgetFrame(Context context) {
         this(context, null, 0);
     }
@@ -65,10 +71,18 @@ public class KeyguardWidgetFrame extends FrameLayout {
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
 
         Resources res = context.getResources();
+        /*
         int hPadding = res.getDimensionPixelSize(R.dimen.kg_widget_pager_horizontal_padding);
         int topPadding = res.getDimensionPixelSize(R.dimen.kg_widget_pager_top_padding);
         int bottomPadding = res.getDimensionPixelSize(R.dimen.kg_widget_pager_bottom_padding);
         setPadding(hPadding, topPadding, hPadding, bottomPadding);
+        */
+        // TODO: this padding should really correspond to the padding embedded in the background
+        // drawable (ie. outlines).
+        int padding = (int) (res.getDisplayMetrics().density * 8);
+        setPadding(padding, padding, padding, padding);
+
+        mBackgroundDrawable = res.getDrawable(R.drawable.security_frame);
         mGradientColor = res.getColor(com.android.internal.R.color.kg_widget_pager_gradient);
         mGradientPaint.setXfermode(sAddBlendMode);
     }
@@ -88,17 +102,114 @@ public class KeyguardWidgetFrame extends FrameLayout {
 
     @Override
     protected void dispatchDraw(Canvas canvas) {
+        drawBg(canvas);
         super.dispatchDraw(canvas);
         drawGradientOverlay(canvas);
 
     }
 
+    /**
+     * Because this view has fading outlines, it is essential that we enable hardware
+     * layers on the content (child) so that updating the alpha of the outlines doesn't
+     * result in the content layer being recreated.
+     */
+    public void enableHardwareLayersForContent() {
+        View widget = getContent();
+        if (widget != null) {
+            widget.setLayerType(LAYER_TYPE_HARDWARE, null);
+        }
+    }
+
+    /**
+     * Because this view has fading outlines, it is essential that we enable hardware
+     * layers on the content (child) so that updating the alpha of the outlines doesn't
+     * result in the content layer being recreated.
+     */
+    public void disableHardwareLayersForContent() {
+        View widget = getContent();
+        if (widget != null) {
+            widget.setLayerType(LAYER_TYPE_NONE, null);
+        }
+    }
+
+    public View getContent() {
+        return getChildAt(0);
+    }
+
     private void drawGradientOverlay(Canvas c) {
         mGradientPaint.setShader(mForegroundGradient);
         mGradientPaint.setAlpha(mForegroundAlpha);
         c.drawRect(mForegroundRect, mGradientPaint);
     }
 
+    protected void drawBg(Canvas canvas) {
+        if (mBackgroundAlpha > 0.0f) {
+            Drawable bg = mBackgroundDrawable;
+
+            bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255));
+            bg.setBounds(mBackgroundRect);
+            bg.draw(canvas);
+        }
+    }
+
+    public float getBackgroundAlpha() {
+        return mBackgroundAlpha;
+    }
+
+    public void setBackgroundAlphaMultiplier(float multiplier) {
+        if (mBackgroundAlphaMultiplier != multiplier) {
+            mBackgroundAlphaMultiplier = multiplier;
+            invalidate();
+        }
+    }
+
+    public float getBackgroundAlphaMultiplier() {
+        return mBackgroundAlphaMultiplier;
+    }
+
+    public void setBackgroundAlpha(float alpha) {
+        if (mBackgroundAlpha != alpha) {
+            mBackgroundAlpha = alpha;
+            invalidate();
+        }
+    }
+
+    /**
+     * Depending on whether the security is up, the widget size needs to change
+     * 
+     * @param height The height of the widget, -1 for full height
+     */
+    public void setWidgetHeight(int height) {
+        boolean needLayout = false;
+        View widget = getContent();
+        if (widget != null) {
+            LayoutParams lp = (LayoutParams) widget.getLayoutParams();
+            if (lp.height != height) {
+                needLayout = true;
+                lp.height = height;
+            }
+        }
+        if (needLayout) {
+            requestLayout();
+        }
+    }
+
+    /**
+     * Set the top location of the challenge.
+     *
+     * @param top The top of the challenge, in _local_ coordinates, or -1 to indicate the challenge
+     *              is down.
+     */
+    public void setChallengeTop(int top) {
+        // The widget starts below the padding, and extends to the top of the challengs.
+        int widgetHeight = top - getPaddingTop();
+        setWidgetHeight(widgetHeight);
+    }
+
+    public void resetSize() {
+        setWidgetHeight(LayoutParams.MATCH_PARENT);
+    }
+
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
@@ -110,6 +221,7 @@ public class KeyguardWidgetFrame extends FrameLayout {
                 mGradientColor, 0, Shader.TileMode.CLAMP);
         mRightToLeftGradient = new LinearGradient(x1, 0f, x0, 0f,
                 mGradientColor, 0, Shader.TileMode.CLAMP);
+        mBackgroundRect.set(0, 0, w, h);
     }
 
     void setOverScrollAmount(float r, boolean left) {
index 1e65665..806ece5 100644 (file)
@@ -15,6 +15,7 @@
  */
 package com.android.internal.policy.impl.keyguard;
 
+import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.appwidget.AppWidgetHostView;
 import android.content.Context;
@@ -22,14 +23,17 @@ import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
-
 import android.widget.FrameLayout;
 
 import com.android.internal.R;
 
-public class KeyguardWidgetPager extends PagedView {
+public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwitchListener,
+        OnLongClickListener {
+
     ZInterpolator mZInterpolator = new ZInterpolator(0.5f);
     private static float CAMERA_DISTANCE = 10000;
     private static float TRANSITION_SCALE_FACTOR = 0.74f;
@@ -38,6 +42,21 @@ public class KeyguardWidgetPager extends PagedView {
     private static final boolean PERFORM_OVERSCROLL_ROTATION = true;
     private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f);
     private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4);
+    private KeyguardViewStateManager mViewStateManager;
+
+    // Related to the fading in / out background outlines
+    private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
+    private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
+    private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
+    private ObjectAnimator mChildrenOutlineFadeInAnimation;
+    private ObjectAnimator mChildrenOutlineFadeOutAnimation;
+    private float mChildrenOutlineAlpha = 0;
+
+    private static final long CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT = 30000;
+    private static final boolean CAFETERIA_TRAY = false;
+
+    private int mPage = 0;
+    private Callbacks mCallbacks;
 
     public KeyguardWidgetPager(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -52,38 +71,162 @@ public class KeyguardWidgetPager extends PagedView {
         if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
             setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
         }
+
+        setPageSwitchListener(this);
+    }
+
+    public void setViewStateManager(KeyguardViewStateManager viewStateManager) {
+        mViewStateManager = viewStateManager;
+    }
+
+    @Override
+    public void onPageSwitch(View newPage, int newPageIndex) {
+        boolean showingStatusWidget = false;
+        if (newPage instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) newPage;
+            if (vg.getChildAt(0) instanceof KeyguardStatusView) {
+                showingStatusWidget = true;
+            }
+        }
+
+        // Disable the status bar clock if we're showing the default status widget
+        if (showingStatusWidget) {
+            setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_CLOCK);
+        } else {
+            setSystemUiVisibility(getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_CLOCK);
+        }
+
+        // Extend the display timeout if the user switches pages
+        if (mPage != newPageIndex) {
+            mPage = newPageIndex;
+            if (mCallbacks != null) {
+                mCallbacks.onUserActivityTimeoutChanged();
+                mCallbacks.userActivity();
+            }
+        }
+        if (mViewStateManager != null) {
+            mViewStateManager.onPageSwitch(newPage, newPageIndex);
+        }
+    }
+
+    public void showPagingFeedback() {
+        // Nothing yet.
+    }
+
+    public long getUserActivityTimeout() {
+        View page = getPageAt(mPage);
+        if (page instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) page;
+            View view = vg.getChildAt(0);
+            if (!(view instanceof KeyguardStatusView)
+                    && !(view instanceof KeyguardMultiUserSelectorView)) {
+                return CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT;
+            }
+        }
+        return -1;
+    }
+
+    public void setCallbacks(Callbacks callbacks) {
+        mCallbacks = callbacks;
+    }
+
+    public interface Callbacks {
+        public void userActivity();
+        public void onUserActivityTimeoutChanged();
+    }
+
+    public void addWidget(View widget) {
+        addWidget(widget, -1);
     }
 
     /*
-     * We wrap widgets in a special frame which handles drawing the overscroll foreground.
+     * We wrap widgets in a special frame which handles drawing the over scroll foreground.
      */
-    public void addWidget(AppWidgetHostView widget) {
-        KeyguardWidgetFrame frame = new KeyguardWidgetFrame(getContext());
-        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
-                LayoutParams.MATCH_PARENT);
-        lp.gravity = Gravity.CENTER;
-        // The framework adds a default padding to AppWidgetHostView. We don't need this padding
-        // for the Keyguard, so we override it to be 0.
-        widget.setPadding(0,  0, 0, 0);
-        widget.setContentDescription(widget.getAppWidgetInfo().label);
-        frame.addView(widget, lp);
-        addView(frame);
+    public void addWidget(View widget, int pageIndex) {
+        KeyguardWidgetFrame frame;
+        // All views contained herein should be wrapped in a KeyguardWidgetFrame
+        if (!(widget instanceof KeyguardWidgetFrame)) {
+            frame = new KeyguardWidgetFrame(getContext());
+            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+                    LayoutParams.MATCH_PARENT);
+            lp.gravity = Gravity.TOP;
+            // The framework adds a default padding to AppWidgetHostView. We don't need this padding
+            // for the Keyguard, so we override it to be 0.
+            widget.setPadding(0,  0, 0, 0);
+            if (widget instanceof AppWidgetHostView) {
+                AppWidgetHostView awhv = (AppWidgetHostView) widget;
+                widget.setContentDescription(awhv.getAppWidgetInfo().label);
+            }
+            frame.addView(widget, lp);
+        } else {
+            frame = (KeyguardWidgetFrame) widget;
+        }
+
+        ViewGroup.LayoutParams pageLp = new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+        frame.setOnLongClickListener(this);
+
+        if (pageIndex == -1) {
+            addView(frame, pageLp);
+        } else {
+            addView(frame, pageIndex, pageLp);
+        }
     }
 
-    protected void onUnhandledTap(MotionEvent ev) {
-        if (getParent() instanceof KeyguardWidgetRegion) {
-            ((KeyguardWidgetRegion) getParent()).showPagingFeedback();
+    // We enforce that all children are KeyguardWidgetFrames
+    @Override
+    public void addView(View child, int index) {
+        enforceKeyguardWidgetFrame(child);
+        super.addView(child, index);
+    }
+
+    @Override
+    public void addView(View child, int width, int height) {
+        enforceKeyguardWidgetFrame(child);
+        super.addView(child, width, height);
+    }
+
+    @Override
+    public void addView(View child, LayoutParams params) {
+        enforceKeyguardWidgetFrame(child);
+        super.addView(child, params);
+    }
+
+    @Override
+    public void addView(View child, int index, LayoutParams params) {
+        enforceKeyguardWidgetFrame(child);
+        super.addView(child, index, params);
+    }
+
+    private void enforceKeyguardWidgetFrame(View child) {
+        if (!(child instanceof KeyguardWidgetFrame)) {
+            throw new IllegalArgumentException(
+                    "KeyguardWidgetPager children must be KeyguardWidgetFrames");
         }
     }
 
+    public KeyguardWidgetFrame getWidgetPageAt(int index) {
+        // This is always a valid cast as we've guarded the ability to
+        return (KeyguardWidgetFrame) getChildAt(index);
+    }
+
+    protected void onUnhandledTap(MotionEvent ev) {
+        showPagingFeedback();
+    }
+
     @Override
     protected void onPageBeginMoving() {
         // Enable hardware layers while pages are moving
         // TODO: We should only do this for the two views that are actually moving
         int children = getChildCount();
         for (int i = 0; i < children; i++) {
-            getChildAt(i).setLayerType(LAYER_TYPE_HARDWARE, null);
+            getWidgetPageAt(i).enableHardwareLayersForContent();
+        }
+
+        if (mViewStateManager != null) {
+            mViewStateManager.onPageBeginMoving();
         }
+        showOutlines();
     }
 
     @Override
@@ -91,8 +234,13 @@ public class KeyguardWidgetPager extends PagedView {
         // Disable hardware layers while pages are moving
         int children = getChildCount();
         for (int i = 0; i < children; i++) {
-            getChildAt(i).setLayerType(LAYER_TYPE_NONE, null);
+            getWidgetPageAt(i).disableHardwareLayersForContent();
         }
+
+        if (mViewStateManager != null) {
+            mViewStateManager.onPageEndMoving();
+        }
+        hideOutlines();
     }
 
     /*
@@ -118,7 +266,7 @@ public class KeyguardWidgetPager extends PagedView {
     public String getCurrentPageDescription() {
         final int nextPageIndex = getNextPage();
         if (nextPageIndex >= 0 && nextPageIndex < getChildCount()) {
-            KeyguardWidgetFrame frame = (KeyguardWidgetFrame) getChildAt(nextPageIndex);
+            KeyguardWidgetFrame frame = getWidgetPageAt(nextPageIndex);
             CharSequence title = frame.getChildAt(0).getContentDescription();
             if (title == null) {
                 title = "";
@@ -135,30 +283,59 @@ public class KeyguardWidgetPager extends PagedView {
         acceleratedOverScroll(amount);
     }
 
+    float backgroundAlphaInterpolator(float r) {
+        return r;
+    }
+
+    private void updatePageAlphaValues(int screenCenter) {
+        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
+        if (!isInOverscroll) {
+            for (int i = 0; i < getChildCount(); i++) {
+                KeyguardWidgetFrame child = getWidgetPageAt(i);
+                if (child != null) {
+                    float scrollProgress = getScrollProgress(screenCenter, child, i);
+                    float alpha = 1 - Math.abs(scrollProgress);
+                    // TODO: Set content alpha
+                    if (!isReordering()) {
+                        child.setBackgroundAlphaMultiplier(
+                                backgroundAlphaInterpolator(Math.abs(scrollProgress)));
+                    } else {
+                        child.setBackgroundAlphaMultiplier(1f);
+                    }
+                }
+            }
+        }
+    }
+
     // 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);
-
+        updatePageAlphaValues(screenCenter);
         for (int i = 0; i < getChildCount(); i++) {
-            View v = getPageAt(i);
+            KeyguardWidgetFrame v = getWidgetPageAt(i);
+            if (v == mDragView) continue;
             if (v != null) {
                 float scrollProgress = getScrollProgress(screenCenter, v, i);
-
-                float interpolatedProgress =
+                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);
+
+                float scale = 1.0f;
+                float translationX = 0;
+                float alpha = 1.0f;
+
+                if (CAFETERIA_TRAY) {
+                    scale = (1 - interpolatedProgress) +
+                            interpolatedProgress * TRANSITION_SCALE_FACTOR;
+                    translationX = Math.min(0, scrollProgress) * v.getMeasuredWidth();
+
+                    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);
@@ -170,10 +347,7 @@ public class KeyguardWidgetPager extends PagedView {
                         // Overscroll to the left
                         v.setPivotX(TRANSITION_PIVOT * pageWidth);
                         v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
-                        if (v instanceof KeyguardWidgetFrame) {
-                            ((KeyguardWidgetFrame) v).setOverScrollAmount(Math.abs(scrollProgress),
-                                    true);
-                        }
+                        v.setOverScrollAmount(Math.abs(scrollProgress), true);
                         scale = 1.0f;
                         alpha = 1.0f;
                         // On the first page, we don't want the page to have any lateral motion
@@ -184,25 +358,22 @@ public class KeyguardWidgetPager extends PagedView {
                         v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress);
                         scale = 1.0f;
                         alpha = 1.0f;
-                        if (v instanceof KeyguardWidgetFrame) {
-                            ((KeyguardWidgetFrame) v).setOverScrollAmount(Math.abs(scrollProgress),
-                                    false);
-                        }
+                        v.setOverScrollAmount(Math.abs(scrollProgress), false);
                         // 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);
-                        if (v instanceof KeyguardWidgetFrame) {
-                            ((KeyguardWidgetFrame) v).setOverScrollAmount(0, false);
-                        }
+                        v.setOverScrollAmount(0, false);
                     }
                 }
 
-                v.setTranslationX(translationX);
-                v.setScaleX(scale);
-                v.setScaleY(scale);
+                if (CAFETERIA_TRAY) {
+                    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
@@ -215,4 +386,60 @@ public class KeyguardWidgetPager extends PagedView {
             }
         }
     }
+
+    @Override
+    protected void onStartReordering() {
+        super.onStartReordering();
+        setChildrenOutlineMultiplier(1.0f);
+        showOutlines();
+    }
+
+    @Override
+    protected void onEndReordering() {
+        super.onEndReordering();
+        hideOutlines();
+    }
+
+    void showOutlines() {
+        if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
+        if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
+        mChildrenOutlineFadeInAnimation = ObjectAnimator.ofFloat(this,
+                "childrenOutlineAlpha", 1.0f);
+        mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
+        mChildrenOutlineFadeInAnimation.start();
+    }
+
+    void hideOutlines() {
+        if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
+        if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
+        mChildrenOutlineFadeOutAnimation = ObjectAnimator.ofFloat(this,
+                "childrenOutlineAlpha", 0.0f);
+        mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
+        mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
+        mChildrenOutlineFadeOutAnimation.start();
+    }
+
+    public void setChildrenOutlineAlpha(float alpha) {
+        mChildrenOutlineAlpha = alpha;
+        for (int i = 0; i < getChildCount(); i++) {
+            getWidgetPageAt(i).setBackgroundAlpha(alpha);
+        }
+    }
+
+    public void setChildrenOutlineMultiplier(float alpha) {
+        mChildrenOutlineAlpha = alpha;
+        for (int i = 0; i < getChildCount(); i++) {
+            getWidgetPageAt(i).setBackgroundAlphaMultiplier(alpha);
+        }
+    }
+
+    public float getChildrenOutlineAlpha() {
+        return mChildrenOutlineAlpha;
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        startReordering();
+        return true;
+    }
 }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetRegion.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetRegion.java
deleted file mode 100644 (file)
index 4ff6f27..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * 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 android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-import com.android.internal.R;
-public class KeyguardWidgetRegion extends LinearLayout implements PagedView.PageSwitchListener {
-    KeyguardGlowStripView mLeftStrip;
-    KeyguardGlowStripView mRightStrip;
-    KeyguardWidgetPager mPager;
-    private int mPage = 0;
-    private Callbacks mCallbacks;
-
-    // We are disabling touch interaction of the widget region for factory ROM. 
-    private static final boolean DISABLE_TOUCH_INTERACTION = true;
-
-    private static final long CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT = 30000;
-
-    public KeyguardWidgetRegion(Context context) {
-        this(context, null, 0);
-    }
-
-    public KeyguardWidgetRegion(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public KeyguardWidgetRegion(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mLeftStrip = (KeyguardGlowStripView) findViewById(R.id.left_strip);
-        mRightStrip = (KeyguardGlowStripView) findViewById(R.id.right_strip);
-        mPager = (KeyguardWidgetPager) findViewById(R.id.app_widget_container);
-        mPager.setPageSwitchListener(this);
-
-        setSoundEffectsEnabled(false);
-        if (!DISABLE_TOUCH_INTERACTION) {
-            setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    showPagingFeedback();
-                }
-            });
-        }
-    }
-
-    public void showPagingFeedback() {
-        if ((mPage < mPager.getPageCount() - 1)) {
-            mLeftStrip.makeEmGo();
-        }
-        if ((mPage > 0)) {
-            mRightStrip.makeEmGo();
-        }
-    }
-
-    @Override
-    public void onPageSwitch(View newPage, int newPageIndex) {
-        boolean showingStatusWidget = false;
-        if (newPage instanceof ViewGroup) {
-            ViewGroup vg = (ViewGroup) newPage;
-            if (vg.getChildAt(0) instanceof KeyguardStatusView) {
-                showingStatusWidget = true;
-            }
-        }
-
-        // Disable the status bar clock if we're showing the default status widget
-        if (showingStatusWidget) {
-            setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_CLOCK);
-        } else {
-            setSystemUiVisibility(getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_CLOCK);
-        }
-
-        // Extend the display timeout if the user switches pages
-        if (mPage != newPageIndex) {
-            mPage = newPageIndex;
-            if (mCallbacks != null) {
-                mCallbacks.onUserActivityTimeoutChanged();
-                mCallbacks.userActivity();
-            }
-        }
-    }
-
-    public long getUserActivityTimeout() {
-        View page = mPager.getPageAt(mPage);
-        if (page instanceof ViewGroup) {
-            ViewGroup vg = (ViewGroup) page;
-            View view = vg.getChildAt(0);
-            if (!(view instanceof KeyguardStatusView)
-                    && !(view instanceof KeyguardMultiUserSelectorView)) {
-                return CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT;
-            }
-        }
-        return -1;
-    }
-
-    public void setCallbacks(Callbacks callbacks) {
-        mCallbacks = callbacks;
-    }
-
-    public interface Callbacks {
-        public void userActivity();
-        public void onUserActivityTimeoutChanged();
-    }
-}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java b/policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java
new file mode 100644 (file)
index 0000000..90f49c4
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * 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.content.res.TypedArray;
+import android.text.SpannableStringBuilder;
+import android.text.style.TextAppearanceSpan;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+public class NumPadKey extends Button {
+    // XXX localize
+    static final String KLONDIKE[] = {
+        "", "", " ABC", " DEF", " GHI", " JKL", " MNO", " PQRS", " TUV", " WXYZ" };
+
+    int mDigit = -1;
+    int mTextViewResId;
+    TextView mTextView = null;
+
+    private View.OnClickListener mListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View thisView) {
+            if (mTextView == null) {
+                if (mTextViewResId > 0) {
+                    final View v = NumPadKey.this.getRootView().findViewById(mTextViewResId);
+                    if (v != null && v instanceof TextView) {
+                        mTextView = (TextView) v;
+                    }
+                }
+            }
+            if (mTextView != null) {
+                mTextView.append(String.valueOf(mDigit));
+            }
+        }
+    };
+
+    public NumPadKey(Context context) {
+        this(context, null);
+    }
+
+    public NumPadKey(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NumPadKey(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NumPadKey);
+        mDigit = a.getInt(R.styleable.NumPadKey_digit, mDigit);
+        setTextViewResId(a.getResourceId(R.styleable.NumPadKey_textView, 0));
+
+        setOnClickListener(mListener);
+
+        SpannableStringBuilder builder = new SpannableStringBuilder();
+        builder.append(String.valueOf(mDigit));
+        if (mDigit >= 0) {
+            final String extra = KLONDIKE[mDigit];
+            final int extraLen = extra.length();
+            if (extraLen > 0) {
+                builder.append(extra);
+                builder.setSpan(
+                        new TextAppearanceSpan(context, R.style.TextAppearance_NumPadKey_Klondike),
+                        builder.length()-extraLen, builder.length(), 0);
+            }
+        }
+        setText(builder);
+    }
+
+    public void setTextView(TextView tv) {
+        mTextView = tv;
+    }
+
+    public void setTextViewResId(int resId) {
+        mTextView = null;
+        mTextViewResId = resId;
+    }
+}
index 86c05b1..259fe76 100644 (file)
@@ -18,12 +18,17 @@ package com.android.internal.policy.impl.keyguard;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -38,9 +43,13 @@ import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.widget.Scroller;
 
@@ -60,7 +69,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
     // 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 PAGE_SNAP_ANIMATION_DURATION = 750;
     protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
     protected static final float NANOTIME_DIV = 1000000000.0f;
 
@@ -78,7 +87,9 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
     private static final int MIN_FLING_VELOCITY = 250;
 
     // We are disabling touch interaction of the widget region for factory ROM. 
-    private static final boolean DISABLE_TOUCH_INTERACTION = true;
+    private static final boolean DISABLE_TOUCH_INTERACTION = false;
+    private static final boolean DISABLE_TOUCH_SIDE_PAGES = true;
+    private static final boolean DISABLE_FLING_TO_DELETE = false;
 
     static final int AUTOMATIC_PAGE_SPACING = -1;
 
@@ -100,7 +111,11 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
     protected Scroller mScroller;
     private VelocityTracker mVelocityTracker;
 
+    private float mParentDownMotionX;
+    private float mParentDownMotionY;
     private float mDownMotionX;
+    private float mDownMotionY;
+    private float mDownScrollX;
     protected float mLastMotionX;
     protected float mLastMotionXRemainder;
     protected float mLastMotionY;
@@ -114,6 +129,8 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
     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 int TOUCH_STATE_REORDERING = 4;
+
     protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
 
     protected int mTouchState = TOUCH_STATE_REST;
@@ -128,15 +145,8 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
     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];
@@ -162,7 +172,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
     protected boolean mContentIsRefreshable = true;
 
     // If true, modify alpha of neighboring pages as user scrolls left/right
-    protected boolean mFadeInAdjacentScreens = true;
+    protected boolean mFadeInAdjacentScreens = false;
 
     // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
     // to switch to a new page
@@ -188,6 +198,37 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
     protected static final int sScrollIndicatorFadeOutDuration = 650;
     protected static final int sScrollIndicatorFlashDuration = 650;
 
+    // Reordering
+    // We use the min scale to determine how much to expand the actually PagedView measured
+    // dimensions such that when we are zoomed out, the view is not clipped
+    private int REORDERING_DROP_REPOSITION_DURATION = 200;
+    private int REORDERING_REORDER_REPOSITION_DURATION = 350;
+    private int REORDERING_ZOOM_IN_OUT_DURATION = 250;
+    private int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 500;
+    private float REORDERING_SIDE_PAGE_BUFFER_PERCENTAGE = 0.1f;
+    private float mMinScale = 1f;
+    protected View mDragView;
+    private AnimatorSet mZoomInOutAnim;
+    private Runnable mSidePageHoverRunnable;
+    private int mSidePageHoverIndex = -1;
+
+    // Edge swiping
+    private boolean mOnlyAllowEdgeSwipes = false;
+    private boolean mDownEventOnEdge = false;
+    private int mEdgeSwipeRegionSize = 0;
+
+    // Convenience/caching
+    private Matrix mTmpInvMatrix = new Matrix();
+    private float[] mTmpPoint = new float[2];
+
+    // Fling to delete
+    private int FLING_TO_DELETE_FADE_OUT_DURATION = 350;
+    private float FLING_TO_DELETE_FRICTION = 0.035f;
+    // The degrees specifies how much deviation from the up vector to still consider a fling "up"
+    private float FLING_TO_DELETE_MAX_FLING_DEGREES = 35f;
+    protected int mFlingToDeleteThresholdVelocity = -1400; // TEMPORARY
+    private boolean mIsFlingingToDelete = false;
+
     public interface PageSwitchListener {
         void onPageSwitch(View newPage, int newPageIndex);
     }
@@ -205,22 +246,12 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         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);
+        mEdgeSwipeRegionSize =
+                a.getDimensionPixelSize(R.styleable.PagedView_edgeSwipeRegionSize, 0);
         a.recycle();
 
         setHapticFeedbackEnabled(false);
@@ -235,7 +266,6 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         mDirtyPageContent.ensureCapacity(32);
         mScroller = new Scroller(getContext(), new ScrollInterpolator());
         mCurrentPage = 0;
-        mCenterPagesVertically = true;
 
         final ViewConfiguration configuration = ViewConfiguration.get(getContext());
         mTouchSlop = configuration.getScaledTouchSlop();
@@ -249,6 +279,66 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         setOnHierarchyChangeListener(this);
     }
 
+    // Convenience methods to map points from self to parent and vice versa
+    float[] mapPointFromSelfToParent(float x, float y) {
+        mTmpPoint[0] = x;
+        mTmpPoint[1] = y;
+        getMatrix().mapPoints(mTmpPoint);
+        mTmpPoint[0] += getLeft();
+        mTmpPoint[1] += getTop();
+        return mTmpPoint;
+    }
+    float[] mapPointFromParentToSelf(float x, float y) {
+        mTmpPoint[0] = x - getLeft();
+        mTmpPoint[1] = y - getTop();
+        getMatrix().invert(mTmpInvMatrix);
+        mTmpInvMatrix.mapPoints(mTmpPoint);
+        return mTmpPoint;
+    }
+
+    void updateDragViewTranslationDuringDrag() {
+        float x = mLastMotionX - mDownMotionX + getScrollX() - mDownScrollX;
+        float y = mLastMotionY - mDownMotionY;
+        mDragView.setTranslationX(x);
+        mDragView.setTranslationY(y);
+
+        if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): " + x + ", " + y);
+    }
+
+    public void setMinScale(float f) {
+        mMinScale = f;
+        requestLayout();
+    }
+
+    @Override
+    public void setScaleX(float scaleX) {
+        super.setScaleX(scaleX);
+        if (mTouchState == TOUCH_STATE_REORDERING) {
+            float[] p = mapPointFromParentToSelf(mParentDownMotionX, mParentDownMotionY);
+            mLastMotionX = p[0];
+            mLastMotionY = p[1];
+            updateDragViewTranslationDuringDrag();
+        }
+    }
+
+    // Convenience methods to get the actual width/height of the PagedView (since it is measured
+    // to be larger to account for the minimum possible scale)
+    int getMinScaledMeasuredWidth() {
+        return (int) (getMeasuredWidth() * mMinScale);
+    }
+    int getMinScaledMeasuredHeight() {
+        return (int) (getMeasuredHeight() * mMinScale);
+    }
+
+    // Convenience methods to get the offset ASSUMING that we are centering the pages in the
+    // PagedView both horizontally and vertically
+    int getOffsetX() {
+        return (getMeasuredWidth() - getMinScaledMeasuredWidth()) / 2;
+    }
+    int getOffsetY() {
+        return (getMeasuredHeight() - getMinScaledMeasuredHeight()) / 2;
+    }
+
     public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
         mPageSwitchListener = pageSwitchListener;
         if (mPageSwitchListener != null) {
@@ -328,6 +418,10 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         invalidate();
     }
 
+    public void setOnlyAllowEdgeSwipes(boolean enable) {
+        mOnlyAllowEdgeSwipes = enable;
+    }
+
     protected void notifyPageSwitchListener() {
         if (mPageSwitchListener != null) {
             mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
@@ -400,6 +494,14 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
 
         mTouchX = x;
         mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+
+        // Update the last motion events when scrolling
+        if (mTouchState == TOUCH_STATE_REORDERING) {
+            float[] p = mapPointFromParentToSelf(mParentDownMotionX, mParentDownMotionY);
+            mLastMotionX = p[0];
+            mLastMotionY = p[1];
+            updateDragViewTranslationDuringDrag();
+        }
     }
 
     // we moved this functionality to a helper function so SmoothPagedView can reuse it
@@ -454,6 +556,10 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
             return;
         }
 
+        // We measure the dimensions of the PagedView to be larger than the pages so that when we
+        // zoom out (and scale down), the view is still contained in the parent
+        int scaledWidthSize = (int) (MeasureSpec.getSize(widthMeasureSpec) / mMinScale);
+        int scaledHeightSize = (int) (MeasureSpec.getSize(heightMeasureSpec) / mMinScale);
         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
         final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
@@ -481,13 +587,26 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         // 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);
+        if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding);
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             // disallowing padding in paged view (just pass 0)
             final View child = getPageAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
 
-            int childWidthMode = MeasureSpec.EXACTLY;
-            int childHeightMode = MeasureSpec.EXACTLY;
+            int childWidthMode;
+            if (lp.width == LayoutParams.WRAP_CONTENT) {
+                childWidthMode = MeasureSpec.AT_MOST;
+            } else {
+                childWidthMode = MeasureSpec.EXACTLY;
+            }
+
+            int childHeightMode;
+            if (lp.height == LayoutParams.WRAP_CONTENT) {
+                childHeightMode = MeasureSpec.AT_MOST;
+            } else {
+                childHeightMode = MeasureSpec.EXACTLY;
+            }
 
             final int childWidthMeasureSpec =
                 MeasureSpec.makeMeasureSpec(widthSize - horizontalPadding, childWidthMode);
@@ -496,8 +615,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
 
             child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
         }
-
-        setMeasuredDimension(widthSize, heightSize);
+        setMeasuredDimension(scaledWidthSize, scaledHeightSize);
 
         // 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
@@ -510,7 +628,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         mChildCountOnLastMeasure = getChildCount();
 
         if (childCount > 0) {
-            if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMeasuredWidth() + ", "
+            if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMinScaledMeasuredWidth() + ", "
                     + getChildWidth(0));
 
             // Calculate the variable page spacing if necessary
@@ -547,19 +665,18 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         }
 
         if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
-        final int verticalPadding = getPaddingTop() + getPaddingBottom();
         final int childCount = getChildCount();
-        int childLeft = getRelativeChildOffset(0);
 
+        int scaleOffsetX = getOffsetX();
+        int scaleOffsetY = getOffsetY();
+
+        int childLeft = scaleOffsetX + getRelativeChildOffset(0);
         for (int i = 0; i < childCount; i++) {
             final View child = getPageAt(i);
+            int childTop = scaleOffsetY + getPaddingTop();
             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,
@@ -606,7 +723,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
 
     @Override
     public void onChildViewRemoved(View parent, View child) {
-        // TODO Auto-generated method stub
+        mForceScreenScrolled = true;
     }
 
     protected void invalidateCachedOffsets() {
@@ -659,7 +776,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         } else {
             final int padding = getPaddingLeft() + getPaddingRight();
             final int offset = getPaddingLeft() +
-                    (getMeasuredWidth() - padding - getChildWidth(index)) / 2;
+                    (getMinScaledMeasuredWidth() - padding - getChildWidth(index)) / 2;
             if (mChildRelativeOffsets != null) {
                 mChildRelativeOffsets[index] = offset;
             }
@@ -676,33 +793,42 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         return (int) (maxWidth * mLayoutScale + 0.5f);
     }
 
+    // TODO: Fix this
     protected void getVisiblePages(int[] range) {
+        range[0] = 0;
+        range[1] = getPageCount() - 1;
+        /*
         final int pageCount = getChildCount();
 
         if (pageCount > 0) {
-            final int screenWidth = getMeasuredWidth();
+            final int screenWidth = getMinScaledMeasuredWidth();
             int leftScreen = 0;
             int rightScreen = 0;
+            int offsetX = getOffsetX() + getScrollX();
             View currPage = getPageAt(leftScreen);
             while (leftScreen < pageCount - 1 &&
                     currPage.getX() + currPage.getWidth() -
-                    currPage.getPaddingRight() < getScrollX()) {
+                    currPage.getPaddingRight() < offsetX) {
                 leftScreen++;
                 currPage = getPageAt(leftScreen);
             }
             rightScreen = leftScreen;
             currPage = getPageAt(rightScreen + 1);
             while (rightScreen < pageCount - 1 &&
-                    currPage.getX() - currPage.getPaddingLeft() < getScrollX() + screenWidth) {
+                    currPage.getX() - currPage.getPaddingLeft() < offsetX + screenWidth) {
                 rightScreen++;
                 currPage = getPageAt(rightScreen + 1);
             }
-            range[0] = leftScreen;
-            range[1] = rightScreen;
+
+            // TEMP: this is a hacky way to ensure that animations to new pages are not clipped
+            // because we don't draw them while scrolling?
+            range[0] = Math.max(0, leftScreen - 1);
+            range[1] = Math.min(rightScreen + 1, getChildCount() - 1);
         } else {
             range[0] = -1;
             range[1] = -1;
         }
+        */
     }
 
     protected boolean shouldDrawChild(View child) {
@@ -711,7 +837,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
 
     @Override
     protected void dispatchDraw(Canvas canvas) {
-        int halfScreenSize = getMeasuredWidth() / 2;
+        int halfScreenSize = getMinScaledMeasuredWidth() / 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;
@@ -737,13 +863,20 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
                 canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
                         getScrollY() + getBottom() - getTop());
 
-                for (int i = getChildCount() - 1; i >= 0; i--) {
+                // Draw all the children, leaving the drag view for last
+                for (int i = pageCount - 1; i >= 0; i--) {
                     final View v = getPageAt(i);
+                    if (v == mDragView) continue;
                     if (mForceDrawAllChildrenNextFrame ||
                                (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) {
                         drawChild(canvas, v, drawingTime);
                     }
                 }
+                // Draw the drag view on top (if there is one)
+                if (mDragView != null) {
+                    drawChild(canvas, mDragView, drawingTime);
+                }
+
                 mForceDrawAllChildrenNextFrame = false;
                 canvas.restore();
             }
@@ -853,14 +986,14 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
      * 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 (x < getOffsetX() + 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));
+        return  (x > (getOffsetX() + getMinScaledMeasuredWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing));
     }
 
     @Override
@@ -912,12 +1045,23 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
                 final float y = ev.getY();
                 // Remember location of down touch
                 mDownMotionX = x;
+                mDownMotionY = y;
+                mDownScrollX = getScrollX();
                 mLastMotionX = x;
                 mLastMotionY = y;
+                float[] p = mapPointFromSelfToParent(x, y);
+                mParentDownMotionX = p[0];
+                mParentDownMotionY = p[1];
                 mLastMotionXRemainder = 0;
                 mTotalMotionX = 0;
                 mActivePointerId = ev.getPointerId(0);
-                mAllowLongPress = true;
+
+                // Determine if the down event is within the threshold to be an edge swipe
+                int leftEdgeBoundary = getOffsetX() + mEdgeSwipeRegionSize;
+                int rightEdgeBoundary = getMeasuredWidth() - getOffsetX() - mEdgeSwipeRegionSize;
+                if ((mDownMotionX <= leftEdgeBoundary || mDownMotionX >= rightEdgeBoundary)) {
+                    mDownEventOnEdge = true;
+                }
 
                 /*
                  * If being flinged and user touches the screen, initiate drag;
@@ -935,12 +1079,14 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
 
                 // 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;
+                if (!DISABLE_TOUCH_SIDE_PAGES) {
+                    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;
+                            }
                         }
                     }
                 }
@@ -949,10 +1095,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
 
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                mTouchState = TOUCH_STATE_REST;
-                mAllowLongPress = false;
-                mActivePointerId = INVALID_POINTER;
-                releaseVelocityTracker();
+                resetTouchState();
                 break;
 
             case MotionEvent.ACTION_POINTER_UP:
@@ -982,9 +1125,17 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
          * of the down event.
          */
         final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+
         if (pointerIndex == -1) {
             return;
         }
+
+        // If we're only allowing edge swipes, we break out early if the down event wasn't
+        // at the edge.
+        if (mOnlyAllowEdgeSwipes && !mDownEventOnEdge) {
+            return;
+        }
+
         final float x = ev.getX(pointerIndex);
         final float y = ev.getY(pointerIndex);
         final int xDiff = (int) Math.abs(x - mLastMotionX);
@@ -1002,30 +1153,15 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
                 mTotalMotionX += Math.abs(mLastMotionX - x);
                 mLastMotionX = x;
                 mLastMotionXRemainder = 0;
-                mTouchX = getScrollX();
+                mTouchX = getOffsetX() + 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;
+        final int halfScreenSize = getMinScaledMeasuredWidth() / 2;
 
         int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing;
         int delta = screenCenter - (getChildOffset(page) -
@@ -1045,7 +1181,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
     }
 
     protected void acceleratedOverScroll(float amount) {
-        int screenSize = getMeasuredWidth();
+        int screenSize = getMinScaledMeasuredWidth();
 
         // We want to reach the max over scroll effect when the user has
         // over scrolled half the size of the screen
@@ -1070,7 +1206,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
     }
 
     protected void dampedOverScroll(float amount) {
-        int screenSize = getMeasuredWidth();
+        int screenSize = getMinScaledMeasuredWidth();
 
         float f = (amount / screenSize);
 
@@ -1130,9 +1266,22 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
 
             // Remember where the motion event started
             mDownMotionX = mLastMotionX = ev.getX();
+            mDownMotionY = mLastMotionY = ev.getY();
+            mDownScrollX = getScrollX();
+            float[] p = mapPointFromSelfToParent(mLastMotionX, mLastMotionY);
+            mParentDownMotionX = p[0];
+            mParentDownMotionY = p[1];
             mLastMotionXRemainder = 0;
             mTotalMotionX = 0;
             mActivePointerId = ev.getPointerId(0);
+
+            // Determine if the down event is within the threshold to be an edge swipe
+            int leftEdgeBoundary = getOffsetX() + mEdgeSwipeRegionSize;
+            int rightEdgeBoundary = getMeasuredWidth() - getOffsetX() - mEdgeSwipeRegionSize;
+            if ((mDownMotionX <= leftEdgeBoundary || mDownMotionX >= rightEdgeBoundary)) {
+                mDownEventOnEdge = true;
+            }
+
             if (mTouchState == TOUCH_STATE_SCROLLING) {
                 pageBeginMoving();
             }
@@ -1164,6 +1313,91 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
                 } else {
                     awakenScrollBars();
                 }
+            } else if (mTouchState == TOUCH_STATE_REORDERING) {
+                // Update the last motion position
+                mLastMotionX = ev.getX();
+                mLastMotionY = ev.getY();
+
+                // Update the parent down so that our zoom animations take this new movement into
+                // account
+                float[] pt = mapPointFromSelfToParent(mLastMotionX, mLastMotionY);
+                mParentDownMotionX = pt[0];
+                mParentDownMotionY = pt[1];
+                updateDragViewTranslationDuringDrag();
+
+                // Find the closest page to the touch point
+                final int dragViewIndex = indexOfChild(mDragView);
+                int bufferSize = (int) (REORDERING_SIDE_PAGE_BUFFER_PERCENTAGE *
+                    getMinScaledMeasuredWidth());
+                int leftBufferEdge = (int) mapPointFromSelfToParent(0, 0)[0] + bufferSize;
+                int rightBufferEdge = (int) mapPointFromSelfToParent(getMeasuredWidth(), 0)[0]
+                        - bufferSize;
+                float parentX = mParentDownMotionX;
+                int pageIndexToSnapTo = -1;
+                if (parentX < leftBufferEdge && dragViewIndex > 0) {
+                    pageIndexToSnapTo = dragViewIndex - 1;
+                } else if (parentX > rightBufferEdge && dragViewIndex < getChildCount() - 1) {
+                    pageIndexToSnapTo = dragViewIndex + 1;
+                }
+
+                final int pageUnderPointIndex = pageIndexToSnapTo;
+                if (pageUnderPointIndex > -1) {
+                    if (pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
+                        mSidePageHoverIndex = pageUnderPointIndex;
+                        mSidePageHoverRunnable = new Runnable() {
+                            @Override
+                            public void run() {
+                                // Update the down scroll position to account for the fact that the
+                                // current page is moved
+                                mDownScrollX = getChildOffset(pageUnderPointIndex)
+                                        - getRelativeChildOffset(pageUnderPointIndex);
+
+                                // Setup the scroll to the correct page before we swap the views
+                                snapToPage(pageUnderPointIndex);
+
+                                // For each of the pages between the paged view and the drag view,
+                                // animate them from the previous position to the new position in
+                                // the layout (as a result of the drag view moving in the layout)
+                                int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1;
+                                int lowerIndex = (dragViewIndex < pageUnderPointIndex) ?
+                                        dragViewIndex + 1 : pageUnderPointIndex;
+                                int upperIndex = (dragViewIndex > pageUnderPointIndex) ?
+                                        dragViewIndex - 1 : pageUnderPointIndex;
+                                for (int i = lowerIndex; i <= upperIndex; ++i) {
+                                    View v = getChildAt(i);
+                                    // dragViewIndex < pageUnderPointIndex, so after we remove the
+                                    // drag view all subsequent views to pageUnderPointIndex will
+                                    // shift down.
+                                    int oldX = getOffsetX() + getChildOffset(i);
+                                    int newX = getOffsetX() + getChildOffset(i + shiftDelta);
+
+                                    // Animate the view translation from its old position to its new
+                                    // position
+                                    AnimatorSet anim = (AnimatorSet) v.getTag();
+                                    if (anim != null) {
+                                        anim.cancel();
+                                    }
+
+                                    v.setTranslationX(oldX - newX);
+                                    anim = new AnimatorSet();
+                                    anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
+                                    anim.playTogether(
+                                            ObjectAnimator.ofFloat(v, "translationX", 0f));
+                                    anim.start();
+                                    v.setTag(anim);
+                                }
+
+                                removeView(mDragView);
+                                addView(mDragView, pageUnderPointIndex);
+                                mSidePageHoverIndex = -1;
+                            }
+                        };
+                        postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
+                    }
+                } else {
+                    removeCallbacks(mSidePageHoverRunnable);
+                    mSidePageHoverIndex = -1;
+                }
             } else {
                 determineScrollingStart(ev);
             }
@@ -1232,21 +1466,29 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
                 } else {
                     snapToDestination();
                 }
+            } else if (mTouchState == TOUCH_STATE_REORDERING) {
+                if (!DISABLE_FLING_TO_DELETE) {
+                    // Check the velocity and see if we are flinging-to-delete
+                    PointF flingToDeleteVector = isFlingingToDelete();
+                    if (flingToDeleteVector != null) {
+                        onFlingToDelete(flingToDeleteVector);
+                    }
+                }
             } else {
                 onUnhandledTap(ev);
             }
-            mTouchState = TOUCH_STATE_REST;
-            mActivePointerId = INVALID_POINTER;
-            releaseVelocityTracker();
+
+            // Remove the callback to wait for the side page hover timeout
+            removeCallbacks(mSidePageHoverRunnable);
+            // End any intermediate reordering states
+            resetTouchState();
             break;
 
         case MotionEvent.ACTION_CANCEL:
             if (mTouchState == TOUCH_STATE_SCROLLING) {
                 snapToDestination();
             }
-            mTouchState = TOUCH_STATE_REST;
-            mActivePointerId = INVALID_POINTER;
-            releaseVelocityTracker();
+            resetTouchState();
             break;
 
         case MotionEvent.ACTION_POINTER_UP:
@@ -1257,6 +1499,16 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         return true;
     }
 
+    private void resetTouchState() {
+        releaseVelocityTracker();
+        endReordering();
+        mTouchState = TOUCH_STATE_REST;
+        mActivePointerId = INVALID_POINTER;
+        mDownEventOnEdge = false;
+    }
+
+    protected void onUnhandledTap(MotionEvent ev) {}
+
     @Override
     public boolean onGenericMotionEvent(MotionEvent event) {
         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
@@ -1319,8 +1571,6 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         }
     }
 
-    protected void onUnhandledTap(MotionEvent ev) {}
-
     @Override
     public void requestChildFocus(View child, View focused) {
         super.requestChildFocus(child, focused);
@@ -1352,16 +1602,28 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         return (minWidth > measuredWidth) ? minWidth : measuredWidth;
     }
 
+    int getPageNearestToPoint(float x) {
+        int index = 0;
+        for (int i = 0; i < getChildCount(); ++i) {
+            if (x < getChildAt(i).getRight() - getScrollX()) {
+                return index;
+            } else {
+                index++;
+            }
+        }
+        return Math.min(index, getChildCount() - 1);
+    }
+
     int getPageNearestToCenterOfScreen() {
         int minDistanceFromScreenCenter = Integer.MAX_VALUE;
         int minDistanceFromScreenCenterIndex = -1;
-        int screenCenter = getScrollX() + (getMeasuredWidth() / 2);
+        int screenCenter = getOffsetX() + getScrollX() + (getMinScaledMeasuredWidth() / 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 childCenter = getOffsetX() + getChildOffset(i) + halfChildWidth;
             int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
             if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
                 minDistanceFromScreenCenter = distanceFromScreenCenter;
@@ -1397,11 +1659,11 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
 
     protected void snapToPageWithVelocity(int whichPage, int velocity) {
         whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
-        int halfScreenSize = getMeasuredWidth() / 2;
+        int halfScreenSize = getMinScaledMeasuredWidth() / 2;
 
         if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
         if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): "
-                + getMeasuredWidth() + ", " + getChildWidth(whichPage));
+                + getMinScaledMeasuredWidth() + ", " + getChildWidth(whichPage));
         final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
         int delta = newX - mUnboundedScrollX;
         int duration = 0;
@@ -1440,7 +1702,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         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() + ", "
+        if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getMinScaledMeasuredWidth() + ", "
                 + getChildWidth(whichPage));
         int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
         final int sX = mUnboundedScrollX;
@@ -1500,21 +1762,6 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         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;
 
@@ -1653,7 +1900,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         if (!isScrollingIndicatorEnabled()) return;
         if (mScrollIndicator == null) return;
         int numPages = getChildCount();
-        int pageWidth = getMeasuredWidth();
+        int pageWidth = getMinScaledMeasuredWidth();
         int lastChildIndex = Math.max(0, getChildCount() - 1);
         int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex);
         int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight;
@@ -1675,6 +1922,234 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
         mScrollIndicator.setTranslationX(indicatorPos);
     }
 
+    // Animate the drag view back to the original position
+    void animateChildrenToOriginalPosition() {
+        if (mDragView != null) {
+            AnimatorSet anim = new AnimatorSet();
+            anim.setDuration(REORDERING_DROP_REPOSITION_DURATION);
+            anim.playTogether(
+                    ObjectAnimator.ofFloat(mDragView, "translationX", 0f),
+                    ObjectAnimator.ofFloat(mDragView, "translationY", 0f));
+            anim.start();
+        }
+    }
+
+    // "Zooms out" the PagedView to reveal more side pages
+    boolean zoomOut() {
+        if (!(getScaleX() < 1f || getScaleY() < 1f)) {
+            if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
+                mZoomInOutAnim.cancel();
+            }
+
+            mZoomInOutAnim = new AnimatorSet();
+            mZoomInOutAnim.setDuration(REORDERING_ZOOM_IN_OUT_DURATION);
+            mZoomInOutAnim.playTogether(
+                    ObjectAnimator.ofFloat(this, "scaleX", mMinScale),
+                    ObjectAnimator.ofFloat(this, "scaleY", mMinScale));
+            mZoomInOutAnim.start();
+            return true;
+        }
+        return false;
+    }
+
+    protected void onStartReordering() {
+    }
+
+    protected void onEndReordering() {
+    }
+
+    public void startReordering() {
+        if (zoomOut()) {
+            onStartReordering();
+
+            // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
+            mTouchState = TOUCH_STATE_REORDERING;
+            // Find the drag view under the pointer
+            mDragView = getChildAt(getPageNearestToCenterOfScreen());
+
+            // We must invalidate to trigger a redraw to update the layers such that the drag view
+            // is always drawn on top
+            invalidate();
+        }
+    }
+
+    void endReordering() {
+        Runnable onCompleteRunnable = new Runnable() {
+            @Override
+            public void run() {
+                onEndReordering();
+            }
+        };
+        if (zoomIn(onCompleteRunnable)) {
+            // Snap to the current page
+            snapToDestination();
+        }
+        // If we haven't flung-to-delete the current child, then we just animate the drag view
+        // back into position
+        if (!mIsFlingingToDelete) {
+            animateChildrenToOriginalPosition();
+        }
+    }
+
+    public boolean isReordering() {
+        return mTouchState == TOUCH_STATE_REORDERING;
+    }
+
+    // "Zooms in" the PagedView to highlight the current page
+    boolean zoomIn(final Runnable onCompleteRunnable) {
+        if (getScaleX() < 1f || getScaleY() < 1f) {
+            if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
+                mZoomInOutAnim.cancel();
+            }
+
+            mZoomInOutAnim = new AnimatorSet();
+            mZoomInOutAnim.setDuration(REORDERING_ZOOM_IN_OUT_DURATION);
+            mZoomInOutAnim.playTogether(
+                    ObjectAnimator.ofFloat(this, "scaleX", 1f),
+                    ObjectAnimator.ofFloat(this, "scaleY", 1f));
+            mZoomInOutAnim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    mDragView = null;
+                }
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mDragView = null;
+                    if (onCompleteRunnable != null) {
+                        onCompleteRunnable.run();
+                    }
+                }
+            });
+            mZoomInOutAnim.start();
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     * Flinging to delete - IN PROGRESS
+     */
+    private PointF isFlingingToDelete() {
+        ViewConfiguration config = ViewConfiguration.get(getContext());
+        mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
+
+        if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
+            // Do a quick dot product test to ensure that we are flinging upwards
+            PointF vel = new PointF(mVelocityTracker.getXVelocity(),
+                    mVelocityTracker.getYVelocity());
+            PointF upVec = new PointF(0f, -1f);
+            float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
+                    (vel.length() * upVec.length()));
+            if (theta <= Math.toRadians(FLING_TO_DELETE_MAX_FLING_DEGREES)) {
+                return vel;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Creates an animation from the current drag view along its current velocity vector.
+     * For this animation, the alpha runs for a fixed duration and we update the position
+     * progressively.
+     */
+    private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
+        private View mDragView;
+        private PointF mVelocity;
+        private Rect mFrom;
+        private long mPrevTime;
+        private float mFriction;
+
+        private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
+
+        public FlingAlongVectorAnimatorUpdateListener(View dragView, PointF vel, Rect from,
+                long startTime, float friction) {
+            mDragView = dragView;
+            mVelocity = vel;
+            mFrom = from;
+            mPrevTime = startTime;
+            mFriction = 1f - (mDragView.getResources().getDisplayMetrics().density * friction);
+        }
+
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            float t = ((Float) animation.getAnimatedValue()).floatValue();
+            long curTime = AnimationUtils.currentAnimationTimeMillis();
+
+            mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
+            mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
+
+            mDragView.setTranslationX(mFrom.left);
+            mDragView.setTranslationY(mFrom.top);
+            mDragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
+
+            mVelocity.x *= mFriction;
+            mVelocity.y *= mFriction;
+            mPrevTime = curTime;
+        }
+    };
+
+    public void onFlingToDelete(PointF vel) {
+        final ViewConfiguration config = ViewConfiguration.get(getContext());
+        final long startTime = AnimationUtils.currentAnimationTimeMillis();
+
+        // NOTE: Because it takes time for the first frame of animation to actually be
+        // called and we expect the animation to be a continuation of the fling, we have
+        // to account for the time that has elapsed since the fling finished.  And since
+        // we don't have a startDelay, we will always get call to update when we call
+        // start() (which we want to ignore).
+        final TimeInterpolator tInterpolator = new TimeInterpolator() {
+            private int mCount = -1;
+            private long mStartTime;
+            private float mOffset;
+            /* Anonymous inner class ctor */ {
+                mStartTime = startTime;
+            }
+
+            @Override
+            public float getInterpolation(float t) {
+                if (mCount < 0) {
+                    mCount++;
+                } else if (mCount == 0) {
+                    mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
+                            mStartTime) / FLING_TO_DELETE_FADE_OUT_DURATION);
+                    mCount++;
+                }
+                return Math.min(1f, mOffset + t);
+            }
+        };
+
+        final Rect from = new Rect();
+        final View dragView = mDragView;
+        from.left = (int) dragView.getTranslationX();
+        from.top = (int) dragView.getTranslationY();
+        AnimatorUpdateListener updateCb = new FlingAlongVectorAnimatorUpdateListener(dragView, vel,
+                from, startTime, FLING_TO_DELETE_FRICTION);
+
+        final Runnable onAnimationEndRunnable = new Runnable() {
+            @Override
+            public void run() {
+                // TEMP - in progress
+                mIsFlingingToDelete = false;
+                removeView(dragView);
+                requestLayout();
+            }
+        };
+
+        // Create and start the animation
+        ValueAnimator mDropAnim = new ValueAnimator();
+        mDropAnim.setInterpolator(tInterpolator);
+        mDropAnim.setDuration(FLING_TO_DELETE_FADE_OUT_DURATION);
+        mDropAnim.setFloatValues(0f, 1f);
+        mDropAnim.addUpdateListener(updateCb);
+        mDropAnim.addListener(new AnimatorListenerAdapter() {
+            public void onAnimationEnd(Animator animation) {
+                onAnimationEndRunnable.run();
+            }
+        });
+        mDropAnim.start();
+        mIsFlingingToDelete = true;
+    }
+
     /* Accessibility */
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/SecureCamera.java b/policy/src/com/android/internal/policy/impl/keyguard/SecureCamera.java
new file mode 100644 (file)
index 0000000..505917e
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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.os.Bundle;
+import android.widget.FrameLayout;
+
+public class SecureCamera extends Activity  {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(new FrameLayout(this));
+    }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/SlidingChallengeLayout.java b/policy/src/com/android/internal/policy/impl/keyguard/SlidingChallengeLayout.java
new file mode 100644 (file)
index 0000000..b264235
--- /dev/null
@@ -0,0 +1,710 @@
+/*
+ * 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.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.widget.Scroller;
+
+import com.android.internal.R;
+
+/**
+ * This layout handles interaction with the sliding security challenge views
+ * that overlay/resize other keyguard contents.
+ */
+public class SlidingChallengeLayout extends ViewGroup {
+    private static final String TAG = "SlidingChallengeLayout";
+
+    // Drawn to show the drag handle in closed state; crossfades to the challenge view
+    // when challenge is fully visible
+    private Drawable mHandleDrawable;
+
+    // Initialized during measurement from child layoutparams
+    private View mChallengeView;
+
+    // Range: 0 (fully hidden) to 1 (fully visible)
+    private float mChallengeOffset = 1.f;
+    private boolean mChallengeShowing = true;
+
+    private final Scroller mScroller;
+    private int mScrollState;
+    private OnChallengeScrolledListener mListener;
+
+    public static final int SCROLL_STATE_IDLE = 0;
+    public static final int SCROLL_STATE_DRAGGING = 1;
+    public static final int SCROLL_STATE_SETTLING = 2;
+
+    private static final int MAX_SETTLE_DURATION = 600; // ms
+
+    // ID of the pointer in charge of a current drag
+    private int mActivePointerId = INVALID_POINTER;
+    private static final int INVALID_POINTER = -1;
+
+    // True if the user is currently dragging the slider
+    private boolean mDragging;
+    // True if the user may not drag until a new gesture begins
+    private boolean mBlockDrag;
+
+    private VelocityTracker mVelocityTracker;
+    private int mMinVelocity;
+    private int mMaxVelocity;
+    private float mLastTouchY;
+    private int mDragHandleSize;
+
+    private static final int DRAG_HANDLE_DEFAULT_SIZE = 32; // dp
+
+    // True if at least one layout pass has happened since the view was attached.
+    private boolean mHasLayout;
+
+    private static final Interpolator sMotionInterpolator = new Interpolator() {
+        public float getInterpolation(float t) {
+            t -= 1.0f;
+            return t * t * t * t * t + 1.0f;
+        }
+    };
+
+    private static final Interpolator sHandleFadeInterpolator = new Interpolator() {
+        public float getInterpolation(float t) {
+            return t * t;
+        }
+    };
+
+    private final Runnable mEndScrollRunnable = new Runnable () {
+        public void run() {
+            completeChallengeScroll();
+        }
+    };
+
+    /**
+     * Listener interface that reports changes in scroll state of the challenge area.
+     */
+    public interface OnChallengeScrolledListener {
+        /**
+         * The scroll state itself changed.
+         *
+         * <p>scrollState will be one of the following:</p>
+         *
+         * <ul>
+         * <li><code>SCROLL_STATE_IDLE</code> - The challenge area is stationary.</li>
+         * <li><code>SCROLL_STATE_DRAGGING</code> - The user is actively dragging
+         * the challenge area.</li>
+         * <li><code>SCROLL_STATE_SETTLING</code> - The challenge area is animating
+         * into place.</li>
+         * </ul>
+         *
+         * <p>Do not perform expensive operations (e.g. layout)
+         * while the scroll state is not <code>SCROLL_STATE_IDLE</code>.</p>
+         *
+         * @param scrollState The new scroll state of the challenge area.
+         */
+        public void onScrollStateChanged(int scrollState);
+
+        /**
+         * The precise position of the challenge area has changed.
+         *
+         * <p>NOTE: It is NOT safe to modify layout or call any View methods that may
+         * result in a requestLayout anywhere in your view hierarchy as a result of this call.
+         * It may be called during drawing.</p>
+         *
+         * @param scrollPosition New relative position of the challenge area.
+         *                       1.f = fully visible/ready to be interacted with.
+         *                       0.f = fully invisible/inaccessible to the user.
+         * @param challengeTop Position of the top edge of the challenge view in px in the
+         *                     SlidingChallengeLayout's coordinate system.
+         */
+        public void onScrollPositionChanged(float scrollPosition, int challengeTop);
+    }
+
+    public SlidingChallengeLayout(Context context) {
+        this(context, null);
+    }
+
+    public SlidingChallengeLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SlidingChallengeLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        final TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.SlidingChallengeLayout, defStyle, 0);
+        setDragHandleDrawable(a.getDrawable(R.styleable.SlidingChallengeLayout_dragHandle));
+
+        a.recycle();
+
+        mScroller = new Scroller(context, sMotionInterpolator);
+
+        final ViewConfiguration vc = ViewConfiguration.get(context);
+        mMinVelocity = vc.getScaledMinimumFlingVelocity();
+        mMaxVelocity = vc.getScaledMaximumFlingVelocity();
+
+        setWillNotDraw(false);
+    }
+
+    public void setDragHandleDrawable(Drawable d) {
+        if (d != null) {
+            mDragHandleSize = d.getIntrinsicHeight();
+        }
+        if (mDragHandleSize == 0 || d == null) {
+            final float density = getResources().getDisplayMetrics().density;
+            mDragHandleSize = (int) (DRAG_HANDLE_DEFAULT_SIZE * density + 0.5f);
+        }
+        mHandleDrawable = d;
+    }
+
+    private void sendInitialListenerUpdates() {
+        if (mListener != null) {
+            int challengeTop = mChallengeView != null ? mChallengeView.getTop() : 0;
+            mListener.onScrollPositionChanged(mChallengeOffset, challengeTop);
+            mListener.onScrollStateChanged(mScrollState);
+        }
+    }
+
+    public void setOnChallengeScrolledListener(OnChallengeScrolledListener listener) {
+        mListener = listener;
+        if (mHasLayout) {
+            sendInitialListenerUpdates();
+        }
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        mHasLayout = false;
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        removeCallbacks(mEndScrollRunnable);
+        mHasLayout = false;
+    }
+
+    // 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);
+    }
+
+    void setScrollState(int state) {
+        if (mScrollState != state) {
+            mScrollState = state;
+
+            if (mListener != null) {
+                mListener.onScrollStateChanged(state);
+            }
+        }
+    }
+
+    void completeChallengeScroll() {
+        setChallengeShowing(mChallengeOffset != 0);
+        setScrollState(SCROLL_STATE_IDLE);
+    }
+
+    /**
+     * Animate the bottom edge of the challenge view to the given position.
+     *
+     * @param y desired final position for the bottom edge of the challenge view in px
+     * @param velocity velocity in
+     */
+    void animateChallengeTo(int y, int velocity) {
+        if (mChallengeView == null) {
+            // Nothing to do.
+            return;
+        }
+        int sy = mChallengeView.getBottom();
+        int dy = y - sy;
+        if (dy == 0) {
+            completeChallengeScroll();
+            return;
+        }
+
+        setScrollState(SCROLL_STATE_SETTLING);
+
+        final int childHeight = mChallengeView.getHeight();
+        final int halfHeight = childHeight / 2;
+        final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dy) / childHeight);
+        final float distance = halfHeight + halfHeight *
+                distanceInfluenceForSnapDuration(distanceRatio);
+
+        int duration = 0;
+        velocity = Math.abs(velocity);
+        if (velocity > 0) {
+            duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
+        } else {
+            final float childDelta = (float) Math.abs(dy) / childHeight;
+            duration = (int) ((childDelta + 1) * 100);
+        }
+        duration = Math.min(duration, MAX_SETTLE_DURATION);
+
+        mScroller.startScroll(0, sy, 0, dy, duration);
+        postInvalidateOnAnimation();
+    }
+
+    private void setChallengeShowing(boolean showChallenge) {
+        if (mChallengeShowing != showChallenge) {
+            mChallengeShowing = showChallenge;
+            if (mChallengeView != null) {
+                mChallengeView.setVisibility(showChallenge ? VISIBLE : INVISIBLE);
+            }
+        }
+    }
+
+    /**
+     * @return true if the challenge is at all visible.
+     */
+    public boolean isChallengeShowing() {
+        return mChallengeShowing;
+    }
+
+    @Override
+    public void requestDisallowInterceptTouchEvent(boolean allowIntercept) {
+        // We'll intercept whoever we feel like! ...as long as it isn't a challenge view.
+        // If there are one or more pointers in the challenge view before we take over
+        // touch events, onInterceptTouchEvent will set mBlockDrag.
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        final int action = ev.getActionMasked();
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mLastTouchY = ev.getY();
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                resetTouch();
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                final int count = ev.getPointerCount();
+                for (int i = 0; i < count; i++) {
+                    final float x = ev.getX(i);
+                    final float y = ev.getY(i);
+
+                    if ((isInDragHandle(x, y) || crossedDragHandle(x, y, mLastTouchY) ||
+                            (isInChallengeView(x, y) && mScrollState == SCROLL_STATE_SETTLING)) &&
+                            mActivePointerId == INVALID_POINTER) {
+                        mActivePointerId = ev.getPointerId(i);
+                        mLastTouchY = y;
+                        mDragging = true;
+                    } else if (isInChallengeView(x, y)) {
+                        mBlockDrag = true;
+                    }
+                }
+                break;
+        }
+
+        if (mBlockDrag) {
+            mActivePointerId = INVALID_POINTER;
+            mDragging = false;
+        }
+
+        return mDragging;
+    }
+
+    private void resetTouch() {
+        mVelocityTracker.recycle();
+        mVelocityTracker = null;
+        mActivePointerId = INVALID_POINTER;
+        mDragging = mBlockDrag = false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        final int action = ev.getActionMasked();
+        switch (action) {
+            case MotionEvent.ACTION_CANCEL:
+                if (mDragging) {
+                    showChallenge(0);
+                }
+                resetTouch();
+                break;
+
+            case MotionEvent.ACTION_POINTER_UP:
+                if (mActivePointerId != ev.getPointerId(ev.getActionIndex())) {
+                    break;
+                }
+            case MotionEvent.ACTION_UP:
+                if (mDragging) {
+                    mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
+                    showChallenge((int) mVelocityTracker.getYVelocity(mActivePointerId));
+                }
+                resetTouch();
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                if (!mDragging && !mBlockDrag) {
+                    final int count = ev.getPointerCount();
+                    for (int i = 0; i < count; i++) {
+                        final float x = ev.getX(i);
+                        final float y = ev.getY(i);
+
+                        if ((isInDragHandle(x, y) || crossedDragHandle(x, y, mLastTouchY) ||
+                                (isInChallengeView(x, y) && mScrollState == SCROLL_STATE_SETTLING))
+                                && mActivePointerId == INVALID_POINTER) {
+                            mActivePointerId = ev.getPointerId(i);
+                            mDragging = true;
+                            break;
+                        }
+                    }
+                }
+                // Not an else; this can be set above.
+                if (mDragging) {
+                    // No-op if already in this state, but set it here in case we arrived
+                    // at this point from either intercept or the above.
+                    setScrollState(SCROLL_STATE_DRAGGING);
+
+                    final int index = ev.findPointerIndex(mActivePointerId);
+                    if (index < 0) {
+                        // Oops, bogus state. We lost some touch events somewhere.
+                        // Just drop it with no velocity and let things settle.
+                        resetTouch();
+                        showChallenge(0);
+                        return true;
+                    }
+                    final float y = Math.max(ev.getY(index), getChallengeOpenedTop());
+                    final float delta = y - mLastTouchY;
+                    final int idelta = (int) delta;
+                    // Don't lose the rounded component
+                    mLastTouchY = y + delta - idelta;
+
+                    moveChallengeBy(idelta);
+                }
+                break;
+        }
+        return true;
+    }
+
+    private boolean isInChallengeView(float x, float y) {
+        if (mChallengeView == null) return false;
+
+        return x >= mChallengeView.getLeft() && y >= mChallengeView.getTop() &&
+                x < mChallengeView.getRight() && y < mChallengeView.getBottom();
+    }
+
+    private boolean isInDragHandle(float x, float y) {
+        if (mChallengeView == null) return false;
+
+        return x >= 0 && y >= mChallengeView.getTop() && x < getWidth() &&
+                y < mChallengeView.getTop() + mDragHandleSize;
+    }
+
+    private boolean crossedDragHandle(float x, float y, float lastY) {
+        final int challengeTop = mChallengeView.getTop();
+        return x >= 0 && x < getWidth() && lastY < challengeTop &&
+                y > challengeTop + mDragHandleSize;
+    }
+
+    @Override
+    protected void onMeasure(int widthSpec, int heightSpec) {
+        if (MeasureSpec.getMode(widthSpec) != MeasureSpec.EXACTLY ||
+                MeasureSpec.getMode(heightSpec) != MeasureSpec.EXACTLY) {
+            throw new IllegalArgumentException(
+                    "SlidingChallengeLayout must be measured with an exact size");
+        }
+
+        final int width = MeasureSpec.getSize(widthSpec);
+        final int height = MeasureSpec.getSize(heightSpec);
+        setMeasuredDimension(width, height);
+
+        // Find one and only one challenge view.
+        final View oldChallengeView = mChallengeView;
+        mChallengeView = null;
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            if (lp.isChallenge) {
+                if (mChallengeView != null) {
+                    throw new IllegalStateException(
+                            "There may only be one child with layout_isChallenge=\"true\"");
+                }
+                mChallengeView = child;
+                if (mChallengeView != oldChallengeView) {
+                    mChallengeView.setVisibility(mChallengeShowing ? VISIBLE : INVISIBLE);
+                }
+            }
+
+            if (child.getVisibility() == GONE) continue;
+
+            measureChildWithMargins(child, widthSpec, 0, heightSpec, 0);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int paddingLeft = getPaddingLeft();
+        final int paddingTop = getPaddingTop();
+        final int paddingRight = getPaddingRight();
+        final int paddingBottom = getPaddingBottom();
+        final int width = r - l;
+        final int height = b - t;
+
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+
+            if (child.getVisibility() == GONE) continue;
+
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            if (lp.isChallenge) {
+                // Challenge views pin to the bottom, offset by a portion of their height,
+                // and center horizontally.
+                final int center = (paddingLeft + width - paddingRight) / 2;
+                final int childWidth = child.getMeasuredWidth();
+                final int childHeight = child.getMeasuredHeight();
+                final int left = center - childWidth / 2;
+                final int layoutBottom = height - paddingBottom - lp.bottomMargin;
+                // We use the top of the challenge view to position the handle, so
+                // we never want less than the handle size showing at the bottom.
+                final int bottom = layoutBottom + (int) ((childHeight - mDragHandleSize)
+                        * (1 - mChallengeOffset));
+                float offset = 1.f - (bottom - layoutBottom) / childHeight;
+                child.setAlpha(offset);
+                child.layout(left, bottom - childHeight, left + childWidth, bottom);
+            } else {
+                // Non-challenge views lay out from the upper left, layered.
+                child.layout(paddingLeft + lp.leftMargin,
+                        paddingTop + lp.topMargin,
+                        paddingLeft + child.getMeasuredWidth(),
+                        paddingTop + child.getMeasuredHeight());
+            }
+        }
+
+        if (!mHasLayout) {
+            // We want to trigger the initial listener updates outside of layout pass,
+            // in case the listeners trigger requestLayout().
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    sendInitialListenerUpdates();
+                }
+            });
+        }
+        mHasLayout = true;
+    }
+
+    public void computeScroll() {
+        super.computeScroll();
+
+        if (!mScroller.isFinished()) {
+            if (mChallengeView == null) {
+                // Can't scroll if the view is missing.
+                Log.e(TAG, "Challenge view missing in computeScroll");
+                mScroller.abortAnimation();
+                return;
+            }
+
+            mScroller.computeScrollOffset();
+            moveChallengeTo(mScroller.getCurrY());
+
+            if (mScroller.isFinished()) {
+                post(mEndScrollRunnable);
+            }
+        }
+    }
+
+    @Override
+    public void draw(Canvas c) {
+        super.draw(c);
+        if (mChallengeOffset < 1.f && mChallengeView != null && mHandleDrawable != null) {
+            final int top = mChallengeView.getTop();
+            mHandleDrawable.setBounds(0, top, getWidth(), top + mDragHandleSize);
+            final float alpha = sHandleFadeInterpolator.getInterpolation(1 - mChallengeOffset);
+            mHandleDrawable.setAlpha((int) (alpha * 0xFF));
+            mHandleDrawable.draw(c);
+        }
+    }
+
+    /**
+     * Move the bottom edge of mChallengeView to a new position and notify the listener
+     * if it represents a change in position. Changes made through this method will
+     * be stable across layout passes. If this method is called before first layout of
+     * this SlidingChallengeLayout it will have no effect.
+     *
+     * @param bottom New bottom edge in px in this SlidingChallengeLayout's coordinate system.
+     * @return true if the challenge view was moved
+     */
+    private boolean moveChallengeTo(int bottom) {
+        if (mChallengeView == null || !mHasLayout) {
+            return false;
+        }
+
+        final int bottomMargin = ((LayoutParams) mChallengeView.getLayoutParams()).bottomMargin;
+        final int layoutBottom = getHeight() - getPaddingBottom() - bottomMargin;
+        final int challengeHeight = mChallengeView.getHeight();
+
+        bottom = Math.max(layoutBottom,
+                Math.min(bottom, layoutBottom + challengeHeight - mDragHandleSize));
+
+        float offset = 1.f - (float) (bottom - layoutBottom) / (challengeHeight - mDragHandleSize);
+        mChallengeOffset = offset;
+        if (offset > 0 && !mChallengeShowing) {
+            setChallengeShowing(true);
+        }
+
+        mChallengeView.layout(mChallengeView.getLeft(),
+                bottom - mChallengeView.getHeight(), mChallengeView.getRight(), bottom);
+
+        mChallengeView.setAlpha(offset);
+        if (mListener != null) {
+            mListener.onScrollPositionChanged(offset, mChallengeView.getTop());
+        }
+        postInvalidateOnAnimation();
+        return true;
+    }
+
+    private int getChallengeBottom() {
+        if (mChallengeView == null) return 0;
+
+        return mChallengeView.getBottom();
+    }
+
+    private int getChallengeOpenedTop() {
+        final int paddedBottom = getHeight() - getPaddingBottom();
+        if (mChallengeView == null) return paddedBottom;
+
+        final int bottomMargin = ((LayoutParams) mChallengeView.getLayoutParams()).bottomMargin;
+        final int layoutBottom = paddedBottom - bottomMargin;
+
+        return layoutBottom - mChallengeView.getHeight();
+    }
+
+    private void moveChallengeBy(int delta) {
+        moveChallengeTo(getChallengeBottom() + delta);
+    }
+
+    /**
+     * Show or hide the challenge view, animating it if necessary.
+     * @param show true to show, false to hide
+     */
+    public void showChallenge(boolean show) {
+        showChallenge(show, 0);
+    }
+
+    private void showChallenge(int velocity) {
+        boolean show = false;
+        if (Math.abs(velocity) > mMinVelocity) {
+            show = velocity < 0;
+        } else {
+            show = mChallengeOffset >= 0.5f;
+        }
+        showChallenge(show, velocity);
+    }
+
+    private void showChallenge(boolean show, int velocity) {
+        if (mChallengeView == null) {
+            setChallengeShowing(false);
+            return;
+        }
+
+        if (mHasLayout) {
+            final int bottomMargin = ((LayoutParams) mChallengeView.getLayoutParams()).bottomMargin;
+            final int layoutBottom = getHeight() - getPaddingBottom() - bottomMargin;
+            animateChallengeTo(show ? layoutBottom :
+                layoutBottom + mChallengeView.getHeight() - mDragHandleSize, velocity);
+        }
+    }
+
+    @Override
+    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LayoutParams ? new LayoutParams((LayoutParams) p) :
+                p instanceof MarginLayoutParams ? new LayoutParams((MarginLayoutParams) p) :
+                new LayoutParams(p);
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams();
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LayoutParams;
+    }
+
+    public static class LayoutParams extends MarginLayoutParams {
+        public boolean isChallenge = false;
+
+        public LayoutParams() {
+            this(MATCH_PARENT, WRAP_CONTENT);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public LayoutParams(android.view.ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(MarginLayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(LayoutParams source) {
+            super(source);
+
+            isChallenge = source.isChallenge;
+        }
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+
+            final TypedArray a = c.obtainStyledAttributes(attrs,
+                    R.styleable.SlidingChallengeLayout_Layout);
+            isChallenge = a.getBoolean(R.styleable.SlidingChallengeLayout_Layout_layout_isChallenge,
+                    false);
+            a.recycle();
+        }
+    }
+}