diff options
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 Binary files differnew file mode 100644 index 000000000000..9cf9b60e0c67 --- /dev/null +++ b/core/res/res/drawable-hdpi/add_widget.png diff --git a/core/res/res/drawable-hdpi/security_frame.9.png b/core/res/res/drawable-hdpi/security_frame.9.png Binary files differnew file mode 100644 index 000000000000..9eeadc4d118f --- /dev/null +++ b/core/res/res/drawable-hdpi/security_frame.9.png diff --git a/core/res/res/drawable-hdpi/security_handle.png b/core/res/res/drawable-hdpi/security_handle.png Binary files differnew file mode 100644 index 000000000000..bd4640f7228c --- /dev/null +++ b/core/res/res/drawable-hdpi/security_handle.png diff --git a/core/res/res/drawable-hdpi/sym_keyboard_return_holo.png b/core/res/res/drawable-hdpi/sym_keyboard_return_holo.png Binary files differnew file mode 100644 index 000000000000..f1bcf487c5f1 --- /dev/null +++ b/core/res/res/drawable-hdpi/sym_keyboard_return_holo.png diff --git a/core/res/res/drawable-mdpi/add_widget.png b/core/res/res/drawable-mdpi/add_widget.png Binary files differnew file mode 100644 index 000000000000..9cf9b60e0c67 --- /dev/null +++ b/core/res/res/drawable-mdpi/add_widget.png diff --git a/core/res/res/drawable-mdpi/security_frame.9.png b/core/res/res/drawable-mdpi/security_frame.9.png Binary files differnew file mode 100644 index 000000000000..9eeadc4d118f --- /dev/null +++ b/core/res/res/drawable-mdpi/security_frame.9.png diff --git a/core/res/res/drawable-mdpi/security_handle.png b/core/res/res/drawable-mdpi/security_handle.png Binary files differnew file mode 100644 index 000000000000..bd4640f7228c --- /dev/null +++ b/core/res/res/drawable-mdpi/security_handle.png diff --git a/core/res/res/drawable-mdpi/sym_keyboard_return_holo.png b/core/res/res/drawable-mdpi/sym_keyboard_return_holo.png Binary files differnew file mode 100644 index 000000000000..d5a7708ca082 --- /dev/null +++ b/core/res/res/drawable-mdpi/sym_keyboard_return_holo.png 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 Binary files differnew file mode 100644 index 000000000000..f1bcf487c5f1 --- /dev/null +++ b/core/res/res/drawable-sw600dp-hdpi/sym_keyboard_return_holo.png 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 Binary files differnew file mode 100644 index 000000000000..d5a7708ca082 --- /dev/null +++ b/core/res/res/drawable-sw600dp-mdpi/sym_keyboard_return_holo.png 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 Binary files differnew file mode 100644 index 000000000000..55174e076765 --- /dev/null +++ b/core/res/res/drawable-sw600dp-xhdpi/sym_keyboard_return_holo.png diff --git a/core/res/res/drawable-xhdpi/add_widget.png b/core/res/res/drawable-xhdpi/add_widget.png Binary files differnew file mode 100644 index 000000000000..9cf9b60e0c67 --- /dev/null +++ b/core/res/res/drawable-xhdpi/add_widget.png diff --git a/core/res/res/drawable-xhdpi/security_frame.9.png b/core/res/res/drawable-xhdpi/security_frame.9.png Binary files differnew file mode 100644 index 000000000000..9eeadc4d118f --- /dev/null +++ b/core/res/res/drawable-xhdpi/security_frame.9.png diff --git a/core/res/res/drawable-xhdpi/security_handle.png b/core/res/res/drawable-xhdpi/security_handle.png Binary files differnew file mode 100644 index 000000000000..bd4640f7228c --- /dev/null +++ b/core/res/res/drawable-xhdpi/security_handle.png diff --git a/core/res/res/drawable-xhdpi/sym_keyboard_return_holo.png b/core/res/res/drawable-xhdpi/sym_keyboard_return_holo.png Binary files differnew file mode 100644 index 000000000000..55174e076765 --- /dev/null +++ b/core/res/res/drawable-xhdpi/sym_keyboard_return_holo.png 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><FragmentBreadCrumbs></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(); + } + } +} |