summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/provider/Settings.java11
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java104
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/GlowPadView.java5
-rw-r--r--core/res/res/anim/keyguard_security_fade_in.xml4
-rw-r--r--core/res/res/anim/keyguard_security_fade_out.xml6
-rw-r--r--core/res/res/drawable-hdpi/add_widget.pngbin0 -> 19960 bytes
-rw-r--r--core/res/res/drawable-hdpi/security_frame.9.pngbin0 -> 6384 bytes
-rw-r--r--core/res/res/drawable-hdpi/security_handle.pngbin0 -> 516 bytes
-rw-r--r--core/res/res/drawable-hdpi/sym_keyboard_return_holo.pngbin0 -> 1104 bytes
-rw-r--r--core/res/res/drawable-mdpi/add_widget.pngbin0 -> 19960 bytes
-rw-r--r--core/res/res/drawable-mdpi/security_frame.9.pngbin0 -> 6384 bytes
-rw-r--r--core/res/res/drawable-mdpi/security_handle.pngbin0 -> 516 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_return_holo.pngbin0 -> 823 bytes
-rw-r--r--core/res/res/drawable-sw600dp-hdpi/sym_keyboard_return_holo.pngbin0 -> 1104 bytes
-rw-r--r--core/res/res/drawable-sw600dp-mdpi/sym_keyboard_return_holo.pngbin0 -> 823 bytes
-rw-r--r--core/res/res/drawable-sw600dp-xhdpi/sym_keyboard_return_holo.pngbin0 -> 1501 bytes
-rw-r--r--core/res/res/drawable-xhdpi/add_widget.pngbin0 -> 19960 bytes
-rw-r--r--core/res/res/drawable-xhdpi/security_frame.9.pngbin0 -> 6384 bytes
-rw-r--r--core/res/res/drawable-xhdpi/security_handle.pngbin0 -> 516 bytes
-rw-r--r--core/res/res/drawable-xhdpi/sym_keyboard_return_holo.pngbin0 -> 1501 bytes
-rw-r--r--core/res/res/layout-land/keyguard_host_view.xml57
-rw-r--r--core/res/res/layout-port/keyguard_host_view.xml55
-rw-r--r--core/res/res/layout-sw600dp-port/keyguard_host_view.xml58
-rw-r--r--core/res/res/layout-sw600dp/keyguard_glow_pad_container.xml6
-rw-r--r--core/res/res/layout/keyguard_add_widget.xml33
-rw-r--r--core/res/res/layout/keyguard_emergency_carrier_area_and_recovery.xml2
-rw-r--r--core/res/res/layout/keyguard_face_unlock_view.xml4
-rw-r--r--core/res/res/layout/keyguard_multi_user_avatar.xml43
-rw-r--r--core/res/res/layout/keyguard_multi_user_selector.xml21
-rw-r--r--core/res/res/layout/keyguard_multi_user_selector_widget.xml3
-rw-r--r--core/res/res/layout/keyguard_password_view.xml26
-rw-r--r--core/res/res/layout/keyguard_pin_view.xml206
-rw-r--r--core/res/res/layout/keyguard_status_view.xml2
-rw-r--r--core/res/res/layout/keyguard_transport_control_view.xml16
-rw-r--r--core/res/res/layout/keyguard_widget_pager.xml32
-rw-r--r--core/res/res/layout/keyguard_widget_region.xml71
-rw-r--r--core/res/res/values-sw600dp/bools.xml2
-rw-r--r--core/res/res/values-sw600dp/dimens.xml6
-rw-r--r--core/res/res/values-sw720dp/dimens.xml12
-rw-r--r--core/res/res/values/arrays.xml13
-rwxr-xr-xcore/res/res/values/attrs.xml57
-rw-r--r--core/res/res/values/bools.xml1
-rw-r--r--core/res/res/values/colors.xml5
-rw-r--r--core/res/res/values/dimens.xml22
-rw-r--r--core/res/res/values/styles.xml23
-rw-r--r--core/res/res/values/symbols.xml32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java7
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/CameraWidgetFrame.java200
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/ChallengeLayout.java92
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/CheckLongPressHelper.java80
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardAbsKeyInputView.java256
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java4
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardActivityLauncher.java175
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardCircleFramedDrawable.java162
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java56
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java236
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardLinearLayout.java46
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserAvatar.java148
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserSelectorView.java57
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java129
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java254
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java4
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityContainer.java20
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java10
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java2
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityViewFlipper.java80
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java103
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java4
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java7
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java6
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardSubdivisionLayout.java214
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java25
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java6
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java12
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java1
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java13
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewStateManager.java168
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java184
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java379
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetRegion.java125
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/MultiPaneChallengeLayout.java486
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java117
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/PagedView.java838
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/SlidingChallengeLayout.java798
84 files changed, 5183 insertions, 1259 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8897039fbe66..cda0f36f8e98 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3222,18 +3222,11 @@ public final class Settings {
public static final String LOCK_SCREEN_OWNER_INFO = "lock_screen_owner_info";
/**
- * Id of the time appwidget on the lockscreen, or -1 if none
- * @hide
- */
- public static final String LOCK_SCREEN_STATUS_APPWIDGET_ID =
- "lock_screen_status_appwidget_id";
-
- /**
* Id of the user-selected appwidget on the lockscreen, or -1 if none
* @hide
*/
- public static final String LOCK_SCREEN_USER_SELECTED_APPWIDGET_ID =
- "lock_screen_user_selected_appwidget_id";
+ public static final String LOCK_SCREEN_APPWIDGET_IDS =
+ "lock_screen_appwidget_ids";
/**
* This preference enables showing the owner info on LockScren.
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 3f40f202bb58..9cc03413cce6 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -29,7 +29,6 @@ import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -43,9 +42,12 @@ import android.util.Log;
import android.view.View;
import android.widget.Button;
+import org.apache.harmony.kernel.vm.StringUtils;
+
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -1045,28 +1047,102 @@ public class LockPatternUtils {
}
}
- public int[] getUserDefinedWidgets() {
- int appWidgetId = -1;
+ public int[] getAppWidgets() {
String appWidgetIdString = Settings.Secure.getStringForUser(
- mContentResolver, Settings.Secure.LOCK_SCREEN_USER_SELECTED_APPWIDGET_ID,
+ mContentResolver, Settings.Secure.LOCK_SCREEN_APPWIDGET_IDS,
UserHandle.USER_CURRENT);
+ String delims = ",";
if (appWidgetIdString != null) {
- appWidgetId = (int) Integer.decode(appWidgetIdString);
+ String[] appWidgetStringIds = appWidgetIdString.split(delims);
+ int[] appWidgetIds = new int[appWidgetStringIds.length];
+ for (int i = 0; i < appWidgetStringIds.length; i++) {
+ String appWidget = appWidgetStringIds[i];
+ try {
+ appWidgetIds[i] = Integer.decode(appWidget);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+ return appWidgetIds;
+ }
+ return new int[0];
+ }
+
+ private static String combineStrings(int[] list, String separator) {
+ int listLength = list.length;
+
+ switch (listLength) {
+ case 0: {
+ return "";
+ }
+ case 1: {
+ return Integer.toString(list[0]);
+ }
+ }
+
+ int strLength = 0;
+ int separatorLength = separator.length();
+
+ String[] stringList = new String[list.length];
+ for (int i = 0; i < listLength; i++) {
+ stringList[i] = Integer.toString(list[i]);
+ strLength += stringList[i].length();
+ if (i < listLength - 1) {
+ strLength += separatorLength;
+ }
+ }
+
+ StringBuilder sb = new StringBuilder(strLength);
+
+ for (int i = 0; i < listLength; i++) {
+ sb.append(list[i]);
+ if (i < listLength - 1) {
+ sb.append(separator);
+ }
}
- return new int[] { appWidgetId };
+ return sb.toString();
}
- public int getStatusWidget() {
- int appWidgetId = -1;
- String appWidgetIdString = Settings.Secure.getStringForUser(
- mContentResolver, Settings.Secure.LOCK_SCREEN_STATUS_APPWIDGET_ID,
- UserHandle.USER_CURRENT);
- if (appWidgetIdString != null) {
- appWidgetId = (int) Integer.decode(appWidgetIdString);
+ private void writeAppWidgets(int[] appWidgetIds) {
+ Settings.Secure.putStringForUser(mContentResolver,
+ Settings.Secure.LOCK_SCREEN_APPWIDGET_IDS,
+ combineStrings(appWidgetIds, ","),
+ UserHandle.USER_CURRENT);
+ }
+
+ public void addAppWidget(int widgetId, int index) {
+ int[] widgets = getAppWidgets();
+ int[] newWidgets = new int[widgets.length + 1];
+ for (int i = 0, j = 0; i < newWidgets.length; i++) {
+ if (index == i) {
+ newWidgets[i] = widgetId;
+ i++;
+ }
+ if (i < newWidgets.length) {
+ newWidgets[i] = widgets[j];
+ j++;
+ }
}
+ writeAppWidgets(newWidgets);
+ }
- return appWidgetId;
+ public boolean removeAppWidget(int widgetId, int index) {
+ int[] widgets = getAppWidgets();
+ int[] newWidgets = new int[widgets.length - 1];
+ for (int i = 0, j = 0; i < widgets.length; i++) {
+ if (index == i) {
+ if (widgets[i] != widgetId) {
+ return false;
+ }
+ // continue...
+ } else {
+ newWidgets[j] = widgets[i];
+ j++;
+ }
+ }
+ writeAppWidgets(newWidgets);
+ return true;
}
private long getLong(String secureSettingKey, long defaultValue) {
diff --git a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
index 0f49776330a2..b7f64ece50bd 100644
--- a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
+++ b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
@@ -256,11 +256,8 @@ public class GlowPadView extends View {
setDirectionDescriptionsResourceId(resourceId);
}
- a.recycle();
+ mGravity = a.getInt(R.styleable.GlowPadView_gravity, Gravity.TOP);
- // Use gravity attribute from LinearLayout
- a = context.obtainStyledAttributes(attrs, android.R.styleable.LinearLayout);
- mGravity = a.getInt(android.R.styleable.LinearLayout_gravity, Gravity.TOP);
a.recycle();
setVibrateEnabled(mVibrationDuration > 0);
diff --git a/core/res/res/anim/keyguard_security_fade_in.xml b/core/res/res/anim/keyguard_security_fade_in.xml
index 7d5516ad4703..6293432c7ccc 100644
--- a/core/res/res/anim/keyguard_security_fade_in.xml
+++ b/core/res/res/anim/keyguard_security_fade_in.xml
@@ -15,8 +15,8 @@
-->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@interpolator/decelerate_quad"
+ android:interpolator="@android:interpolator/decelerate_quad"
android:fromAlpha="0.0" android:toAlpha="1.0"
- android:duration="@integer/kg_security_fade_duration" />
+ android:duration="@*android:integer/kg_security_fade_duration" />
diff --git a/core/res/res/anim/keyguard_security_fade_out.xml b/core/res/res/anim/keyguard_security_fade_out.xml
index 08c8b2ba7e16..4ab0229e2092 100644
--- a/core/res/res/anim/keyguard_security_fade_out.xml
+++ b/core/res/res/anim/keyguard_security_fade_out.xml
@@ -13,9 +13,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<alpha xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@interpolator/accelerate_quad"
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:interpolator/accelerate_quad"
android:fromAlpha="1.0"
android:toAlpha="0.0"
- android:duration="@integer/kg_security_fade_duration"
+ android:duration="@*android:integer/kg_security_fade_duration"
/>
diff --git a/core/res/res/drawable-hdpi/add_widget.png b/core/res/res/drawable-hdpi/add_widget.png
new file mode 100644
index 000000000000..9cf9b60e0c67
--- /dev/null
+++ b/core/res/res/drawable-hdpi/add_widget.png
Binary files 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
index 000000000000..9eeadc4d118f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/security_frame.9.png
Binary files 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
index 000000000000..bd4640f7228c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/security_handle.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_return_holo.png b/core/res/res/drawable-hdpi/sym_keyboard_return_holo.png
new file mode 100644
index 000000000000..f1bcf487c5f1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_return_holo.png
Binary files 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
index 000000000000..9cf9b60e0c67
--- /dev/null
+++ b/core/res/res/drawable-mdpi/add_widget.png
Binary files 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
index 000000000000..9eeadc4d118f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/security_frame.9.png
Binary files 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
index 000000000000..bd4640f7228c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/security_handle.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_return_holo.png b/core/res/res/drawable-mdpi/sym_keyboard_return_holo.png
new file mode 100644
index 000000000000..d5a7708ca082
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_return_holo.png
Binary files differ
diff --git a/core/res/res/drawable-sw600dp-hdpi/sym_keyboard_return_holo.png b/core/res/res/drawable-sw600dp-hdpi/sym_keyboard_return_holo.png
new file mode 100644
index 000000000000..f1bcf487c5f1
--- /dev/null
+++ b/core/res/res/drawable-sw600dp-hdpi/sym_keyboard_return_holo.png
Binary files differ
diff --git a/core/res/res/drawable-sw600dp-mdpi/sym_keyboard_return_holo.png b/core/res/res/drawable-sw600dp-mdpi/sym_keyboard_return_holo.png
new file mode 100644
index 000000000000..d5a7708ca082
--- /dev/null
+++ b/core/res/res/drawable-sw600dp-mdpi/sym_keyboard_return_holo.png
Binary files differ
diff --git a/core/res/res/drawable-sw600dp-xhdpi/sym_keyboard_return_holo.png b/core/res/res/drawable-sw600dp-xhdpi/sym_keyboard_return_holo.png
new file mode 100644
index 000000000000..55174e076765
--- /dev/null
+++ b/core/res/res/drawable-sw600dp-xhdpi/sym_keyboard_return_holo.png
Binary files 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
index 000000000000..9cf9b60e0c67
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/add_widget.png
Binary files 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
index 000000000000..9eeadc4d118f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/security_frame.9.png
Binary files 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
index 000000000000..bd4640f7228c
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/security_handle.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/sym_keyboard_return_holo.png b/core/res/res/drawable-xhdpi/sym_keyboard_return_holo.png
new file mode 100644
index 000000000000..55174e076765
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/sym_keyboard_return_holo.png
Binary files differ
diff --git a/core/res/res/layout-land/keyguard_host_view.xml b/core/res/res/layout-land/keyguard_host_view.xml
index 521853f4174f..e76951b4a47e 100644
--- a/core/res/res/layout-land/keyguard_host_view.xml
+++ b/core/res/res/layout-land/keyguard_host_view.xml
@@ -21,30 +21,53 @@
and the security view. -->
<com.android.internal.policy.impl.keyguard.KeyguardHostView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="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:orientation="horizontal">
- <include layout="@layout/keyguard_widget_region"
- android:layout_width="0dp"
+ <com.android.internal.policy.impl.keyguard.MultiPaneChallengeLayout
+ android:id="@+id/multi_pane_challenge"
+ android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_weight="@integer/kg_widget_region_weight" />
+ android:clipChildren="false">
- <com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper
- android:id="@+id/view_flipper"
- android:layout_width="0dp"
- 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">
+ <include layout="@layout/keyguard_widget_pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ androidprv:layout_centerWithinArea="0.55"
+ androidprv:layout_childType="widget"
+ androidprv:layout_maxWidth="480dp"
+ androidprv:layout_maxHeight="480dp" />
+
+ <include layout="@layout/keyguard_multi_user_selector"/>
- </com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper>
+ <View android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_childType="scrim"
+ android:background="#99000000" />
+ <com.android.internal.policy.impl.keyguard.KeyguardSecurityContainer
+ android:id="@+id/keyguard_security_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ androidprv:layout_childType="challenge"
+ androidprv:layout_centerWithinArea="0.55">
+ <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.MultiPaneChallengeLayout>
</com.android.internal.policy.impl.keyguard.KeyguardHostView>
+
diff --git a/core/res/res/layout-port/keyguard_host_view.xml b/core/res/res/layout-port/keyguard_host_view.xml
index 981fe6d0da55..98091359cce1 100644
--- a/core/res/res/layout-port/keyguard_host_view.xml
+++ b/core/res/res/layout-port/keyguard_host_view.xml
@@ -21,28 +21,53 @@
and the security view. -->
<com.android.internal.policy.impl.keyguard.KeyguardHostView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="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"
+ androidprv: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>
+
+ <View android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ androidprv:layout_childType="scrim"
+ android:background="#99000000" />
+
+ <com.android.internal.policy.impl.keyguard.KeyguardSecurityContainer
+ android:id="@+id/keyguard_security_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ androidprv:layout_childType="challenge"
+ 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
index 23c1e9cb28a9..9e5fc484ef14 100644
--- a/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
+++ b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
@@ -21,33 +21,53 @@
and the security view. -->
<com.android.internal.policy.impl.keyguard.KeyguardHostView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="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">
+ android:orientation="horizontal">
- <include layout="@layout/keyguard_widget_region"
+ <com.android.internal.policy.impl.keyguard.MultiPaneChallengeLayout
+ android:id="@+id/multi_pane_challenge"
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:layout_height="match_parent"
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">
+ android:orientation="vertical">
+ <include layout="@layout/keyguard_widget_pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ androidprv:layout_centerWithinArea="0.55"
+ androidprv:layout_childType="widget"
+ androidprv:layout_maxWidth="480dp"
+ androidprv:layout_maxHeight="480dp" />
+ <include layout="@layout/keyguard_multi_user_selector"/>
- </com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper>
+ <View android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ androidprv:layout_childType="scrim"
+ android:background="#99000000" />
-</com.android.internal.policy.impl.keyguard.KeyguardHostView>
+ <com.android.internal.policy.impl.keyguard.KeyguardSecurityContainer
+ android:id="@+id/keyguard_security_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ androidprv:layout_childType="challenge"
+ android:layout_gravity="center_horizontal|bottom">
+ <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.MultiPaneChallengeLayout>
+</com.android.internal.policy.impl.keyguard.KeyguardHostView>
diff --git a/core/res/res/layout-sw600dp/keyguard_glow_pad_container.xml b/core/res/res/layout-sw600dp/keyguard_glow_pad_container.xml
index 1eef099e2985..207bc5728282 100644
--- a/core/res/res/layout-sw600dp/keyguard_glow_pad_container.xml
+++ b/core/res/res/layout-sw600dp/keyguard_glow_pad_container.xml
@@ -18,7 +18,7 @@
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<include layout="@layout/keyguard_glow_pad_view"
- android:layout_width="@dimen/kg_glow_pad_size"
- android:layout_height="@dimen/kg_glow_pad_size"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_gravity="center" />
-</merge> \ No newline at end of file
+</merge>
diff --git a/core/res/res/layout/keyguard_add_widget.xml b/core/res/res/layout/keyguard_add_widget.xml
new file mode 100644
index 000000000000..6345e896018a
--- /dev/null
+++ b/core/res/res/layout/keyguard_add_widget.xml
@@ -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_emergency_carrier_area_and_recovery.xml b/core/res/res/layout/keyguard_emergency_carrier_area_and_recovery.xml
index 68840abc051b..eeb4178af956 100644
--- a/core/res/res/layout/keyguard_emergency_carrier_area_and_recovery.xml
+++ b/core/res/res/layout/keyguard_emergency_carrier_area_and_recovery.xml
@@ -61,7 +61,7 @@
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:drawableLeft="@drawable/lockscreen_forgot_password_button"
+ android:drawableLeft="@*android:drawable/lockscreen_forgot_password_button"
style="?android:attr/buttonBarButtonStyle"
android:textSize="@dimen/kg_status_line_font_size"
android:textColor="?android:attr/textColorSecondary"
diff --git a/core/res/res/layout/keyguard_face_unlock_view.xml b/core/res/res/layout/keyguard_face_unlock_view.xml
index 845c265fdb29..00f14f5c7290 100644
--- a/core/res/res/layout/keyguard_face_unlock_view.xml
+++ b/core/res/res/layout/keyguard_face_unlock_view.xml
@@ -39,7 +39,7 @@
android:id="@+id/spotlightMask"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/facelock_spotlight_mask"
+ android:background="@*android:color/facelock_spotlight_mask"
/>
<ImageButton
@@ -50,7 +50,7 @@
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:background="#00000000"
- android:src="@drawable/ic_facial_backup"
+ android:src="@*android:drawable/ic_facial_backup"
/>
</RelativeLayout>
diff --git a/core/res/res/layout/keyguard_multi_user_avatar.xml b/core/res/res/layout/keyguard_multi_user_avatar.xml
index 0e851e378c87..2d8f02d6aa09 100644
--- a/core/res/res/layout/keyguard_multi_user_avatar.xml
+++ b/core/res/res/layout/keyguard_multi_user_avatar.xml
@@ -20,35 +20,26 @@
<!-- 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>
diff --git a/core/res/res/layout/keyguard_multi_user_selector.xml b/core/res/res/layout/keyguard_multi_user_selector.xml
index 5a6e9989c140..ee01285062a1 100644
--- a/core/res/res/layout/keyguard_multi_user_selector.xml
+++ b/core/res/res/layout/keyguard_multi_user_selector.xml
@@ -17,18 +17,23 @@
*/
-->
<com.android.internal.policy.impl.keyguard.KeyguardMultiUserSelectorView
+ xmlns:androidprv="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
+ androidprv:layout_childType="userSwitcher"
+ android:id="@+id/keyguard_user_selector"
android:orientation="horizontal"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:contentDescription="@string/keyguard_accessibility_user_selector">
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:contentDescription="@*android:string/keyguard_accessibility_user_selector"
+ android:visibility="gone">
- <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="@dimen/keyguard_muliuser_selector_margin"
+ 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_multi_user_selector_widget.xml b/core/res/res/layout/keyguard_multi_user_selector_widget.xml
index ad9fdfee4849..fc126fec9a1c 100644
--- a/core/res/res/layout/keyguard_multi_user_selector_widget.xml
+++ b/core/res/res/layout/keyguard_multi_user_selector_widget.xml
@@ -23,7 +23,4 @@
android:id="@+id/keyguard_multi_user_selector"
android:layout_width="match_parent"
android:layout_height="match_parent">
-
- <include layout="@layout/keyguard_multi_user_selector"/>
-
</com.android.internal.policy.impl.keyguard.KeyguardWidgetFrame> \ No newline at end of file
diff --git a/core/res/res/layout/keyguard_password_view.xml b/core/res/res/layout/keyguard_password_view.xml
index 81916f73cb09..e28f2ac38bba 100644
--- a/core/res/res/layout/keyguard_password_view.xml
+++ b/core/res/res/layout/keyguard_password_view.xml
@@ -77,18 +77,6 @@
android:imeOptions="flagForceAscii|actionDone"
/>
- <!-- This delete button is only visible for numeric PIN entry -->
- <ImageButton android:id="@+id/delete_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:src="@*android:drawable/ic_input_delete"
- android:clickable="true"
- android:padding="8dip"
- android:background="?android:attr/selectableItemBackground"
- android:visibility="gone"
- />
-
<ImageView android:id="@+id/switch_ime_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -101,20 +89,6 @@
/>
</LinearLayout>
-
- <!-- Numeric keyboard -->
- <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="4dip"
- android:layout_marginEnd="4dip"
- android:paddingTop="4dip"
- android:paddingBottom="4dip"
- android:background="#40000000"
- android:keyBackground="@*android:drawable/btn_keyboard_key_ics"
- android:visibility="gone"
- android:clickable="true"
- />
</LinearLayout>
</LinearLayout>
</FrameLayout>
diff --git a/core/res/res/layout/keyguard_pin_view.xml b/core/res/res/layout/keyguard_pin_view.xml
new file mode 100644
index 000000000000..9b883afee9b1
--- /dev/null
+++ b/core/res/res/layout/keyguard_pin_view.xml
@@ -0,0 +1,206 @@
+<?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"
+ xmlns:androidprv="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"
+ >
+ <TextView android:id="@+id/passwordEntry"
+ android:editable="true"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center"
+ android:layout_marginStart="@*android:dimen/keyguard_lockscreen_pin_margin_left"
+ android:singleLine="true"
+ android:cursorVisible="false"
+ android:background="@null"
+ android:textAppearance="@android:style/TextAppearance.NumPadKey"
+ android:imeOptions="flagForceAscii|actionDone"
+ />
+ <ImageButton android:id="@+id/delete_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:src="@*android:drawable/ic_input_delete"
+ android:clickable="true"
+ android:paddingTop="8dip"
+ android:paddingBottom="8dip"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:background="?android:attr/selectableItemBackground"
+ />
+ </LinearLayout>
+ <View
+ android:layout_width="wrap_content"
+ android:layout_height="1dp"
+ android:background="#55FFFFFF"
+ />
+ <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="@style/Widget.Button.NumPadKey"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ androidprv:textView="@+id/passwordEntry"
+ androidprv:digit="1"
+ />
+ <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+ android:id="@+id/key2"
+ style="@style/Widget.Button.NumPadKey"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ androidprv:textView="@+id/passwordEntry"
+ androidprv:digit="2"
+ />
+ <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+ android:id="@+id/key3"
+ style="@style/Widget.Button.NumPadKey"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ androidprv:textView="@+id/passwordEntry"
+ androidprv: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="@style/Widget.Button.NumPadKey"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ androidprv:textView="@+id/passwordEntry"
+ androidprv:digit="4"
+ />
+ <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+ android:id="@+id/key5"
+ style="@style/Widget.Button.NumPadKey"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ androidprv:textView="@+id/passwordEntry"
+ androidprv:digit="5"
+ />
+ <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+ android:id="@+id/key6"
+ style="@style/Widget.Button.NumPadKey"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ androidprv:textView="@+id/passwordEntry"
+ androidprv: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="@style/Widget.Button.NumPadKey"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ androidprv:textView="@+id/passwordEntry"
+ androidprv:digit="7"
+ />
+ <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+ android:id="@+id/key8"
+ style="@style/Widget.Button.NumPadKey"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ androidprv:textView="@+id/passwordEntry"
+ androidprv:digit="8"
+ />
+ <view class="com.android.internal.policy.impl.keyguard.NumPadKey"
+ android:id="@+id/key9"
+ style="@style/Widget.Button.NumPadKey"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ androidprv:textView="@+id/passwordEntry"
+ androidprv: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="@style/Widget.Button.NumPadKey"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ androidprv:textView="@+id/passwordEntry"
+ androidprv:digit="0"
+ />
+ <ImageButton
+ 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:src="@drawable/sym_keyboard_return_holo"
+ />
+ </LinearLayout>
+
+ <include layout="@layout/keyguard_emergency_carrier_area_and_recovery"
+ android:id="@+id/keyguard_selector_fade_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_gravity="bottom|center_horizontal"
+ android:gravity="center_horizontal" />
+
+</com.android.internal.policy.impl.keyguard.KeyguardPINView>
diff --git a/core/res/res/layout/keyguard_status_view.xml b/core/res/res/layout/keyguard_status_view.xml
index 1de0f6ccad80..9532a889c783 100644
--- a/core/res/res/layout/keyguard_status_view.xml
+++ b/core/res/res/layout/keyguard_status_view.xml
@@ -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_transport_control_view.xml b/core/res/res/layout/keyguard_transport_control_view.xml
index 5a6083a2c084..532322c764bf 100644
--- a/core/res/res/layout/keyguard_transport_control_view.xml
+++ b/core/res/res/layout/keyguard_transport_control_view.xml
@@ -26,8 +26,8 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:foreground="@drawable/ic_lockscreen_player_background"
- android:contentDescription="@string/keygaurd_accessibility_media_controls">
+ android:foreground="@*android:drawable/ic_lockscreen_player_background"
+ android:contentDescription="@*android:string/keygaurd_accessibility_media_controls">
<!-- Use ImageView for its cropping features; otherwise could be android:background -->
<ImageView
android:id="@+id/albumart"
@@ -70,11 +70,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:src="@drawable/ic_media_previous"
+ android:src="@*android:drawable/ic_media_previous"
android:clickable="true"
android:background="?android:attr/selectableItemBackground"
android:padding="10dip"
- android:contentDescription="@string/lockscreen_transport_prev_description"/>
+ android:contentDescription="@*android:string/lockscreen_transport_prev_description"/>
</FrameLayout>
<FrameLayout
android:layout_width="wrap_content"
@@ -86,10 +86,10 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:clickable="true"
- android:src="@drawable/ic_media_play"
+ android:src="@*android:drawable/ic_media_play"
android:background="?android:attr/selectableItemBackground"
android:padding="10dip"
- android:contentDescription="@string/lockscreen_transport_play_description"/>
+ android:contentDescription="@*android:string/lockscreen_transport_play_description"/>
</FrameLayout>
<FrameLayout
android:layout_width="wrap_content"
@@ -101,10 +101,10 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:clickable="true"
- android:src="@drawable/ic_media_next"
+ android:src="@*android:drawable/ic_media_next"
android:background="?android:attr/selectableItemBackground"
android:padding="10dip"
- android:contentDescription="@string/lockscreen_transport_next_description"/>
+ android:contentDescription="@*android:string/lockscreen_transport_next_description"/>
</FrameLayout>
</LinearLayout>
</LinearLayout>
diff --git a/core/res/res/layout/keyguard_widget_pager.xml b/core/res/res/layout/keyguard_widget_pager.xml
new file mode 100644
index 000000000000..6662f83ce62b
--- /dev/null
+++ b/core/res/res/layout/keyguard_widget_pager.xml
@@ -0,0 +1,32 @@
+<?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:androidprv="http://schemas.android.com/apk/res/android"
+ 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"
+ androidprv:pageSpacing="10dp">
+</com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
diff --git a/core/res/res/layout/keyguard_widget_region.xml b/core/res/res/layout/keyguard_widget_region.xml
deleted file mode 100644
index ed10c2bdf6e0..000000000000
--- a/core/res/res/layout/keyguard_widget_region.xml
+++ /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>
diff --git a/core/res/res/values-sw600dp/bools.xml b/core/res/res/values-sw600dp/bools.xml
index 3753aba3749a..eae4f87d7b09 100644
--- a/core/res/res/values-sw600dp/bools.xml
+++ b/core/res/res/values-sw600dp/bools.xml
@@ -19,4 +19,6 @@
<bool name="show_ongoing_ime_switcher">true</bool>
<bool name="kg_share_status_area">false</bool>
<bool name="kg_sim_puk_account_full_screen">false</bool>
+ <!-- No camera for you, tablet user -->
+ <bool name="kg_enable_camera_default_widget">false</bool>
</resources>
diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index 564545adb1fe..0d01df4e9824 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -108,5 +108,9 @@
<dimen name="kg_runway_lights_top_margin">-10dp</dimen>
<!-- Margin around the various security views -->
- <dimen name="keyguard_security_view_margin">24dp</dimen>
+ <dimen name="keyguard_security_view_margin">12dp</dimen>
+
+ <!-- Margin around the various security views -->
+ <dimen name="keyguard_muliuser_selector_margin">12dp</dimen>
+
</resources>
diff --git a/core/res/res/values-sw720dp/dimens.xml b/core/res/res/values-sw720dp/dimens.xml
index 6144961b120a..d6d2b6621a0d 100644
--- a/core/res/res/values-sw720dp/dimens.xml
+++ b/core/res/res/values-sw720dp/dimens.xml
@@ -101,5 +101,15 @@
<dimen name="kg_runway_lights_top_margin">-30dp</dimen>
<!-- Margin around the various security views -->
- <dimen name="keyguard_security_view_margin">100dp</dimen>
+ <dimen name="keyguard_muliuser_selector_margin">24dp</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>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 86154762d597..8744bfe8c1c6 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -398,4 +398,17 @@
<item>@null</item>
</array>
+ <!-- list of 3- or 4-letter mnemonics for a 10-key numeric keypad -->
+ <string-array translatable="false" name="lockscreen_num_pad_klondike">
+ <item></item><!-- 0 -->
+ <item></item><!-- 1 -->
+ <item>ABC</item><!-- 2 -->
+ <item>DEF</item><!-- 3 -->
+ <item>GHI</item><!-- 4 -->
+ <item>JKL</item><!-- 5 -->
+ <item>MNO</item><!-- 6 -->
+ <item>PQRS</item><!-- 7 -->
+ <item>TUV</item><!-- 8 -->
+ <item>WXYZ</item><!-- 9 -->
+ </string-array>
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 3550df93c4f2..932811e73be9 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5464,6 +5464,8 @@
<!-- Used when the handle shouldn't wait to be hit before following the finger -->
<attr name="alwaysTrackFinger"/>
+
+ <attr name="gravity" />
</declare-styleable>
<!-- =============================== -->
@@ -5759,14 +5761,6 @@
<!-- 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 -->
@@ -5781,10 +5775,57 @@
<attr name="leftToRight" format="boolean" />
</declare-styleable>
+ <!-- Some child types have special behavior. -->
+ <attr name="layout_childType">
+ <!-- No special behavior. Layout will proceed as normal. -->
+ <enum name="none" value="0" />
+ <!-- Widget container.
+ This will be resized in response to certain events. -->
+ <enum name="widget" value="1" />
+ <!-- Security challenge container.
+ This will be dismissed/shown in response to certain events,
+ possibly obscuring widget elements. -->
+ <enum name="challenge" value="2" />
+ <!-- User switcher.
+ This will consume space from the total layout area. -->
+ <enum name="userSwitcher" value="3" />
+ <!-- Scrim. This will block access to child views that
+ come before it in the child list in bouncer mode. -->
+ <enum name="scrim" value="4" />
+ </attr>
+
+ <declare-styleable name="SlidingChallengeLayout">
+ <attr name="dragHandle" format="reference" />
+ </declare-styleable>
+
+ <declare-styleable name="SlidingChallengeLayout_Layout">
+ <attr name="layout_childType" />
+ </declare-styleable>
+
<!-- Attributes that can be used with <code>&lt;FragmentBreadCrumbs&gt;</code>
tags. -->
<declare-styleable name="FragmentBreadCrumbs">
<attr name="gravity" />
</declare-styleable>
+ <declare-styleable name="MultiPaneChallengeLayout">
+ <!-- Influences how layout_centerWithinArea behaves -->
+ <attr name="orientation" />
+ </declare-styleable>
+
+ <declare-styleable name="MultiPaneChallengeLayout_Layout">
+ <!-- Percentage of the screen this child should consume or center within.
+ If 0/default, the view will be measured by standard rules
+ as if this were a FrameLayout. -->
+ <attr name="layout_centerWithinArea" format="float" />
+ <attr name="layout_childType" />
+ <attr name="layout_gravity" />
+ <attr name="layout_maxWidth" format="dimension" />
+ <attr name="layout_maxHeight" />
+ </declare-styleable>
+
+ <declare-styleable name="NumPadKey">
+ <attr name="digit" format="integer" />
+ <attr name="textView" format="reference" />
+ </declare-styleable>
</resources>
diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml
index f9762b18f9ad..d4ead012f3a0 100644
--- a/core/res/res/values/bools.xml
+++ b/core/res/res/values/bools.xml
@@ -15,6 +15,7 @@
-->
<resources>
+ <bool name="kg_enable_camera_default_widget">true</bool>
<bool name="action_bar_embed_tabs">true</bool>
<bool name="action_bar_embed_tabs_pre_jb">false</bool>
<bool name="split_action_bar_is_narrow">true</bool>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 6a93f3081863..b19e23df9825 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -190,5 +190,10 @@
<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>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 948a3d3c40c8..f377c847a9eb 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -242,6 +242,9 @@
<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>
@@ -305,4 +308,23 @@
<!-- Margin around the various security views -->
<dimen name="keyguard_security_view_margin">8dp</dimen>
+
+ <!-- Margin around the various security views -->
+ <dimen name="keyguard_muliuser_selector_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>
+
+ <!-- Size of the region along the edge of the screen that will accept
+ swipes to scroll the widget area. -->
+ <dimen name="kg_edge_swipe_region_size">16dp</dimen>
</resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 4371aec76228..1dfb6e0785e6 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -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>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8ef91df1853b..f518e7f3d87a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -962,6 +962,7 @@
<java-symbol type="drawable" name="status_bar_background" />
<java-symbol type="drawable" name="sym_keyboard_shift" />
<java-symbol type="drawable" name="sym_keyboard_shift_locked" />
+ <java-symbol type="drawable" name="sym_keyboard_return_holo" />
<java-symbol type="drawable" name="tab_bottom_left" />
<java-symbol type="drawable" name="tab_bottom_left_v4" />
<java-symbol type="drawable" name="tab_bottom_right" />
@@ -1086,8 +1087,8 @@
<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="anim" name="slide_in_child_bottom" />
<java-symbol type="anim" name="slide_in_right" />
@@ -1177,6 +1178,7 @@
<java-symbol type="array" name="lockscreen_targets_when_silent" />
<java-symbol type="array" name="lockscreen_targets_when_soundon" />
<java-symbol type="array" name="lockscreen_targets_with_camera" />
+ <java-symbol type="array" name="lockscreen_num_pad_klondike" />
<java-symbol type="attr" name="actionModePopupWindowStyle" />
<java-symbol type="attr" name="dialogCustomTitleDecorLayout" />
<java-symbol type="attr" name="dialogTitleDecorLayout" />
@@ -1191,12 +1193,17 @@
<java-symbol type="bool" name="config_lidControlsSleep" />
<java-symbol type="bool" name="config_reverseDefaultRotation" />
<java-symbol type="bool" name="config_showNavigationBar" />
+ <java-symbol type="bool" name="kg_enable_camera_default_widget" />
<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" />
@@ -1204,6 +1211,10 @@
<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="dimen" name="kg_edge_swipe_region_size" />
<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" />
@@ -1222,6 +1233,7 @@
<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" />
@@ -1280,6 +1292,7 @@
<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" />
@@ -1304,12 +1317,15 @@
<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="multi_pane_challenge" />
+ <java-symbol type="id" name="keyguard_user_selector" />
+ <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" />
@@ -1334,6 +1350,7 @@
<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" />
@@ -1403,6 +1420,9 @@
<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" />
+ <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/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index eef54460d8af..983328d438ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -55,6 +55,9 @@ public class NavigationBarView extends LinearLayout {
final static boolean NAVBAR_ALWAYS_AT_RIGHT = true;
+ // slippery nav bar when everything is disabled, e.g. during setup
+ final static boolean SLIPPERY_WHEN_DISABLED= true;
+
final static boolean ANIMATE_HIDE_TRANSITION = false; // turned off because it introduces unsightly delay when videos goes to full screen
protected IStatusBarService mBarService;
@@ -237,7 +240,9 @@ public class NavigationBarView extends LinearLayout {
final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0);
final boolean disableSearch = ((disabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0);
- setSlippery(disableHome && disableRecent && disableBack);
+ if (SLIPPERY_WHEN_DISABLED) {
+ setSlippery(disableHome && disableRecent && disableBack && disableSearch);
+ }
if (!mScreenOn && mCurrentView != null) {
ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons);
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/CameraWidgetFrame.java b/policy/src/com/android/internal/policy/impl/keyguard/CameraWidgetFrame.java
new file mode 100644
index 000000000000..c87093a7086d
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/CameraWidgetFrame.java
@@ -0,0 +1,200 @@
+/*
+ * 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.pm.PackageManager.NameNotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+
+import com.android.internal.policy.impl.keyguard.KeyguardActivityLauncher.CameraWidgetInfo;
+import com.android.internal.R;
+
+public class CameraWidgetFrame extends KeyguardWidgetFrame {
+ private static final String TAG = CameraWidgetFrame.class.getSimpleName();
+ private static final boolean DEBUG = KeyguardHostView.DEBUG;
+ private static final int WIDGET_ANIMATION_DURATION = 250;
+
+ interface Callbacks {
+ void onLaunchingCamera();
+ void onCameraLaunched();
+ }
+
+ private final Handler mHandler = new Handler();
+ private final KeyguardActivityLauncher mActivityLauncher;
+ private final Callbacks mCallbacks;
+
+ private View mWidgetView;
+ private long mLaunchCameraStart;
+
+ private final Runnable mLaunchCameraRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mActivityLauncher.launchCamera();
+ }};
+
+ private final Runnable mRenderRunnable = new Runnable() {
+ @Override
+ public void run() {
+ render();
+ }};
+
+ private CameraWidgetFrame(Context context, Callbacks callbacks,
+ KeyguardActivityLauncher activityLauncher) {
+ super(context);
+
+ mCallbacks = callbacks;
+ mActivityLauncher = activityLauncher;
+ }
+
+ public static CameraWidgetFrame create(Context context, Callbacks callbacks,
+ KeyguardActivityLauncher launcher) {
+ if (context == null || callbacks == null || launcher == null)
+ return null;
+
+ CameraWidgetInfo widgetInfo = launcher.getCameraWidgetInfo();
+ if (widgetInfo == null)
+ return null;
+ View widgetView = widgetInfo.layoutId > 0 ?
+ inflateWidgetView(context, widgetInfo) :
+ inflateGenericWidgetView(context);
+ if (widgetView == null)
+ return null;
+
+ ImageView preview = new ImageView(context);
+ preview.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
+ preview.setScaleType(ScaleType.FIT_CENTER);
+ CameraWidgetFrame cameraWidgetFrame = new CameraWidgetFrame(context, callbacks, launcher);
+ cameraWidgetFrame.addView(preview);
+ cameraWidgetFrame.mWidgetView = widgetView;
+ return cameraWidgetFrame;
+ }
+
+ private static View inflateWidgetView(Context context, CameraWidgetInfo widgetInfo) {
+ View widgetView = null;
+ Exception exception = null;
+ try {
+ Context cameraContext = context.createPackageContext(
+ widgetInfo.contextPackage, Context.CONTEXT_RESTRICTED);
+ LayoutInflater cameraInflater = (LayoutInflater)
+ cameraContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ cameraInflater = cameraInflater.cloneInContext(cameraContext);
+ widgetView = cameraInflater.inflate(widgetInfo.layoutId, null, false);
+ } catch (NameNotFoundException e) {
+ exception = e;
+ } catch (RuntimeException e) {
+ exception = e;
+ }
+ if (exception != null) {
+ Log.w(TAG, "Error creating camera widget view", exception);
+ }
+ return widgetView;
+ }
+
+ private static View inflateGenericWidgetView(Context context) {
+ ImageView iv = new ImageView(context);
+ iv.setImageResource(com.android.internal.R.drawable.ic_lockscreen_camera);
+ iv.setScaleType(ScaleType.CENTER);
+ iv.setBackgroundColor(Color.argb(127, 0, 0, 0));
+ return iv;
+ }
+
+ public void render() {
+ int width = getRootView().getWidth();
+ int height = getRootView().getHeight();
+ if (DEBUG) Log.d(TAG, String.format("render [%sx%s]", width, height));
+ Bitmap offscreen = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(offscreen);
+ mWidgetView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+ mWidgetView.layout(0, 0, width, height);
+ mWidgetView.draw(c);
+ ((ImageView)getChildAt(0)).setImageBitmap(offscreen);
+ }
+
+ private void transitionToCamera() {
+ int startWidth = getChildAt(0).getWidth();
+ int startHeight = getChildAt(0).getHeight();
+
+ int finishWidth = getRootView().getWidth();
+ int finishHeight = getRootView().getHeight();
+
+ float scaleX = (float) finishWidth / startWidth;
+ float scaleY = (float) finishHeight / startHeight;
+
+ float scale = Math.max(scaleX, scaleY);
+ animate()
+ .scaleX(scale)
+ .scaleY(scale)
+ .setDuration(WIDGET_ANIMATION_DURATION)
+ .withEndAction(mLaunchCameraRunnable)
+ .start();
+ mCallbacks.onLaunchingCamera();
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+
+ if (!hasWindowFocus) {
+ if (mLaunchCameraStart > 0) {
+ long launchTime = SystemClock.uptimeMillis() - mLaunchCameraStart;
+ if (DEBUG) Log.d(TAG, String.format("Camera took %s to launch", launchTime));
+ mLaunchCameraStart = 0;
+ }
+ onCameraLaunched();
+ }
+ }
+
+ @Override
+ public void onActive(boolean isActive) {
+ if (isActive) {
+ mHandler.post(new Runnable(){
+ @Override
+ public void run() {
+ transitionToCamera();
+ }});
+ } else {
+ reset();
+ }
+ }
+
+ private void onCameraLaunched() {
+ reset();
+ mCallbacks.onCameraLaunched();
+ }
+
+ private void reset() {
+ animate().cancel();
+ setScaleX(1);
+ setScaleY(1);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mHandler.post(mRenderRunnable);
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/ChallengeLayout.java b/policy/src/com/android/internal/policy/impl/keyguard/ChallengeLayout.java
new file mode 100644
index 000000000000..605a7380775c
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/ChallengeLayout.java
@@ -0,0 +1,92 @@
+/*
+ * 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;
+
+/**
+ * Interface implemented by ViewGroup-derived layouts that implement
+ * special logic for presenting security challenges to the user.
+ */
+public interface ChallengeLayout {
+ /**
+ * @return true if the security challenge area of this layout is currently visible
+ */
+ boolean isChallengeShowing();
+
+ /**
+ * @return true if the challenge area significantly overlaps other content
+ */
+ boolean isChallengeOverlapping();
+
+ /**
+ * Show or hide the challenge layout.
+ *
+ * If you want to show the challenge layout in bouncer mode where applicable,
+ * use {@link #showBouncer()} instead.
+ *
+ * @param b true to show, false to hide
+ */
+ void showChallenge(boolean b);
+
+ /**
+ * Show the bouncer challenge. This may block access to other child views.
+ */
+ void showBouncer();
+
+ /**
+ * Hide the bouncer challenge if it is currently showing.
+ * This may restore previously blocked access to other child views.
+ */
+ void hideBouncer();
+
+ /**
+ * Returns true if the challenge is currently in bouncer mode,
+ * potentially blocking access to other child views.
+ */
+ boolean isBouncing();
+
+ /**
+ * Set a listener that will respond to changes in bouncer state.
+ *
+ * @param listener listener to register
+ */
+ void setOnBouncerStateChangedListener(OnBouncerStateChangedListener listener);
+
+ /**
+ * Listener interface that reports changes in bouncer state.
+ * The bouncer is
+ */
+ public interface OnBouncerStateChangedListener {
+ /**
+ * Called when the bouncer state changes.
+ * The bouncer is activated when the user must pass a security challenge
+ * to proceed with the requested action.
+ *
+ * <p>This differs from simply showing or hiding the security challenge
+ * as the bouncer will prevent interaction with other elements of the UI.
+ * If the user attempts to escape from the bouncer, it will be dismissed,
+ * this method will be called with false as the parameter, and the action
+ * should be canceled. If the security component reports a successful
+ * authentication and the containing code calls hideBouncer() as a result,
+ * this method will also be called with a false parameter. It is up to the
+ * caller of hideBouncer to be ready for this.</p>
+ *
+ * @param bouncerActive true if the bouncer is now active,
+ * false if the bouncer was dismissed.
+ */
+ public void onBouncerStateChanged(boolean bouncerActive);
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/CheckLongPressHelper.java b/policy/src/com/android/internal/policy/impl/keyguard/CheckLongPressHelper.java
new file mode 100644
index 000000000000..020fdbabb64b
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/CheckLongPressHelper.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl.keyguard;
+
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+public class CheckLongPressHelper {
+ private View mView;
+ private boolean mHasPerformedLongPress;
+ private CheckForLongPress mPendingCheckForLongPress;
+ private float mDownX, mDownY;
+ private int mLongPressTimeout;
+ private int mScaledTouchSlop;
+
+ class CheckForLongPress implements Runnable {
+ public void run() {
+ if ((mView.getParent() != null) && mView.hasWindowFocus()
+ && !mHasPerformedLongPress) {
+ if (mView.performLongClick()) {
+ mView.setPressed(false);
+ mHasPerformedLongPress = true;
+ }
+ }
+ }
+ }
+
+ public CheckLongPressHelper(View v) {
+ mScaledTouchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
+ mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
+ mView = v;
+ }
+
+ public void postCheckForLongPress(MotionEvent ev) {
+ mDownX = ev.getX();
+ mDownY = ev.getY();
+ mHasPerformedLongPress = false;
+
+ if (mPendingCheckForLongPress == null) {
+ mPendingCheckForLongPress = new CheckForLongPress();
+ }
+ mView.postDelayed(mPendingCheckForLongPress, mLongPressTimeout);
+ }
+
+ public void onMove(MotionEvent ev) {
+ float x = ev.getX();
+ float y = ev.getY();
+
+ if (Math.sqrt(Math.pow(mDownX - x, 2) + Math.pow(mDownY - y, 2)) > mScaledTouchSlop) {
+ cancelLongPress();
+ }
+ }
+
+ public void cancelLongPress() {
+ mHasPerformedLongPress = false;
+ if (mPendingCheckForLongPress != null) {
+ mView.removeCallbacks(mPendingCheckForLongPress);
+ mPendingCheckForLongPress = null;
+ }
+ }
+
+ public boolean hasPerformedLongPress() {
+ return mHasPerformedLongPress;
+ }
+} \ No newline at end of file
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
index 000000000000..bad2d6ca7a3e
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAbsKeyInputView.java
@@ -0,0 +1,256 @@
+/*
+ * 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.HapticFeedbackConstants;
+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 TextView mPasswordEntry;
+ protected LockPatternUtils mLockPatternUtils;
+ protected SecurityMessageDisplay mSecurityMessageDisplay;
+ protected boolean mEnableHaptics;
+
+ // To avoid accidental lockout due to events while the device in in the pocket, ignore
+ // any passwords with length less than or equal to this length.
+ protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
+
+ public KeyguardAbsKeyInputView(Context context) {
+ this(context, null);
+ }
+
+ public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ mCallback = callback;
+ }
+
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ mLockPatternUtils = utils;
+ mEnableHaptics = mLockPatternUtils.isTactileFeedbackEnabled();
+ }
+
+ @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);
+
+ mPasswordEntry = (TextView) 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();
+ }
+
+ // Cause a VIRTUAL_KEY vibration
+ public void doHapticKeyClick() {
+ if (mEnableHaptics) {
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
+ | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
+ }
+ }
+}
+
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java
index ebca4acd0eac..ea7a8e7e30e1 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAccountView.java
@@ -321,5 +321,9 @@ public class KeyguardAccountView extends LinearLayout implements KeyguardSecurit
mSecurityMessageDisplay = display;
reset();
}
+
+ @Override
+ public void showUsabilityHint() {
+ }
}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardActivityLauncher.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardActivityLauncher.java
new file mode 100644
index 000000000000..d1033066500e
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardActivityLauncher.java
@@ -0,0 +1,175 @@
+/*
+ * 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.ActivityManagerNative;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.WindowManager;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import java.util.List;
+
+public abstract class KeyguardActivityLauncher {
+ private static final String TAG = KeyguardActivityLauncher.class.getSimpleName();
+ private static final boolean DEBUG = KeyguardHostView.DEBUG;
+ private static final String META_DATA_KEYGUARD_LAYOUT = "com.android.keyguard.layout";
+ private static final Intent SECURE_CAMERA_INTENT =
+ new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
+ .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ private static final Intent INSECURE_CAMERA_INTENT =
+ new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
+
+ abstract Context getContext();
+
+ abstract KeyguardSecurityCallback getCallback();
+
+ abstract LockPatternUtils getLockPatternUtils();
+
+ public static class CameraWidgetInfo {
+ public String contextPackage;
+ public int layoutId;
+ }
+
+ public CameraWidgetInfo getCameraWidgetInfo() {
+ CameraWidgetInfo info = new CameraWidgetInfo();
+ Intent intent = getCameraIntent();
+ PackageManager packageManager = getContext().getPackageManager();
+ final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
+ intent, PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser());
+ if (appList.size() == 0) {
+ if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): Nothing found");
+ return null;
+ }
+ ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
+ PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA,
+ getLockPatternUtils().getCurrentUser());
+ if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): resolved: " + resolved);
+ if (wouldLaunchResolverActivity(resolved, appList)) {
+ if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): Would launch resolver");
+ return info;
+ }
+ if (resolved == null || resolved.activityInfo == null) {
+ return null;
+ }
+ if (resolved.activityInfo.metaData == null || resolved.activityInfo.metaData.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): no metadata found");
+ return info;
+ }
+ int layoutId = resolved.activityInfo.metaData.getInt(META_DATA_KEYGUARD_LAYOUT);
+ if (layoutId == 0) {
+ if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): no layout specified");
+ return info;
+ }
+ info.contextPackage = resolved.activityInfo.packageName;
+ info.layoutId = layoutId;
+ return info;
+ }
+
+ public void launchCamera() {
+ LockPatternUtils lockPatternUtils = getLockPatternUtils();
+ if (lockPatternUtils.isSecure()) {
+ // Launch the secure version of the camera
+ if (wouldLaunchResolverActivity(SECURE_CAMERA_INTENT)) {
+ // TODO: Show disambiguation dialog instead.
+ // For now, we'll treat this like launching any other app from secure keyguard.
+ // When they do, user sees the system's ResolverActivity which lets them choose
+ // which secure camera to use.
+ launchActivity(SECURE_CAMERA_INTENT, false);
+ } else {
+ launchActivity(SECURE_CAMERA_INTENT, true);
+ }
+ } else {
+// wouldLaunchResolverActivity(new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA));
+ // Launch the normal camera
+ launchActivity(INSECURE_CAMERA_INTENT, false);
+ }
+ }
+
+ /**
+ * Launches the said intent for the current foreground user.
+ * @param intent
+ * @param showsWhileLocked true if the activity can be run on top of keyguard.
+ * See {@link WindowManager#FLAG_SHOW_WHEN_LOCKED}
+ */
+ public void launchActivity(final Intent intent, boolean showsWhileLocked) {
+ final Context context = getContext();
+ LockPatternUtils lockPatternUtils = getLockPatternUtils();
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_SINGLE_TOP
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ boolean isSecure = lockPatternUtils.isSecure();
+ if (!isSecure || showsWhileLocked) {
+ if (!isSecure) try {
+ ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
+ } catch (RemoteException e) {
+ Log.w(TAG, "can't dismiss keyguard on launch");
+ }
+ try {
+ context.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Activity not found for intent + " + intent.getAction());
+ }
+ } else {
+ // Create a runnable to start the activity and ask the user to enter their
+ // credentials.
+ KeyguardSecurityCallback callback = getCallback();
+ callback.setOnDismissRunnable(new Runnable() {
+ @Override
+ public void run() {
+ context.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ }
+ });
+ callback.dismiss(false);
+ }
+ }
+
+ private Intent getCameraIntent() {
+ return getLockPatternUtils().isSecure() ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
+ }
+
+ private boolean wouldLaunchResolverActivity(Intent intent) {
+ PackageManager packageManager = getContext().getPackageManager();
+ ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
+ PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser());
+ List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
+ intent, PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser());
+ return wouldLaunchResolverActivity(resolved, appList);
+ }
+
+ private boolean wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList) {
+ // If the list contains the above resolved activity, then it can't be
+ // ResolverActivity itself.
+ for (int i = 0; i < appList.size(); i++) {
+ ResolveInfo tmp = appList.get(i);
+ if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
+ && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
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
index 000000000000..79d0d12d97de
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardCircleFramedDrawable.java
@@ -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) {
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java
index 04ab8713eaa6..4aa6b05dc7b8 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardFaceUnlockView.java
@@ -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,33 @@ 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;
+ }
+
+ @Override
+ public void showUsabilityHint() {
}
}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
index b86e5b8564ca..d8d9ab99ca78 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
@@ -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;
@@ -47,7 +49,6 @@ import android.view.WindowManager;
import android.view.animation.AnimationUtils;
import android.widget.RemoteViews.OnClickHandler;
import android.widget.TextView;
-import android.widget.ViewFlipper;
import com.android.internal.R;
import com.android.internal.policy.impl.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -71,9 +72,8 @@ 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 KeyguardSecurityViewFlipper mSecurityViewContainer;
private KeyguardSelectorView mKeyguardSelectorView;
private KeyguardTransportControlView mTransportControl;
private boolean mEnableMenuKey;
@@ -87,6 +87,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;
@@ -101,6 +102,7 @@ public class KeyguardHostView extends KeyguardViewBase {
void hideSecurityView(int duration);
void showSecurityView();
void showUnlockHint();
+ void userActivity();
}
public KeyguardHostView(Context context) {
@@ -151,17 +153,37 @@ 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);
- mSecurityViewContainer = (ViewFlipper) findViewById(R.id.view_flipper);
- mKeyguardSelectorView = (KeyguardSelectorView) findViewById(R.id.keyguard_selector_view);
+ mAppWidgetContainer.setVisibility(VISIBLE);
+ mAppWidgetContainer.setCallbacks(mWidgetCallbacks);
+ mAppWidgetContainer.setMinScale(0.5f);
addDefaultWidgets();
+ maybePopulateWidgets();
+
+ mViewStateManager = new KeyguardViewStateManager();
+ SlidingChallengeLayout slider =
+ (SlidingChallengeLayout) findViewById(R.id.sliding_layout);
+ if (slider != null) {
+ slider.setOnChallengeScrolledListener(mViewStateManager);
+ }
+ mAppWidgetContainer.setViewStateManager(mViewStateManager);
+
+ mViewStateManager.setPagedView(mAppWidgetContainer);
+ mViewStateManager.setChallengeLayout(slider != null ? slider :
+ (ChallengeLayout) findViewById(R.id.multi_pane_challenge));
+ mSecurityViewContainer = (KeyguardSecurityViewFlipper) findViewById(R.id.view_flipper);
+ mKeyguardSelectorView = (KeyguardSelectorView) findViewById(R.id.keyguard_selector_view);
+ mViewStateManager.setSecurityViewContainer(mSecurityViewContainer);
+
+ mViewStateManager.showUsabilityHints();
+
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,22 +217,9 @@ 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);
}
- private void disableStatusViewInteraction() {
- // Disable all user interaction on status view. This is done to prevent falsing in the
- // pocket from triggering useractivity and prevents 3rd party replacement widgets
- // from responding to user interaction while in this position.
- View statusView = findViewById(R.id.keyguard_status_view);
- if (statusView instanceof KeyguardWidgetFrame) {
- ((KeyguardWidgetFrame) statusView).setDisableUserInteraction(true);
- }
- }
-
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
@@ -221,12 +230,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 +255,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 +326,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 +472,7 @@ public class KeyguardHostView extends KeyguardViewBase {
switch (mCurrentSecuritySelection) {
case Pattern:
case Password:
+ case PIN:
case Account:
case Biometric:
finish = true;
@@ -504,6 +512,8 @@ public class KeyguardHostView extends KeyguardViewBase {
if (mViewMediatorCallback != null) {
mViewMediatorCallback.keyguardDone(true);
}
+ } else {
+ mViewStateManager.showBouncer(true);
}
}
@@ -607,7 +617,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 +642,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);
}
}
@@ -706,6 +717,10 @@ public class KeyguardHostView extends KeyguardViewBase {
// layout is blank but forcing a layout causes it to reappear (e.g. with with
// hierarchyviewer).
requestLayout();
+
+ if (mViewStateManager != null) {
+ mViewStateManager.showUsabilityHints();
+ }
}
@Override
@@ -717,7 +732,8 @@ public class KeyguardHostView extends KeyguardViewBase {
@Override
public void show() {
- onScreenTurnedOn();
+ if (DEBUG) Log.d(TAG, "show()");
+ showPrimarySecurityScreen(false);
}
private boolean isSecure() {
@@ -726,6 +742,7 @@ public class KeyguardHostView extends KeyguardViewBase {
case Pattern:
return mLockPatternUtils.isLockPatternEnabled();
case Password:
+ case PIN:
return mLockPatternUtils.isLockPasswordEnabled();
case SimPin:
case SimPuk:
@@ -760,6 +777,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 +794,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 +808,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,23 +819,96 @@ 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);
}
}
+ private final CameraWidgetFrame.Callbacks mCameraWidgetCallbacks =
+ new CameraWidgetFrame.Callbacks() {
+ @Override
+ public void onLaunchingCamera() {
+ SlidingChallengeLayout slider = locateSlider();
+ if (slider != null) {
+ slider.showHandle(false);
+ }
+ }
+
+ @Override
+ public void onCameraLaunched() {
+ SlidingChallengeLayout slider = locateSlider();
+ if (slider != null) {
+ slider.showHandle(true);
+ }
+ mAppWidgetContainer.scrollLeft();
+ }
+
+ private SlidingChallengeLayout locateSlider() {
+ return (SlidingChallengeLayout) findViewById(R.id.sliding_layout);
+ }
+ };
+
+ private final KeyguardActivityLauncher mActivityLauncher = new KeyguardActivityLauncher() {
+ @Override
+ Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ KeyguardSecurityCallback getCallback() {
+ return mCallback;
+ }
+
+ @Override
+ LockPatternUtils getLockPatternUtils() {
+ return mLockPatternUtils;
+ }};
+
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);
- inflateAndAddUserSelectorWidgetIfNecessary();
+ 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);
+ if (mContext.getResources().getBoolean(R.bool.kg_enable_camera_default_widget)) {
+ View cameraWidget =
+ CameraWidgetFrame.create(mContext, mCameraWidgetCallbacks, mActivityLauncher);
+ if (cameraWidget != null) {
+ 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);
+ }
+ });
+
+ enableUserSelectorIfNecessary();
initializeTransportControl();
}
@@ -874,27 +967,25 @@ 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++) {
- if (widgets[i] != -1) {
- addWidget(widgets[i]);
+ final int[] widgets = mLockPatternUtils.getAppWidgets();
+ if (widgets == null) {
+ Log.d(TAG, "Problem reading widgets");
+ } else {
+ for (int i = widgets.length -1; i >= 0; i--) {
+ if (widgets[i] != -1) {
+ // 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);
+ }
}
}
}
@@ -982,22 +1073,17 @@ public class KeyguardHostView extends KeyguardViewBase {
mAppWidgetContainer.setCurrentPage(pageToShow);
}
- private void inflateAndAddUserSelectorWidgetIfNecessary() {
+ private void enableUserSelectorIfNecessary() {
// if there are multiple users, we need to add the multi-user switcher widget to the
// keyguard.
UserManager mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
List<UserInfo> users = mUm.getUsers(true);
if (users.size() > 1) {
- KeyguardWidgetFrame userSwitcher = (KeyguardWidgetFrame)
- LayoutInflater.from(mContext).inflate(R.layout.keyguard_multi_user_selector_widget,
- mAppWidgetContainer, false);
-
- // add the switcher in the first position
- mAppWidgetContainer.addView(userSwitcher, getWidgetPosition(R.id.keyguard_status_view));
- KeyguardMultiUserSelectorView multiUser = (KeyguardMultiUserSelectorView)
- userSwitcher.getChildAt(0);
-
+ KeyguardMultiUserSelectorView multiUser =
+ (KeyguardMultiUserSelectorView) findViewById(R.id.keyguard_user_selector);
+ multiUser.setVisibility(View.VISIBLE);
+ multiUser.addUsers(mUm.getUsers(true));
UserSwitcherCallback callback = new UserSwitcherCallback() {
@Override
public void hideSecurityView(int duration) {
@@ -1012,10 +1098,16 @@ public class KeyguardHostView extends KeyguardViewBase {
@Override
public void showUnlockHint() {
if (mKeyguardSelectorView != null) {
- mKeyguardSelectorView.ping();
+ mKeyguardSelectorView.showUsabilityHint();
}
}
+ @Override
+ public void userActivity() {
+ if (mViewMediatorCallback != null) {
+ mViewMediatorCallback.userActivity();
+ }
+ }
};
multiUser.setCallback(callback);
}
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
index 000000000000..0fc54cdfbcaf
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardLinearLayout.java
@@ -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();
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserAvatar.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserAvatar.java
index 3c972bc40d41..8b7c7c8bbad7 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserAvatar.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserAvatar.java
@@ -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;
@@ -34,23 +38,38 @@ import android.widget.TextView;
import com.android.internal.R;
class KeyguardMultiUserAvatar extends FrameLayout {
+ private static final String TAG = KeyguardMultiUserAvatar.class.getSimpleName();
+ private static final boolean DEBUG = KeyguardHostView.DEBUG;
private ImageView mUserImage;
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 +92,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 +124,52 @@ 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));
+ Bitmap icon = null;
+ try {
+ icon = BitmapFactory.decodeFile(rewriteIconPath(user.iconPath));
+ } catch (Exception e) {
+ if (DEBUG) Log.d(TAG, "failed to open profile icon " + user.iconPath, e);
+ }
+
+ if (icon == null) {
+ icon = BitmapFactory.decodeResource(mContext.getResources(),
+ com.android.internal.R.drawable.ic_contact_picture);
+ }
+
+ mFramed = new KeyguardCircleFramedDrawable(icon, (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 +177,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 +195,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() {
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserSelectorView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserSelectorView.java
index 246c25587f83..35d177adc58d 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserSelectorView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserSelectorView.java
@@ -23,23 +23,24 @@ import android.os.RemoteException;
import android.os.UserManager;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
-
import com.android.internal.R;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
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) {
@@ -55,19 +56,18 @@ public class KeyguardMultiUserSelectorView extends FrameLayout implements View.O
}
protected void onFinishInflate () {
- init();
+ mUsersGrid = (ViewGroup) findViewById(R.id.keyguard_users_grid);
+ mUsersGrid.removeAllViews();
+ setClipChildren(false);
+ setClipToPadding(false);
+
}
public void setCallback(KeyguardHostView.UserSwitcherCallback callback) {
mCallback = callback;
}
- public void init() {
- mUsersGrid = (KeyguardSubdivisionLayout) findViewById(R.id.keyguard_users_grid);
- mUsersGrid.removeAllViews();
- setClipChildren(false);
- setClipToPadding(false);
-
+ public void addUsers(Collection<UserInfo> userList) {
UserInfo activeUser;
try {
activeUser = ActivityManagerNative.getDefault().getCurrentUser();
@@ -75,17 +75,18 @@ public class KeyguardMultiUserSelectorView extends FrameLayout implements View.O
activeUser = null;
}
- UserManager mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUm.getUsers(true));
+ ArrayList<UserInfo> users = new ArrayList<UserInfo>(userList);
Collections.sort(users, mOrderAddedComparator);
for (UserInfo user: users) {
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>() {
@@ -103,6 +104,14 @@ public class KeyguardMultiUserSelectorView extends FrameLayout implements View.O
}
@Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if(event.getActionMasked() != MotionEvent.ACTION_CANCEL && mCallback != null) {
+ mCallback.userActivity();
+ }
+ return false;
+ }
+
+ @Override
public void onClick(View v) {
if (!(v instanceof KeyguardMultiUserAvatar)) return;
final KeyguardMultiUserAvatar avatar = (KeyguardMultiUserAvatar) v;
@@ -112,16 +121,24 @@ 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
index 000000000000..fd3d88b6dd9d
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java
@@ -0,0 +1,129 @@
+/*
+ * 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) {
+ doHapticKeyClick();
+ 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) {
+ CharSequence str = mPasswordEntry.getText();
+ if (str.length() > 0) {
+ mPasswordEntry.setText(str.subSequence(0, str.length()-1));
+ }
+ doHapticKeyClick();
+ }
+ });
+ pinDelete.setOnLongClickListener(new View.OnLongClickListener() {
+ public boolean onLongClick(View v) {
+ mPasswordEntry.setText("");
+ doHapticKeyClick();
+ return true;
+ }
+ });
+ }
+
+ mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance());
+ mPasswordEntry.setInputType(InputType.TYPE_CLASS_NUMBER
+ | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+
+ mPasswordEntry.requestFocus();
+ }
+
+ @Override
+ public void showUsabilityHint() {
+ }
+} \ No newline at end of file
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java
index be2dc8ffb28e..9b5f00addaa9 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java
@@ -38,7 +38,6 @@ import android.text.TextWatcher;
import android.text.method.DigitsKeyListener;
import android.text.method.TextKeyListener;
import android.view.KeyEvent;
-import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
@@ -50,36 +49,14 @@ import android.widget.TextView.OnEditorActionListener;
import com.android.internal.widget.PasswordEntryKeyboardHelper;
/**
- * Displays a dialer-like interface or alphanumeric (latin-1) key entry for the user to enter
+ * Displays an alphanumeric (latin-1) key entry for the user to enter
* an unlock password
*/
-public class KeyguardPasswordView extends LinearLayout
+public class KeyguardPasswordView extends KeyguardAbsKeyInputView
implements KeyguardSecurityView, OnEditorActionListener, TextWatcher {
- /** Delay in ms between updates to the "too many attempts" count down. */
- private static final long LOCKOUT_INTERVAL = 1000;
- /**
- * Delay in ms between updates to the "too many attempts" count down used
- * when accessibility is turned on. Less annoying than the shorter default
- * {@link #LOCKOUT_INTERVAL}.
- */
- private static final long ACCESSIBILITY_LOCKOUT_INTERVAL = 10000;
-
- 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;
+ InputMethodManager mImm;
public KeyguardPasswordView(Context context) {
super(context);
@@ -89,110 +66,40 @@ public class KeyguardPasswordView extends LinearLayout
super(context, attrs);
}
- public void setKeyguardCallback(KeyguardSecurityCallback callback) {
- mCallback = callback;
- }
-
- public void setLockPatternUtils(LockPatternUtils utils) {
- mLockPatternUtils = utils;
+ protected void resetState() {
+ mSecurityMessageDisplay.setMessage(R.string.kg_password_instructions, false);
+ mPasswordEntry.setEnabled(true);
}
@Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- if (hasWindowFocus) {
- reset();
- }
+ public boolean needsInput() {
+ return true;
}
- 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();
- }
+ @Override
+ public void onResume() {
+ super.onResume();
+ mImm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
- private void resetState() {
- mSecurityMessageDisplay.setMessage(
- mIsAlpha ? R.string.kg_password_instructions : R.string.kg_pin_instructions, false);
- mPasswordEntry.setEnabled(true);
- mKeyboardView.setEnabled(true);
+ @Override
+ public void onPause() {
+ super.onPause();
+ mImm.hideSoftInputFromWindow(getWindowToken(), 0);
}
@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
-
- final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality();
- mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == quality
- || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == quality
- || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == quality;
-
- mKeyboardView = (PasswordEntryKeyboardView) findViewById(R.id.keyboard);
- mPasswordEntry = (EditText) findViewById(R.id.passwordEntry);
- mPasswordEntry.setOnEditorActionListener(this);
- mPasswordEntry.addTextChangedListener(this);
-
- mKeyboardHelper = new PasswordEntryKeyboardHelper(mContext, mKeyboardView, this, false,
- new int[] {
- R.xml.kg_password_kbd_numeric,
- com.android.internal.R.xml.password_kbd_qwerty,
- com.android.internal.R.xml.password_kbd_qwerty_shifted,
- com.android.internal.R.xml.password_kbd_symbols,
- com.android.internal.R.xml.password_kbd_symbols_shift
- }
- );
- mKeyboardHelper.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled());
+ super.onFinishInflate();
boolean imeOrDeleteButtonVisible = false;
- if (mIsAlpha) {
- // We always use the system IME for alpha keyboard, so hide lockscreen's soft keyboard
- mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA);
- mKeyboardView.setVisibility(View.GONE);
- } else {
- mKeyboardHelper.setKeyboardMode(PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
-
- // Use lockscreen's numeric keyboard if the physical keyboard isn't showing
- boolean hardKeyboardVisible = getResources().getConfiguration().hardKeyboardHidden
- == Configuration.HARDKEYBOARDHIDDEN_NO;
- mKeyboardView.setVisibility(
- (ENABLE_HIDE_KEYBOARD && hardKeyboardVisible) ? View.INVISIBLE : View.VISIBLE);
-
- // The delete button is of the PIN keyboard itself in some (e.g. tablet) layouts,
- // not a separate view
- View pinDelete = findViewById(R.id.delete_button);
- if (pinDelete != null) {
- pinDelete.setVisibility(View.VISIBLE);
- imeOrDeleteButtonVisible = true;
- pinDelete.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- mKeyboardHelper.handleBackspace();
- }
- });
- }
- }
- mPasswordEntry.requestFocus();
+ mImm = (InputMethodManager) getContext().getSystemService(
+ Context.INPUT_METHOD_SERVICE);
- // This allows keyboards with overlapping qwerty/numeric keys to choose just numeric keys.
- if (mIsAlpha) {
- mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
- mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
- | InputType.TYPE_TEXT_VARIATION_PASSWORD);
- } else {
- mPasswordEntry.setKeyListener(DigitsKeyListener.getInstance());
- mPasswordEntry.setInputType(InputType.TYPE_CLASS_NUMBER
- | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
- }
+ mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
+ mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
+ | InputType.TYPE_TEXT_VARIATION_PASSWORD);
// Poke the wakelock any time the text is selected or modified
mPasswordEntry.setOnClickListener(new OnClickListener() {
@@ -215,17 +122,17 @@ 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(
- Context.INPUT_METHOD_SERVICE);
- if (mIsAlpha && switchImeButton != null && hasMultipleEnabledIMEsOrSubtypes(imm, false)) {
+ if (switchImeButton != null && hasMultipleEnabledIMEsOrSubtypes(mImm, false)) {
switchImeButton.setVisibility(View.VISIBLE);
imeOrDeleteButtonVisible = true;
switchImeButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mCallback.userActivity(0); // Leave the screen on a bit longer
- imm.showInputMethodPicker();
+ mImm.showInputMethodPicker();
}
});
}
@@ -291,113 +198,6 @@ public class KeyguardPasswordView extends LinearLayout
}
@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();
- final AccessibilityManager accessManager =
- (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
- // Use a longer update interval when accessibility is turned on.
- final long interval = accessManager.isEnabled() ? ACCESSIBILITY_LOCKOUT_INTERVAL
- : LOCKOUT_INTERVAL;
- new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, interval) {
-
- @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();
+ public void showUsabilityHint() {
}
}
-
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java
index dcf40bf966d0..408a9c8c22af 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPatternView.java
@@ -200,6 +200,10 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
}
+ @Override
+ public void showUsabilityHint() {
+ }
+
/** TODO: hook this up */
public void cleanUp() {
if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);
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
index 000000000000..f6a90c5941bc
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityContainer.java
@@ -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);
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
index 59e2ca93ba30..7a69586a91fa 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityModel.java
@@ -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
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java
index 19bcae9466d2..c3684c4ab9ef 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityView.java
@@ -62,4 +62,6 @@ public interface KeyguardSecurityView {
KeyguardSecurityCallback getCallback();
void setSecurityMessageDisplay(SecurityMessageDisplay display);
+
+ void showUsabilityHint();
}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityViewFlipper.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityViewFlipper.java
index c4e1607f4cdd..9cdbc2de203e 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityViewFlipper.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSecurityViewFlipper.java
@@ -21,14 +21,17 @@ import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
+import android.view.WindowManager;
import android.widget.ViewFlipper;
+import com.android.internal.widget.LockPatternUtils;
+
/**
* Subclass of the current view flipper that allows us to overload dispatchTouchEvent() so
* we can emulate {@link WindowManager.LayoutParams#FLAG_SLIPPERY} within a view hierarchy.
*
*/
-public class KeyguardSecurityViewFlipper extends ViewFlipper {
+public class KeyguardSecurityViewFlipper extends ViewFlipper implements KeyguardSecurityView {
private Rect mTempRect = new Rect();
public KeyguardSecurityViewFlipper(Context context) {
@@ -55,4 +58,79 @@ public class KeyguardSecurityViewFlipper extends ViewFlipper {
return result;
}
+ KeyguardSecurityView getSecurityView() {
+ View child = getChildAt(getDisplayedChild());
+ if (child instanceof KeyguardSecurityView) {
+ return (KeyguardSecurityView) child;
+ }
+ return null;
+ }
+
+ @Override
+ public void setKeyguardCallback(KeyguardSecurityCallback callback) {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.setKeyguardCallback(callback);
+ }
+ }
+
+ @Override
+ public void setLockPatternUtils(LockPatternUtils utils) {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.setLockPatternUtils(utils);
+ }
+ }
+
+ @Override
+ public void reset() {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.reset();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.onPause();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.onResume();
+ }
+ }
+
+ @Override
+ public boolean needsInput() {
+ KeyguardSecurityView ksv = getSecurityView();
+ return (ksv != null) ? ksv.needsInput() : false;
+ }
+
+ @Override
+ public KeyguardSecurityCallback getCallback() {
+ KeyguardSecurityView ksv = getSecurityView();
+ return (ksv != null) ? ksv.getCallback() : null;
+ }
+
+ @Override
+ public void setSecurityMessageDisplay(SecurityMessageDisplay display) {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.setSecurityMessageDisplay(display);
+ }
+ }
+
+ @Override
+ public void showUsabilityHint() {
+ KeyguardSecurityView ksv = getSecurityView();
+ if (ksv != null) {
+ ksv.showUsabilityHint();
+ }
+ }
}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java
index 1d26def19693..eba9a76d0309 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSelectorView.java
@@ -16,18 +16,12 @@
package com.android.internal.policy.impl.keyguard;
import android.animation.ObjectAnimator;
-import android.app.ActivityManagerNative;
import android.app.SearchManager;
import android.app.admin.DevicePolicyManager;
-import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.RemoteException;
import android.os.UserHandle;
-import android.provider.MediaStore;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
@@ -41,8 +35,6 @@ import com.android.internal.widget.multiwaveview.GlowPadView;
import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener;
import com.android.internal.R;
-import java.util.List;
-
public class KeyguardSelectorView extends LinearLayout implements KeyguardSecurityView {
private static final boolean DEBUG = KeyguardHostView.DEBUG;
private static final String TAG = "SecuritySelectorView";
@@ -67,7 +59,7 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
.getAssistIntent(mContext, UserHandle.USER_CURRENT);
if (assistIntent != null) {
- launchActivity(assistIntent, false);
+ mActivityLauncher.launchActivity(assistIntent, false);
} else {
Log.w(TAG, "Failed to get intent for assist activity");
}
@@ -75,7 +67,7 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
break;
case com.android.internal.R.drawable.ic_lockscreen_camera:
- launchCamera();
+ mActivityLauncher.launchCamera();
mCallback.userActivity(0);
break;
@@ -119,47 +111,25 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
}
};
- public KeyguardSelectorView(Context context) {
- this(context, null);
- }
+ private final KeyguardActivityLauncher mActivityLauncher = new KeyguardActivityLauncher() {
- private boolean wouldLaunchResolverActivity(Intent intent) {
- PackageManager packageManager = mContext.getPackageManager();
- ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
- PackageManager.MATCH_DEFAULT_ONLY, mLockPatternUtils.getCurrentUser());
- final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
- intent, PackageManager.MATCH_DEFAULT_ONLY, mLockPatternUtils.getCurrentUser());
- // If the list contains the above resolved activity, then it can't be
- // ResolverActivity itself.
- for (int i = 0; i < appList.size(); i++) {
- ResolveInfo tmp = appList.get(i);
- if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
- && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
- return false;
- }
+ @Override
+ KeyguardSecurityCallback getCallback() {
+ return mCallback;
}
- return true;
- }
- protected void launchCamera() {
- if (mLockPatternUtils.isSecure()) {
- // Launch the secure version of the camera
- final Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE);
- intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-
- if (wouldLaunchResolverActivity(intent)) {
- // TODO: Show disambiguation dialog instead.
- // For now, we'll treat this like launching any other app from secure keyguard.
- // When they do, user sees the system's ResolverActivity which lets them choose
- // which secure camera to use.
- launchActivity(intent, false);
- } else {
- launchActivity(intent, true);
- }
- } else {
- // Launch the normal camera
- launchActivity(new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA), false);
+ @Override
+ LockPatternUtils getLockPatternUtils() {
+ return mLockPatternUtils;
}
+
+ @Override
+ Context getContext() {
+ return mContext;
+ }};
+
+ public KeyguardSelectorView(Context context) {
+ this(context, null);
}
public KeyguardSelectorView(Context context, AttributeSet attrs) {
@@ -183,7 +153,8 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
return mGlowPadView.getTargetPosition(resId) != -1;
}
- public void ping() {
+ @Override
+ public void showUsabilityHint() {
mGlowPadView.ping();
}
@@ -266,42 +237,6 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri
mLockPatternUtils = utils;
}
- /**
- * Launches the said intent for the current foreground user.
- * @param intent
- * @param showsWhileLocked true if the activity can be run on top of keyguard.
- * See {@link WindowManager#FLAG_SHOW_WHEN_LOCKED}
- */
- private void launchActivity(final Intent intent, boolean showsWhileLocked) {
- intent.addFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_SINGLE_TOP
- | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- boolean isSecure = mLockPatternUtils.isSecure();
- if (!isSecure || showsWhileLocked) {
- if (!isSecure) try {
- ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
- } catch (RemoteException e) {
- Log.w(TAG, "can't dismiss keyguard on launch");
- }
- try {
- mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "Activity not found for intent + " + intent.getAction());
- }
- } else {
- // Create a runnable to start the activity and ask the user to enter their
- // credentials.
- mCallback.setOnDismissRunnable(new Runnable() {
- @Override
- public void run() {
- mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
- }
- });
- mCallback.dismiss(false);
- }
- }
-
@Override
public void reset() {
mGlowPadView.reset(false);
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java
index 7878e461313a..018a1aa752e0 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPinView.java
@@ -121,6 +121,10 @@ public class KeyguardSimPinView extends LinearLayout
mPinEntry.requestFocus();
}
+ @Override
+ public void showUsabilityHint() {
+ }
+
/** {@inheritDoc} */
public void cleanUp() {
// dismiss the dialog.
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java
index 562d8931e2a3..d0585b99f6ea 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSimPukView.java
@@ -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;
@@ -169,6 +170,10 @@ public class KeyguardSimPukView extends LinearLayout implements View.OnClickList
reset();
}
+ @Override
+ public void showUsabilityHint() {
+ }
+
/** {@inheritDoc} */
public void cleanUp() {
// dismiss the dialog.
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java
index 5b85064fdabd..b6985bdc8159 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java
@@ -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
index 1cd796c8a994..000000000000
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardSubdivisionLayout.java
+++ /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
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
index d8e1c1a94ed4..316825a241c8 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitor.java
@@ -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();
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java
index 3d65e6817239..8c9ac8ba5126 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardUpdateMonitorCallback.java
@@ -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.
*/
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java
index 3191f4a1df24..9e3424df06e8 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewBase.java
@@ -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(
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
index b66c8833b0b7..452fbca66327 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
@@ -233,7 +233,6 @@ public class KeyguardViewManager {
if (mScreenOn) {
mKeyguardView.show();
- mKeyguardView.requestFocus();
}
}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
index ceb032559c57..f76af41853a4 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java
@@ -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);
@@ -1321,7 +1324,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
index 000000000000..c163b975fbc9
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewStateManager.java
@@ -0,0 +1,168 @@
+/*
+ * 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.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.View;
+
+public class KeyguardViewStateManager implements SlidingChallengeLayout.OnChallengeScrolledListener {
+
+ private KeyguardWidgetPager mPagedView;
+ private int mCurrentPageIndex;
+ private ChallengeLayout mChallengeLayout;
+ private Runnable mHideHintsRunnable;
+ private KeyguardSecurityView mKeyguardSecurityContainer;
+ private int[] mTmpPoint = new int[2];
+ private static final int SCREEN_ON_HINT_DURATION = 1000;
+ Handler mMainQueue = new Handler(Looper.myLooper());
+
+ int mChallengeTop = 0;
+
+ public KeyguardViewStateManager() {
+ }
+
+ public void setPagedView(KeyguardWidgetPager pagedView) {
+ mPagedView = pagedView;
+ }
+
+ public void setChallengeLayout(ChallengeLayout layout) {
+ mChallengeLayout = layout;
+ }
+
+ public void setSecurityViewContainer(KeyguardSecurityView container) {
+ mKeyguardSecurityContainer = container;
+ }
+
+ public void onPageBeginMoving() {
+ if (mChallengeLayout.isChallengeShowing()) {
+ mChallengeLayout.showChallenge(false);
+ }
+ if (mHideHintsRunnable != null) {
+ mMainQueue.removeCallbacks(mHideHintsRunnable);
+ mHideHintsRunnable = null;
+ }
+ }
+
+ public void onPageEndMoving() {
+ }
+
+ public void showBouncer(boolean show) {
+ mChallengeLayout.showBouncer();
+ }
+
+ 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 (mChallengeLayout.isChallengeOverlapping()) {
+ sizeWidgetFrameToChallengeTop(newCurPage);
+ }
+ }
+ mCurrentPageIndex = newPageIndex;
+ }
+
+ private void sizeWidgetFrameToChallengeTop(KeyguardWidgetFrame frame) {
+ if (frame == null) return;
+ mTmpPoint[0] = 0;
+ mTmpPoint[1] = mChallengeTop;
+ mapPoint((View) mChallengeLayout, 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 challengeOverlapping = mChallengeLayout.isChallengeOverlapping();
+ int curPage = mPagedView.getCurrentPage();
+ KeyguardWidgetFrame frame = mPagedView.getWidgetPageAt(curPage);
+
+ if (frame != null) {
+ if (!challengeOverlapping) {
+ frame.resetSize();
+ } else {
+ sizeWidgetFrameToChallengeTop(frame);
+ }
+ }
+
+ if (challengeOverlapping) {
+ mPagedView.setOnlyAllowEdgeSwipes(true);
+ } else {
+ mPagedView.setOnlyAllowEdgeSwipes(false);
+ }
+
+ if (mChallengeLayout.isChallengeShowing()) {
+ mKeyguardSecurityContainer.onResume();
+ } else {
+ mKeyguardSecurityContainer.onPause();
+ }
+ } else {
+ // View is on the move. Pause the security view until it completes.
+ mKeyguardSecurityContainer.onPause();
+ }
+ }
+
+ public void showUsabilityHints() {
+ mKeyguardSecurityContainer.showUsabilityHint();
+ mPagedView.showInitialPageHints();
+ mHideHintsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mPagedView.hideOutlinesAndSidePages();
+ mHideHintsRunnable = null;
+ }
+ };
+
+ mMainQueue.postDelayed(mHideHintsRunnable, SCREEN_ON_HINT_DURATION);
+ }
+
+ @Override
+ public void onScrollPositionChanged(float scrollPosition, int challengeTop) {
+ mChallengeTop = challengeTop;
+ }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java
index 311eec68bc59..69ea6d57060b 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java
@@ -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;
@@ -48,8 +49,12 @@ public class KeyguardWidgetFrame extends FrameLayout {
private float mOverScrollAmount = 0f;
private final Rect mForegroundRect = new Rect();
private int mForegroundAlpha = 0;
- private PowerManager mPowerManager;
- private boolean mDisableInteraction;
+ private CheckLongPressHelper mLongPressHelper;
+
+ private float mBackgroundAlpha;
+ private float mBackgroundAlphaMultiplier = 1.0f;
+ private Drawable mBackgroundDrawable;
+ private Rect mBackgroundRect = new Rect();
public KeyguardWidgetFrame(Context context) {
this(context, null, 0);
@@ -62,35 +67,106 @@ public class KeyguardWidgetFrame extends FrameLayout {
public KeyguardWidgetFrame(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mLongPressHelper = new CheckLongPressHelper(this);
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);
}
- public void setDisableUserInteraction(boolean disabled) {
- mDisableInteraction = disabled;
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // Watch for longpress events at this level to make sure
+ // users can always pick up this widget
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mLongPressHelper.postCheckForLongPress(ev);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ mLongPressHelper.onMove(ev);
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mLongPressHelper.cancelLongPress();
+ break;
+ }
+
+ // Otherwise continue letting touch events fall through to children
+ return false;
}
@Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (!mDisableInteraction) {
- mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
- return super.onInterceptTouchEvent(ev);
+ public boolean onTouchEvent(MotionEvent ev) {
+ // Watch for longpress events at this level to make sure
+ // users can always pick up this widget
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_MOVE:
+ mLongPressHelper.onMove(ev);
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mLongPressHelper.cancelLongPress();
+ break;
}
+
+ // We return true here to ensure that we will get cancel / up signal
+ // even if none of our children have requested touch.
return true;
}
@Override
+ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ super.requestDisallowInterceptTouchEvent(disallowIntercept);
+ cancelLongPress();
+ }
+
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+ mLongPressHelper.cancelLongPress();
+ }
+
+ @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) {
@@ -99,6 +175,81 @@ public class KeyguardWidgetFrame extends FrameLayout {
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();
+ }
+ }
+
+ public void setContentAlpha(float alpha) {
+ View content = getContent();
+ if (content != null) {
+ content.setAlpha(alpha);
+ }
+ }
+
+ /**
+ * 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 +261,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) {
@@ -120,4 +272,8 @@ public class KeyguardWidgetFrame extends FrameLayout {
invalidate();
}
}
+
+ public void onActive(boolean isActive) {
+ // hook for subclasses
+ }
}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java
index 1e65665b2af7..868b86712fbb 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java
@@ -15,6 +15,8 @@
*/
package com.android.internal.policy.impl.keyguard;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
@@ -22,14 +24,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 +43,22 @@ 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 float mSidePagesAlpha = 1f;
+
+ 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 +73,171 @@ 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) {
+ int oldPageIndex = mPage;
+ mPage = newPageIndex;
+ if (mCallbacks != null) {
+ mCallbacks.onUserActivityTimeoutChanged();
+ mCallbacks.userActivity();
+ }
+ KeyguardWidgetFrame oldWidgetPage = getWidgetPageAt(oldPageIndex);
+ if (oldWidgetPage != null) {
+ oldWidgetPage.onActive(false);
+ }
+ KeyguardWidgetFrame newWidgetPage = getWidgetPageAt(newPageIndex);
+ if (newWidgetPage != null) {
+ newWidgetPage.onActive(true);
+ }
+ }
+ 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();
}
+ showOutlinesAndSidePages();
}
@Override
@@ -91,8 +245,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();
+ }
+ hideOutlinesAndSidePages();
}
/*
@@ -118,7 +277,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 +294,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(false)) {
+ 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 +358,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 +369,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 +397,101 @@ public class KeyguardWidgetPager extends PagedView {
}
}
}
+
+ @Override
+ protected void onStartReordering() {
+ super.onStartReordering();
+ setChildrenOutlineMultiplier(1.0f);
+ showOutlinesAndSidePages();
+ }
+
+ @Override
+ protected void onEndReordering() {
+ super.onEndReordering();
+ hideOutlinesAndSidePages();
+ }
+
+ void showOutlinesAndSidePages() {
+ if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
+ if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
+
+ PropertyValuesHolder outlinesAlpha =
+ PropertyValuesHolder.ofFloat("childrenOutlineAlpha", 1.0f);
+ PropertyValuesHolder sidePagesAlpha = PropertyValuesHolder.ofFloat("sidePagesAlpha", 1.0f);
+ mChildrenOutlineFadeInAnimation =
+ ObjectAnimator.ofPropertyValuesHolder(this, outlinesAlpha, sidePagesAlpha);
+
+ mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
+ mChildrenOutlineFadeInAnimation.start();
+ }
+
+ public void showInitialPageHints() {
+ // We start with everything showing
+ setChildrenOutlineAlpha(1.0f);
+ setSidePagesAlpha(1.0f);
+ setChildrenOutlineMultiplier(1.0f);
+
+ int currPage = getCurrentPage();
+ KeyguardWidgetFrame frame = getWidgetPageAt(currPage);
+ frame.setBackgroundAlphaMultiplier(0f);
+ }
+
+ void hideOutlinesAndSidePages() {
+ if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
+ if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
+
+ PropertyValuesHolder outlinesAlpha =
+ PropertyValuesHolder.ofFloat("childrenOutlineAlpha", 0f);
+ PropertyValuesHolder sidePagesAlpha = PropertyValuesHolder.ofFloat("sidePagesAlpha", 0f);
+ mChildrenOutlineFadeOutAnimation =
+ ObjectAnimator.ofPropertyValuesHolder(this, outlinesAlpha, sidePagesAlpha);
+
+ 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 setSidePagesAlpha(float alpha) {
+ // This gives the current page, or the destination page if in transit.
+ int curPage = getNextPage();
+ mSidePagesAlpha = alpha;
+ for (int i = 0; i < getChildCount(); i++) {
+ if (curPage != i) {
+ getWidgetPageAt(i).setContentAlpha(alpha);
+ } else {
+ // We lock the current page alpha to 1.
+ getWidgetPageAt(i).setContentAlpha(1.0f);
+ }
+ }
+ }
+
+ public void setChildrenOutlineMultiplier(float alpha) {
+ mChildrenOutlineAlpha = alpha;
+ for (int i = 0; i < getChildCount(); i++) {
+ getWidgetPageAt(i).setBackgroundAlphaMultiplier(alpha);
+ }
+ }
+
+ public float getSidePagesAlpha() {
+ return mSidePagesAlpha;
+ }
+
+ public float getChildrenOutlineAlpha() {
+ return mChildrenOutlineAlpha;
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ if (startReordering()) {
+ return true;
+ }
+ return false;
+ }
}
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
index 4ff6f27302ce..000000000000
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetRegion.java
+++ /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/MultiPaneChallengeLayout.java b/policy/src/com/android/internal/policy/impl/keyguard/MultiPaneChallengeLayout.java
new file mode 100644
index 000000000000..b6cada83e0ac
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/MultiPaneChallengeLayout.java
@@ -0,0 +1,486 @@
+/*
+ * 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.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.internal.R;
+
+public class MultiPaneChallengeLayout extends ViewGroup implements ChallengeLayout {
+ private static final String TAG = "MultiPaneChallengeLayout";
+
+ final int mOrientation;
+ private boolean mIsBouncing;
+
+ public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
+ public static final int VERTICAL = LinearLayout.VERTICAL;
+
+ private View mChallengeView;
+ private View mUserSwitcherView;
+ private View mScrimView;
+ private OnBouncerStateChangedListener mBouncerListener;
+
+ private final Rect mTempRect = new Rect();
+
+ private final OnClickListener mScrimClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideBouncer();
+ }
+ };
+
+ public MultiPaneChallengeLayout(Context context) {
+ this(context, null);
+ }
+
+ public MultiPaneChallengeLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public MultiPaneChallengeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.MultiPaneChallengeLayout, defStyleAttr, 0);
+ mOrientation = a.getInt(R.styleable.MultiPaneChallengeLayout_orientation,
+ HORIZONTAL);
+ a.recycle();
+ }
+
+ @Override
+ public boolean isChallengeShowing() {
+ return true;
+ }
+
+ @Override
+ public boolean isChallengeOverlapping() {
+ return false;
+ }
+
+ @Override
+ public void showChallenge(boolean b) {
+ }
+
+ @Override
+ public void showBouncer() {
+ if (mIsBouncing) return;
+ mIsBouncing = true;
+ if (mScrimView != null) {
+ mScrimView.setVisibility(GONE);
+ }
+ if (mBouncerListener != null) {
+ mBouncerListener.onBouncerStateChanged(true);
+ }
+ }
+
+ @Override
+ public void hideBouncer() {
+ if (!mIsBouncing) return;
+ mIsBouncing = false;
+ if (mScrimView != null) {
+ mScrimView.setVisibility(GONE);
+ }
+ if (mBouncerListener != null) {
+ mBouncerListener.onBouncerStateChanged(false);
+ }
+ }
+
+ @Override
+ public boolean isBouncing() {
+ return mIsBouncing;
+ }
+
+ @Override
+ public void setOnBouncerStateChangedListener(OnBouncerStateChangedListener listener) {
+ mBouncerListener = listener;
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ if (mIsBouncing && child != mChallengeView) {
+ // Clear out of the bouncer if the user tries to move focus outside of
+ // the security challenge view.
+ hideBouncer();
+ }
+ super.requestChildFocus(child, focused);
+ }
+
+ void setScrimView(View scrim) {
+ if (mScrimView != null) {
+ mScrimView.setOnClickListener(null);
+ }
+ mScrimView = scrim;
+ mScrimView.setVisibility(mIsBouncing ? VISIBLE : GONE);
+ mScrimView.setFocusable(true);
+ mScrimView.setOnClickListener(mScrimClickListener);
+ }
+
+ @Override
+ protected void onMeasure(int widthSpec, int heightSpec) {
+ if (MeasureSpec.getMode(widthSpec) != MeasureSpec.EXACTLY ||
+ MeasureSpec.getMode(heightSpec) != MeasureSpec.EXACTLY) {
+ throw new IllegalArgumentException(
+ "MultiPaneChallengeLayout must be measured with an exact size");
+ }
+
+ final int width = MeasureSpec.getSize(widthSpec);
+ final int height = MeasureSpec.getSize(heightSpec);
+ setMeasuredDimension(width, height);
+
+ int widthUsed = 0;
+ int heightUsed = 0;
+
+ // First pass. Find the challenge view and measure the user switcher,
+ // which consumes space in the layout.
+ mChallengeView = null;
+ mUserSwitcherView = 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.childType == LayoutParams.CHILD_TYPE_CHALLENGE) {
+ if (mChallengeView != null) {
+ throw new IllegalStateException(
+ "There may only be one child of type challenge");
+ }
+ mChallengeView = child;
+ } else if (lp.childType == LayoutParams.CHILD_TYPE_USER_SWITCHER) {
+ if (mUserSwitcherView != null) {
+ throw new IllegalStateException(
+ "There may only be one child of type userSwitcher");
+ }
+ mUserSwitcherView = child;
+
+ if (child.getVisibility() == GONE) continue;
+
+ int adjustedWidthSpec = widthSpec;
+ int adjustedHeightSpec = heightSpec;
+ if (lp.maxWidth >= 0) {
+ adjustedWidthSpec = MeasureSpec.makeMeasureSpec(
+ Math.min(lp.maxWidth, MeasureSpec.getSize(widthSpec)),
+ MeasureSpec.EXACTLY);
+ }
+ if (lp.maxHeight >= 0) {
+ adjustedHeightSpec = MeasureSpec.makeMeasureSpec(
+ Math.min(lp.maxHeight, MeasureSpec.getSize(heightSpec)),
+ MeasureSpec.EXACTLY);
+ }
+ // measureChildWithMargins will resolve layout direction for the LayoutParams
+ measureChildWithMargins(child, adjustedWidthSpec, 0, adjustedHeightSpec, 0);
+
+ // Only subtract out space from one dimension. Favor vertical.
+ // Offset by 1.5x to add some balance along the other edge.
+ if (Gravity.isVertical(lp.gravity)) {
+ heightUsed += child.getMeasuredHeight() * 1.5f;
+ } else if (Gravity.isHorizontal(lp.gravity)) {
+ widthUsed += child.getMeasuredWidth() * 1.5f;
+ }
+ } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) {
+ setScrimView(child);
+ child.measure(widthSpec, heightSpec);
+ }
+ }
+
+ // Second pass. Measure everything that's left.
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ if (lp.childType == LayoutParams.CHILD_TYPE_USER_SWITCHER ||
+ lp.childType == LayoutParams.CHILD_TYPE_SCRIM ||
+ child.getVisibility() == GONE) {
+ // Don't need to measure GONE children, and the user switcher was already measured.
+ continue;
+ }
+
+ int adjustedWidthSpec;
+ int adjustedHeightSpec;
+ if (lp.centerWithinArea > 0) {
+ if (mOrientation == HORIZONTAL) {
+ adjustedWidthSpec = MeasureSpec.makeMeasureSpec(
+ (int) (width * lp.centerWithinArea + 0.5f), MeasureSpec.EXACTLY);
+ adjustedHeightSpec = heightSpec;
+ } else {
+ adjustedWidthSpec = widthSpec;
+ adjustedHeightSpec = MeasureSpec.makeMeasureSpec(
+ (int) (height * lp.centerWithinArea + 0.5f), MeasureSpec.EXACTLY);
+ }
+ } else {
+ adjustedWidthSpec = widthSpec;
+ adjustedHeightSpec = heightSpec;
+ }
+ if (lp.maxWidth >= 0) {
+ adjustedWidthSpec = MeasureSpec.makeMeasureSpec(
+ Math.min(lp.maxWidth, MeasureSpec.getSize(widthSpec)),
+ MeasureSpec.EXACTLY);
+ }
+ if (lp.maxHeight >= 0) {
+ adjustedHeightSpec = MeasureSpec.makeMeasureSpec(
+ Math.min(lp.maxHeight, MeasureSpec.getSize(heightSpec)),
+ MeasureSpec.EXACTLY);
+ }
+
+ measureChildWithMargins(child, adjustedWidthSpec, widthUsed,
+ adjustedHeightSpec, heightUsed);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final Rect padding = mTempRect;
+ padding.left = getPaddingLeft();
+ padding.top = getPaddingTop();
+ padding.right = getPaddingRight();
+ padding.bottom = getPaddingBottom();
+ final int width = r - l;
+ final int height = b - t;
+
+ // Reserve extra space in layout for the user switcher by modifying
+ // local padding during this layout pass
+ if (mUserSwitcherView != null && mUserSwitcherView.getVisibility() != GONE) {
+ layoutWithGravity(width, height, mUserSwitcherView, padding, true);
+ }
+
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+
+ // We did the user switcher above if we have one.
+ if (child == mUserSwitcherView || child.getVisibility() == GONE) continue;
+
+ if (child == mScrimView) {
+ child.layout(0, 0, width, height);
+ continue;
+ }
+
+ layoutWithGravity(width, height, child, padding, false);
+ }
+ }
+
+ private void layoutWithGravity(int width, int height, View child, Rect padding,
+ boolean adjustPadding) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ final int gravity = Gravity.getAbsoluteGravity(lp.gravity, getLayoutDirection());
+
+ final boolean fixedLayoutSize = lp.centerWithinArea > 0;
+ final boolean fixedLayoutHorizontal = fixedLayoutSize && mOrientation == HORIZONTAL;
+ final boolean fixedLayoutVertical = fixedLayoutSize && mOrientation == VERTICAL;
+
+ final int adjustedWidth;
+ final int adjustedHeight;
+ if (fixedLayoutHorizontal) {
+ final int paddedWidth = width - padding.left - padding.right;
+ adjustedWidth = (int) (paddedWidth * lp.centerWithinArea + 0.5f);
+ adjustedHeight = height;
+ } else if (fixedLayoutVertical) {
+ final int paddedHeight = height - padding.top - padding.bottom;
+ adjustedWidth = width;
+ adjustedHeight = (int) (paddedHeight * lp.centerWithinArea + 0.5f);
+ } else {
+ adjustedWidth = width;
+ adjustedHeight = height;
+ }
+
+ final boolean isVertical = Gravity.isVertical(gravity);
+ final boolean isHorizontal = Gravity.isHorizontal(gravity);
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = child.getMeasuredHeight();
+
+ int left = padding.left;
+ int top = padding.top;
+ int right = left + childWidth;
+ int bottom = top + childHeight;
+ switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
+ case Gravity.TOP:
+ top = fixedLayoutVertical ?
+ padding.top + (adjustedHeight - childHeight) / 2 : padding.top;
+ bottom = top + childHeight;
+ if (adjustPadding && isVertical) {
+ padding.top = bottom;
+ padding.bottom += childHeight / 2;
+ }
+ break;
+ case Gravity.BOTTOM:
+ bottom = fixedLayoutVertical
+ ? height - padding.bottom - (adjustedHeight - childHeight) / 2
+ : height - padding.bottom;
+ top = bottom - childHeight;
+ if (adjustPadding && isVertical) {
+ padding.bottom = height - top;
+ padding.top += childHeight / 2;
+ }
+ break;
+ case Gravity.CENTER_VERTICAL:
+ final int paddedHeight = height - padding.top - padding.bottom;
+ top = padding.top + (paddedHeight - childHeight) / 2;
+ bottom = top + childHeight;
+ break;
+ }
+ switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.LEFT:
+ left = fixedLayoutHorizontal ?
+ padding.left + (adjustedWidth - childWidth) / 2 : padding.left;
+ right = left + childWidth;
+ if (adjustPadding && isHorizontal && !isVertical) {
+ padding.left = right;
+ padding.right += childWidth / 2;
+ }
+ break;
+ case Gravity.RIGHT:
+ right = fixedLayoutHorizontal
+ ? width - padding.right - (adjustedWidth - childWidth) / 2
+ : width - padding.right;
+ left = right - childWidth;
+ if (adjustPadding && isHorizontal && !isVertical) {
+ padding.right = width - left;
+ padding.left += childWidth / 2;
+ }
+ break;
+ case Gravity.CENTER_HORIZONTAL:
+ final int paddedWidth = width - padding.left - padding.right;
+ left = (paddedWidth - childWidth) / 2;
+ right = left + childWidth;
+ break;
+ }
+ child.layout(left, top, right, bottom);
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs, this);
+ }
+
+ @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 float centerWithinArea = 0;
+
+ public int childType = 0;
+
+ public static final int CHILD_TYPE_NONE = 0;
+ public static final int CHILD_TYPE_WIDGET = 1;
+ public static final int CHILD_TYPE_CHALLENGE = 2;
+ public static final int CHILD_TYPE_USER_SWITCHER = 3;
+ public static final int CHILD_TYPE_SCRIM = 4;
+
+ public int gravity = Gravity.NO_GRAVITY;
+
+ public int maxWidth = -1;
+ public int maxHeight = -1;
+
+ public LayoutParams() {
+ this(WRAP_CONTENT, WRAP_CONTENT);
+ }
+
+ LayoutParams(Context c, AttributeSet attrs, MultiPaneChallengeLayout parent) {
+ super(c, attrs);
+
+ final TypedArray a = c.obtainStyledAttributes(attrs,
+ R.styleable.MultiPaneChallengeLayout_Layout);
+
+ centerWithinArea = a.getFloat(
+ R.styleable.MultiPaneChallengeLayout_Layout_layout_centerWithinArea, 0);
+ childType = a.getInt(R.styleable.MultiPaneChallengeLayout_Layout_layout_childType,
+ CHILD_TYPE_NONE);
+ gravity = a.getInt(R.styleable.MultiPaneChallengeLayout_Layout_layout_gravity,
+ Gravity.NO_GRAVITY);
+ maxWidth = a.getDimensionPixelSize(
+ R.styleable.MultiPaneChallengeLayout_Layout_layout_maxWidth, -1);
+ maxHeight = a.getDimensionPixelSize(
+ R.styleable.MultiPaneChallengeLayout_Layout_layout_maxHeight, -1);
+
+ // Default gravity settings based on type and parent orientation
+ if (gravity == Gravity.NO_GRAVITY) {
+ if (parent.mOrientation == HORIZONTAL) {
+ switch (childType) {
+ case CHILD_TYPE_WIDGET:
+ gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
+ break;
+ case CHILD_TYPE_CHALLENGE:
+ gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
+ break;
+ case CHILD_TYPE_USER_SWITCHER:
+ gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+ break;
+ }
+ } else {
+ switch (childType) {
+ case CHILD_TYPE_WIDGET:
+ gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+ break;
+ case CHILD_TYPE_CHALLENGE:
+ gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+ break;
+ case CHILD_TYPE_USER_SWITCHER:
+ gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+ break;
+ }
+ }
+ }
+
+ a.recycle();
+ }
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(MarginLayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(LayoutParams source) {
+ this((MarginLayoutParams) source);
+
+ centerWithinArea = source.centerWithinArea;
+ childType = source.childType;
+ gravity = source.gravity;
+ maxWidth = source.maxWidth;
+ maxHeight = source.maxHeight;
+ }
+ }
+}
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
index 000000000000..27316ff193e5
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java
@@ -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.content.res.TypedArray;
+import android.text.SpannableStringBuilder;
+import android.text.style.TextAppearanceSpan;
+import android.util.AttributeSet;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
+
+public class NumPadKey extends Button {
+ // list of "ABC", etc per digit, starting with '0'
+ static String sKlondike[];
+
+ int mDigit = -1;
+ int mTextViewResId;
+ TextView mTextView = null;
+ boolean mEnableHaptics;
+
+ 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));
+ }
+ doHapticKeyClick();
+ }
+ };
+
+ 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);
+
+ mEnableHaptics = new LockPatternUtils(context).isTactileFeedbackEnabled();
+
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+ builder.append(String.valueOf(mDigit));
+ if (mDigit >= 0) {
+ if (sKlondike == null) {
+ sKlondike = context.getResources().getStringArray(
+ R.array.lockscreen_num_pad_klondike);
+ }
+ if (sKlondike != null && sKlondike.length > mDigit) {
+ final String extra = sKlondike[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;
+ }
+
+ // Cause a VIRTUAL_KEY vibration
+ public void doHapticKeyClick() {
+ if (mEnableHaptics) {
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
+ | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
+ }
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java b/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java
index 86c05b196e3f..76bd005ad024 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java
@@ -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;
@@ -121,22 +138,13 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
protected OnLongClickListener mLongClickListener;
- protected boolean mAllowLongPress = true;
-
protected int mTouchSlop;
private int mPagingTouchSlop;
private int mMaximumVelocity;
private int mMinimumWidth;
protected int mPageSpacing;
- protected int mPageLayoutPaddingTop;
- protected int mPageLayoutPaddingBottom;
- protected int mPageLayoutPaddingLeft;
- protected int mPageLayoutPaddingRight;
- protected int mPageLayoutWidthGap;
- protected int mPageLayoutHeightGap;
protected int mCellCountX = 0;
protected int mCellCountY = 0;
- protected boolean mCenterPagesVertically;
protected boolean mAllowOverScroll = true;
protected int mUnboundedScrollX;
protected int[] mTempVisiblePagesRange = new int[2];
@@ -162,7 +170,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 +196,43 @@ 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;
+ // This variable's scope is only for the duration of startReordering() and endReordering()
+ private boolean mReorderingStarted = false;
+ // This variable's scope is for the duration of startReordering() and after the zoomIn()
+ // animation after endReordering()
+ private boolean mIsReordering;
+
+ // 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;
+ private int FLING_TO_DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250;
+ protected int mFlingToDeleteThresholdVelocity = -1400; // TEMPORARY
+ private boolean mIsFlingingToDelete = false;
+
public interface PageSwitchListener {
void onPageSwitch(View newPage, int newPageIndex);
}
@@ -205,24 +250,15 @@ 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);
+ a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0);
a.recycle();
+ mEdgeSwipeRegionSize =
+ getResources().getDimensionPixelSize(R.dimen.kg_edge_swipe_region_size);
+
setHapticFeedbackEnabled(false);
init();
}
@@ -235,7 +271,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 +284,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 (isReordering(true)) {
+ 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 +423,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 +499,14 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
mTouchX = x;
mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
+
+ // Update the last motion events when scrolling
+ if (isReordering(true)) {
+ 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 +561,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 +592,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;
+ if (lp.width == LayoutParams.WRAP_CONTENT) {
+ childWidthMode = MeasureSpec.AT_MOST;
+ } else {
+ childWidthMode = MeasureSpec.EXACTLY;
+ }
- int childWidthMode = MeasureSpec.EXACTLY;
- int childHeightMode = 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,21 +620,20 @@ 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
// ensure that the cache is filled with good values.
invalidateCachedOffsets();
- if (mChildCountOnLastMeasure != getChildCount()) {
+ if (mChildCountOnLastMeasure != getChildCount() && !mIsFlingingToDelete) {
setCurrentPage(mCurrentPage);
}
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 +670,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,
@@ -585,7 +707,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
if (mFadeInAdjacentScreens && !isInOverscroll) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
- if (child != null) {
+ if (child != null && child != mDragView) {
float scrollProgress = getScrollProgress(screenCenter, child, i);
float alpha = 1 - Math.abs(scrollProgress);
child.setAlpha(alpha);
@@ -606,7 +728,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 +781,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 +798,57 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
return (int) (maxWidth * mLayoutScale + 0.5f);
}
+ void boundByReorderablePages(boolean isReordering, int[] range) {
+ if (isReordering) {
+ if (isAddWidgetPageVisible()) {
+ range[0]++;
+ }
+ if (isMusicWidgetVisible()) {
+ range[1]--;
+ }
+ if (isCameraWidgetVisible()) {
+ range[1]--;
+ }
+ }
+ }
+
+ // 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 +857,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;
@@ -728,6 +874,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
final int pageCount = getChildCount();
if (pageCount > 0) {
getVisiblePages(mTempVisiblePagesRange);
+ boundByReorderablePages(isReordering(false), mTempVisiblePagesRange);
final int leftScreen = mTempVisiblePagesRange[0];
final int rightScreen = mTempVisiblePagesRange[1];
if (leftScreen != -1 && rightScreen != -1) {
@@ -737,13 +884,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();
}
@@ -836,31 +990,17 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
}
/**
- * {@inheritDoc}
- */
- @Override
- public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- if (disallowIntercept) {
- // We need to make sure to cancel our long press if
- // a scrollable widget takes over touch events
- final View currentPage = getPageAt(mCurrentPage);
- currentPage.cancelLongPress();
- }
- super.requestDisallowInterceptTouchEvent(disallowIntercept);
- }
-
- /**
* Return true if a tap at (x, y) should trigger a flip to the previous page.
*/
protected boolean hitsPreviousPage(float x, float y) {
- return (x < getRelativeChildOffset(mCurrentPage) - mPageSpacing);
+ return (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 +1052,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 +1086,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 +1102,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 +1132,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 +1160,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 +1188,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 +1213,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 +1273,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 +1320,96 @@ 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) {
+ mTempVisiblePagesRange[0] = 0;
+ mTempVisiblePagesRange[1] = getPageCount() - 1;
+ boundByReorderablePages(true, mTempVisiblePagesRange);
+ if (mTempVisiblePagesRange[0] <= pageUnderPointIndex &&
+ pageUnderPointIndex <= mTempVisiblePagesRange[1] &&
+ 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 +1478,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 +1511,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 +1583,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 +1614,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 +1671,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;
@@ -1435,20 +1709,29 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
protected void snapToPage(int whichPage) {
snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
}
+ protected void snapToPageImmediately(int whichPage) {
+ snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true);
+ }
protected void snapToPage(int whichPage, int duration) {
+ snapToPage(whichPage, duration, false);
+ }
+ protected void snapToPage(int whichPage, int duration, boolean immediate) {
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;
final int delta = newX - sX;
- snapToPage(whichPage, delta, duration);
+ snapToPage(whichPage, delta, duration, immediate);
}
protected void snapToPage(int whichPage, int delta, int duration) {
+ snapToPage(whichPage, delta, duration, false);
+ }
+ protected void snapToPage(int whichPage, int delta, int duration, boolean immediate) {
mNextPage = whichPage;
View focusedChild = getFocusedChild();
@@ -1459,7 +1742,9 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
pageBeginMoving();
awakenScrollBars(duration);
- if (duration == 0) {
+ if (immediate) {
+ duration = 0;
+ } else if (duration == 0) {
duration = Math.abs(delta);
}
@@ -1467,6 +1752,12 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL
mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
notifyPageSwitchListener();
+
+ // Trigger a compute() to finish switching pages if necessary
+ if (immediate) {
+ computeScroll();
+ }
+
invalidate();
}
@@ -1500,21 +1791,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 +1929,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 +1951,318 @@ 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 (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
+ mZoomInOutAnim.cancel();
+ }
+
+ if (!(getScaleX() < 1f || getScaleY() < 1f)) {
+ 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() {
+ // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
+ mTouchState = TOUCH_STATE_REORDERING;
+ mIsReordering = true;
+
+ // We must invalidate to trigger a redraw to update the layers such that the drag view
+ // is always drawn on top
+ invalidate();
+ }
+
+ protected void onEndReordering() {
+ mIsReordering = false;
+ }
+
+ public boolean startReordering() {
+ int dragViewIndex = getPageNearestToCenterOfScreen();
+ mTempVisiblePagesRange[0] = 0;
+ mTempVisiblePagesRange[1] = getPageCount() - 1;
+ boundByReorderablePages(true, mTempVisiblePagesRange);
+ mReorderingStarted = true;
+
+ // Check if we are within the reordering range
+ if (mTempVisiblePagesRange[0] <= dragViewIndex &&
+ dragViewIndex <= mTempVisiblePagesRange[1]) {
+ if (zoomOut()) {
+ // Find the drag view under the pointer
+ mDragView = getChildAt(dragViewIndex);
+
+ onStartReordering();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ boolean isReordering(boolean testTouchState) {
+ boolean state = mIsReordering;
+ if (testTouchState) {
+ state &= (mTouchState == TOUCH_STATE_REORDERING);
+ }
+ return state;
+ }
+ void endReordering() {
+ // For simplicity, we call endReordering sometimes even if reordering was never started.
+ // In that case, we don't want to do anything.
+ if (!mReorderingStarted) return;
+ mReorderingStarted = false;
+ Runnable onCompleteRunnable = new Runnable() {
+ @Override
+ public void run() {
+ onEndReordering();
+ }
+ };
+ zoomIn(onCompleteRunnable);
+
+ // If we haven't flung-to-delete the current child, then we just animate the drag view
+ // back into position
+ if (!mIsFlingingToDelete) {
+ // Snap to the current page
+ snapToDestination();
+
+ animateChildrenToOriginalPosition();
+ }
+ }
+
+ // "Zooms in" the PagedView to highlight the current page
+ boolean zoomIn(final Runnable onCompleteRunnable) {
+ if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
+ mZoomInOutAnim.cancel();
+ }
+ if (getScaleX() < 1f || getScaleY() < 1f) {
+ 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;
+ } else {
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ }
+ return false;
+ }
+
+ /*
+ * Special widgets
+ */
+ boolean isAddWidgetPageVisible() {
+ // TODO: Make proper test once we decide whether the add-page is always showing
+ return true;
+ }
+ boolean isMusicWidgetVisible() {
+ // TODO: Make proper test once we have music in the list
+ return false;
+ }
+ boolean isCameraWidgetVisible() {
+ // TODO: Make proper test once we have camera in the list
+ return true;
+ }
+
+ /*
+ * 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() {
+ int dragViewIndex = indexOfChild(dragView);
+ // Setup the scroll to the correct page before we swap the views
+ snapToPageImmediately(dragViewIndex - 1);
+
+ // For each of the pages around 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)
+ // NOTE: We can make an assumption here because we have side-bound pages that we
+ // will always have pages to animate in from the left
+ int lowerIndex = 0;
+ int upperIndex = dragViewIndex - 1;
+ 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 = 0;
+ if (i == 0) {
+ oldX = -(getOffsetX() + getChildOffset(i));
+ } else {
+ oldX = getOffsetX() + getChildOffset(i - 1);
+ }
+ int newX = getOffsetX() + getChildOffset(i);
+
+ // 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(FLING_TO_DELETE_SLIDE_IN_SIDE_PAGE_DURATION);
+ anim.playTogether(
+ ObjectAnimator.ofFloat(v, "translationX", 0f));
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIsFlingingToDelete = false;
+ }
+ });
+ anim.start();
+ v.setTag(anim);
+ }
+
+ removeView(dragView);
+ }
+ };
+
+ // 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/SlidingChallengeLayout.java b/policy/src/com/android/internal/policy/impl/keyguard/SlidingChallengeLayout.java
new file mode 100644
index 000000000000..165fbe73d38e
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/SlidingChallengeLayout.java
@@ -0,0 +1,798 @@
+/*
+ * 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 implements ChallengeLayout {
+ 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;
+ private boolean mShowHandle = true;
+
+ // Initialized during measurement from child layoutparams
+ private View mChallengeView;
+ private View mScrimView;
+
+ // Range: 0 (fully hidden) to 1 (fully visible)
+ private float mChallengeOffset = 1.f;
+ private boolean mChallengeShowing = true;
+ private boolean mIsBouncing = false;
+
+ private final Scroller mScroller;
+ private int mScrollState;
+ private OnChallengeScrolledListener mScrollListener;
+ private OnBouncerStateChangedListener mBouncerListener;
+
+ 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 int mDragHandleEdgeSlop;
+
+ 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();
+ }
+ };
+
+ private final OnClickListener mScrimClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideBouncer();
+ }
+ };
+
+ /**
+ * 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();
+
+ mDragHandleEdgeSlop = getResources().getDimensionPixelSize(
+ R.dimen.kg_edge_swipe_region_size);
+
+ 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 (mScrollListener != null) {
+ int challengeTop = mChallengeView != null ? mChallengeView.getTop() : 0;
+ mScrollListener.onScrollPositionChanged(mChallengeOffset, challengeTop);
+ mScrollListener.onScrollStateChanged(mScrollState);
+ }
+ }
+
+ public void setOnChallengeScrolledListener(OnChallengeScrolledListener listener) {
+ mScrollListener = listener;
+ if (mHasLayout) {
+ sendInitialListenerUpdates();
+ }
+ }
+
+ public void setOnBouncerStateChangedListener(OnBouncerStateChangedListener listener) {
+ mBouncerListener = listener;
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mHasLayout = false;
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ removeCallbacks(mEndScrollRunnable);
+ mHasLayout = false;
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ if (mIsBouncing && child != mChallengeView) {
+ // Clear out of the bouncer if the user tries to move focus outside of
+ // the security challenge view.
+ hideBouncer();
+ }
+ super.requestChildFocus(child, focused);
+ }
+
+ // 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 (mScrollListener != null) {
+ mScrollListener.onScrollStateChanged(state);
+ }
+ }
+ }
+
+ void completeChallengeScroll() {
+ setChallengeShowing(mChallengeOffset != 0);
+ setScrollState(SCROLL_STATE_IDLE);
+ }
+
+ void setScrimView(View scrim) {
+ if (mScrimView != null) {
+ mScrimView.setOnClickListener(null);
+ }
+ mScrimView = scrim;
+ mScrimView.setVisibility(mIsBouncing ? VISIBLE : GONE);
+ mScrimView.setFocusable(true);
+ mScrimView.setOnClickListener(mScrimClickListener);
+ }
+
+ /**
+ * 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 boolean isChallengeOverlapping() {
+ return mChallengeShowing;
+ }
+
+ @Override
+ public boolean isBouncing() {
+ return mIsBouncing;
+ }
+
+ @Override
+ public void showBouncer() {
+ if (mIsBouncing) return;
+ showChallenge(true);
+ mIsBouncing = true;
+ if (mScrimView != null) {
+ mScrimView.setVisibility(GONE);
+ }
+ if (mBouncerListener != null) {
+ mBouncerListener.onBouncerStateChanged(true);
+ }
+ }
+
+ @Override
+ public void hideBouncer() {
+ if (!mIsBouncing) return;
+ setChallengeShowing(false);
+ mIsBouncing = false;
+ if (mScrimView != null) {
+ mScrimView.setVisibility(GONE);
+ }
+ if (mBouncerListener != null) {
+ mBouncerListener.onBouncerStateChanged(false);
+ }
+ }
+
+ @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 >= mDragHandleEdgeSlop &&
+ y >= mChallengeView.getTop() &&
+ x < getWidth() - mDragHandleEdgeSlop &&
+ 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.childType == LayoutParams.CHILD_TYPE_CHALLENGE) {
+ 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);
+ }
+ } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) {
+ setScrimView(child);
+ }
+
+ 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.childType == LayoutParams.CHILD_TYPE_CHALLENGE) {
+ // 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 && mShowHandle) {
+ 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 (mScrollListener != null) {
+ mScrollListener.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);
+ }
+ }
+
+ public void showHandle(boolean show) {
+ mShowHandle = show;
+ invalidate();
+ }
+
+ @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 int childType = CHILD_TYPE_NONE;
+ public static final int CHILD_TYPE_NONE = 0;
+ public static final int CHILD_TYPE_CHALLENGE = 2;
+ public static final int CHILD_TYPE_SCRIM = 4;
+
+ 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);
+
+ childType = source.childType;
+ }
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+
+ final TypedArray a = c.obtainStyledAttributes(attrs,
+ R.styleable.SlidingChallengeLayout_Layout);
+ childType = a.getInt(R.styleable.SlidingChallengeLayout_Layout_layout_childType,
+ CHILD_TYPE_NONE);
+ a.recycle();
+ }
+ }
+}