diff options
218 files changed, 7800 insertions, 1593 deletions
@@ -10093,8 +10093,10 @@ package android.hardware.display { public final class DisplayManager { method public android.view.Display getDisplay(int); method public android.view.Display[] getDisplays(); + method public android.view.Display[] getDisplays(java.lang.String); method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler); method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener); + field public static final java.lang.String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION"; } public static abstract interface DisplayManager.DisplayListener { @@ -11768,6 +11770,7 @@ package android.media { method public abstract void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo); method public abstract void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo); method public abstract void onRouteGrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup, int); + method public void onRoutePresentationDisplayChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo); method public abstract void onRouteRemoved(android.media.MediaRouter, android.media.MediaRouter.RouteInfo); method public abstract void onRouteSelected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo); method public abstract void onRouteUngrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup); @@ -11802,6 +11805,7 @@ package android.media { method public java.lang.CharSequence getName(android.content.Context); method public int getPlaybackStream(); method public int getPlaybackType(); + method public android.view.Display getPresentationDisplay(); method public java.lang.CharSequence getStatus(); method public int getSupportedTypes(); method public java.lang.Object getTag(); @@ -16619,6 +16623,7 @@ package android.os { public class UserManager { method public long getSerialNumberForUser(android.os.UserHandle); + method public int getUserCount(); method public android.os.UserHandle getUserForSerialNumber(long); method public java.lang.String getUserName(); method public boolean isUserAGoat(); @@ -25364,7 +25369,6 @@ package android.view { field public static final int SYSTEM_UI_FLAG_VISIBLE = 0; // 0x0 field public static final int SYSTEM_UI_LAYOUT_FLAGS = 1536; // 0x600 field public static final int TEXT_ALIGNMENT_CENTER = 4; // 0x4 - field public static int TEXT_ALIGNMENT_DEFAULT; field public static final int TEXT_ALIGNMENT_GRAVITY = 1; // 0x1 field public static final int TEXT_ALIGNMENT_INHERIT = 0; // 0x0 field public static final int TEXT_ALIGNMENT_TEXT_END = 3; // 0x3 @@ -25372,7 +25376,6 @@ package android.view { field public static final int TEXT_ALIGNMENT_VIEW_END = 6; // 0x6 field public static final int TEXT_ALIGNMENT_VIEW_START = 5; // 0x5 field public static final int TEXT_DIRECTION_ANY_RTL = 2; // 0x2 - field public static int TEXT_DIRECTION_DEFAULT; field public static final int TEXT_DIRECTION_FIRST_STRONG = 1; // 0x1 field public static final int TEXT_DIRECTION_INHERIT = 0; // 0x0 field public static final int TEXT_DIRECTION_LOCALE = 5; // 0x5 diff --git a/api/current.txt b/api/current.txt index e7f02dc..e26d8f0 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10093,8 +10093,10 @@ package android.hardware.display { public final class DisplayManager { method public android.view.Display getDisplay(int); method public android.view.Display[] getDisplays(); + method public android.view.Display[] getDisplays(java.lang.String); method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler); method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener); + field public static final java.lang.String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION"; } public static abstract interface DisplayManager.DisplayListener { @@ -11768,6 +11770,7 @@ package android.media { method public abstract void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo); method public abstract void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo); method public abstract void onRouteGrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup, int); + method public void onRoutePresentationDisplayChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo); method public abstract void onRouteRemoved(android.media.MediaRouter, android.media.MediaRouter.RouteInfo); method public abstract void onRouteSelected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo); method public abstract void onRouteUngrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup); @@ -11802,6 +11805,7 @@ package android.media { method public java.lang.CharSequence getName(android.content.Context); method public int getPlaybackStream(); method public int getPlaybackType(); + method public android.view.Display getPresentationDisplay(); method public java.lang.CharSequence getStatus(); method public int getSupportedTypes(); method public java.lang.Object getTag(); @@ -16619,6 +16623,7 @@ package android.os { public class UserManager { method public long getSerialNumberForUser(android.os.UserHandle); + method public int getUserCount(); method public android.os.UserHandle getUserForSerialNumber(long); method public java.lang.String getUserName(); method public boolean isUserAGoat(); @@ -25364,7 +25369,6 @@ package android.view { field public static final int SYSTEM_UI_FLAG_VISIBLE = 0; // 0x0 field public static final int SYSTEM_UI_LAYOUT_FLAGS = 1536; // 0x600 field public static final int TEXT_ALIGNMENT_CENTER = 4; // 0x4 - field public static int TEXT_ALIGNMENT_DEFAULT; field public static final int TEXT_ALIGNMENT_GRAVITY = 1; // 0x1 field public static final int TEXT_ALIGNMENT_INHERIT = 0; // 0x0 field public static final int TEXT_ALIGNMENT_TEXT_END = 3; // 0x3 @@ -25372,7 +25376,6 @@ package android.view { field public static final int TEXT_ALIGNMENT_VIEW_END = 6; // 0x6 field public static final int TEXT_ALIGNMENT_VIEW_START = 5; // 0x5 field public static final int TEXT_DIRECTION_ANY_RTL = 2; // 0x2 - field public static int TEXT_DIRECTION_DEFAULT; field public static final int TEXT_DIRECTION_FIRST_STRONG = 1; // 0x1 field public static final int TEXT_DIRECTION_INHERIT = 0; // 0x0 field public static final int TEXT_DIRECTION_LOCALE = 5; // 0x5 diff --git a/core/java/android/app/Presentation.java b/core/java/android/app/Presentation.java index 20b27c5..3e8af60 100644 --- a/core/java/android/app/Presentation.java +++ b/core/java/android/app/Presentation.java @@ -50,6 +50,93 @@ import android.util.TypedValue; * whenever the activity itself is paused or resumed. * </p> * + * <h3>Choosing a presentation display</h3> + * <p> + * Before showing a {@link Presentation} it's important to choose the {@link Display} + * on which it will appear. Choosing a presentation display is sometimes difficult + * because there may be multiple displays attached. Rather than trying to guess + * which display is best, an application should let the system choose a suitable + * presentation display. + * </p><p> + * There are two main ways to choose a {@link Display}. + * </p> + * + * <h4>Using the media router to choose a presentation display</h4> + * <p> + * The easiest way to choose a presentation display is to use the + * {@link android.media.MediaRouter MediaRouter} API. The media router service keeps + * track of which audio and video routes are available on the system. + * The media router sends notifications whenever routes are selected or unselected + * or when the preferred presentation display of a route changes. + * So an application can simply watch for these notifications and show or dismiss + * a presentation on the preferred presentation display automatically. + * </p><p> + * The preferred presentation display is the display that the media router recommends + * that the application should use if it wants to show content on the secondary display. + * Sometimes there may not be a preferred presentation display in which + * case the application should show its content locally without using a presentation. + * </p><p> + * Here's how to use the media router to create and show a presentation on the preferred + * presentation display using {@link android.media.MediaRouter.RouteInfo#getPresentationDisplay()}. + * </p> + * {@samplecode + * MediaRouter mediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); + * MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute(); + * if (route != null) ${ + * Display presentationDisplay = route.getPresentationDisplay(); + * if (presentationDisplay != null) ${ + * Presentation presentation = new MyPresentation(context, presentationDisplay); + * presentation.show(); + * $} + * $} + * } + * <p> + * The following sample code from <code>ApiDemos</code> demonstrates how to use the media + * router to automatically switch between showing content in the main activity and showing + * the content in a presentation when a presentation display is available. + * </p> + * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/PresentationWithMediaRouterActivity.java + * activity} + * + * <h4>Using the display manager to choose a presentation display</h4> + * <p> + * Another way to choose a presentation display is to use the {@link DisplayManager} API + * directly. The display manager service provides functions to enumerate and describe all + * displays that are attached to the system including displays that may be used + * for presentations. + * </p><p> + * The display manager keeps track of all displays in the system. However, not all + * displays are appropriate for showing presentations. For example, if an activity + * attempted to show a presentation on the main display it might obscure its own content + * (it's like opening a dialog on top of your activity). + * </p><p> + * Here's how to identify suitable displays for showing presentations using + * {@link DisplayManager#getDisplays(String)} and the + * {@link DisplayManager#DISPLAY_CATEGORY_PRESENTATION} category. + * </p> + * {@samplecode + * DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); + * Display[] presentationDisplays = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION); + * if (presentationDisplays.length > 0) ${ + * // If there is more than one suitable presentation display, then we could consider + * // giving the user a choice. For this example, we simply choose the first display + * // which is the one the system recommends as the preferred presentation display. + * Display display = presentationDisplays[0]; + * Presentation presentation = new MyPresentation(context, presentationDisplay); + * presentation.show(); + * $} + * } + * <p> + * The following sample code from <code>ApiDemos</code> demonstrates how to use the display + * manager to enumerate displays and show content on multiple presentation displays + * simultaneously. + * </p> + * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/PresentationActivity.java + * activity} + * + * @see android.media.MediaRouter#ROUTE_TYPE_LIVE_VIDEO for information on about live + * video routes and how to obtain the preferred presentation display for the + * current media route. * @see DisplayManager for information on how to enumerate displays and receive * notifications when displays are added or removed. */ @@ -121,7 +208,7 @@ public class Presentation extends Dialog { @Override protected void onStart() { super.onStart(); - mDisplayManager.registerDisplayListener(mDisplayListener, null); + mDisplayManager.registerDisplayListener(mDisplayListener, mHandler); // Since we were not watching for display changes until just now, there is a // chance that the display metrics have changed. If so, we will need to diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 28e320b..0a7a2e7 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -21,6 +21,8 @@ import android.os.Handler; import android.util.SparseArray; import android.view.Display; +import java.util.ArrayList; + /** * Manages the properties of attached displays. * <p> @@ -40,6 +42,8 @@ public final class DisplayManager { private final Object mLock = new Object(); private final SparseArray<Display> mDisplays = new SparseArray<Display>(); + private final ArrayList<Display> mTempDisplays = new ArrayList<Display>(); + /** * Broadcast receiver that indicates when the Wifi display status changes. * <p> @@ -60,6 +64,20 @@ public final class DisplayManager { public static final String EXTRA_WIFI_DISPLAY_STATUS = "android.hardware.display.extra.WIFI_DISPLAY_STATUS"; + /** + * Display category: Presentation displays. + * <p> + * This category can be used to identify secondary displays that are suitable for + * use as presentation displays. + * </p> + * + * @see android.app.Presentation for information about presenting content + * on secondary displays. + * @see #getDisplays(String) + */ + public static final String DISPLAY_CATEGORY_PRESENTATION = + "android.hardware.display.category.PRESENTATION"; + /** @hide */ public DisplayManager(Context context) { mContext = context; @@ -87,24 +105,52 @@ public final class DisplayManager { * @return An array containing all displays. */ public Display[] getDisplays() { - int[] displayIds = mGlobal.getDisplayIds(); - int expectedCount = displayIds.length; - Display[] displays = new Display[expectedCount]; + return getDisplays(null); + } + + /** + * Gets all currently valid logical displays of the specified category. + * <p> + * When there are multiple displays in a category the returned displays are sorted + * of preference. For example, if the requested category is + * {@link #DISPLAY_CATEGORY_PRESENTATION} and there are multiple presentation displays + * then the displays are sorted so that the first display in the returned array + * is the most preferred presentation display. The application may simply + * use the first display or allow the user to choose. + * </p> + * + * @param category The requested display category or null to return all displays. + * @return An array containing all displays sorted by order of preference. + * + * @see #DISPLAY_CATEGORY_PRESENTATION + */ + public Display[] getDisplays(String category) { + final int[] displayIds = mGlobal.getDisplayIds(); synchronized (mLock) { - int actualCount = 0; - for (int i = 0; i < expectedCount; i++) { - Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/); - if (display != null) { - displays[actualCount++] = display; + try { + if (category == null) { + addMatchingDisplaysLocked(mTempDisplays, displayIds, -1); + } else if (category.equals(DISPLAY_CATEGORY_PRESENTATION)) { + addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI); + addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_HDMI); + addMatchingDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY); } + return mTempDisplays.toArray(new Display[mTempDisplays.size()]); + } finally { + mTempDisplays.clear(); } - if (actualCount != expectedCount) { - Display[] oldDisplays = displays; - displays = new Display[actualCount]; - System.arraycopy(oldDisplays, 0, displays, 0, actualCount); + } + } + + private void addMatchingDisplaysLocked( + ArrayList<Display> displays, int[] displayIds, int matchType) { + for (int i = 0; i < displayIds.length; i++) { + Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/); + if (display != null + && (matchType < 0 || display.getType() == matchType)) { + displays.add(display); } } - return displays; } private Display getOrCreateDisplayLocked(int displayId, boolean assumeValid) { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 898c766..d73f99a 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -122,7 +122,7 @@ public class UserManager { * @param userHandle the user handle of the user whose information is being requested. * @return the UserInfo object for a specific user. * @hide - * */ + */ public UserInfo getUserInfo(int userHandle) { try { return mService.getUserInfo(userHandle); @@ -134,10 +134,11 @@ public class UserManager { /** * Return the serial number for a user. This is a device-unique - * number assigned to that user; if the user is deleted and new users - * created, the new users will not be given the same serial number. + * number assigned to that user; if the user is deleted and then a new + * user created, the new users will not be given the same serial number. * @param user The user whose serial number is to be retrieved. - * @return The serial number of the given user. + * @return The serial number of the given user; returns -1 if the + * given UserHandle does not exist. * @see #getUserForSerialNumber(long) */ public long getSerialNumberForUser(UserHandle user) { @@ -179,6 +180,14 @@ public class UserManager { } /** + * Return the number of users currently created on the device. + */ + public int getUserCount() { + List<UserInfo> users = getUsers(); + return users != null ? users.size() : 1; + } + + /** * Returns information for all users on this device. * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * @return the list of users that were created. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 8897039..cda0f36 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/android/view/Display.java b/core/java/android/view/Display.java index 1cd3e05..758abb5 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -54,7 +54,9 @@ public final class Display { private final DisplayManagerGlobal mGlobal; private final int mDisplayId; private final int mLayerStack; - private final String mName; + private final int mFlags; + private final int mType; + private final String mAddress; private final CompatibilityInfoHolder mCompatibilityInfo; private DisplayInfo mDisplayInfo; // never null @@ -141,6 +143,36 @@ public final class Display { public static final int FLAG_SECURE = 1 << 1; /** + * Display type: Unknown display type. + * @hide + */ + public static final int TYPE_UNKNOWN = 0; + + /** + * Display type: Built-in display. + * @hide + */ + public static final int TYPE_BUILT_IN = 1; + + /** + * Display type: HDMI display. + * @hide + */ + public static final int TYPE_HDMI = 2; + + /** + * Display type: WiFi display. + * @hide + */ + public static final int TYPE_WIFI = 3; + + /** + * Display type: Overlay display. + * @hide + */ + public static final int TYPE_OVERLAY = 4; + + /** * Internal method to create a display. * Applications should use {@link android.view.WindowManager#getDefaultDisplay()} * or {@link android.hardware.display.DisplayManager#getDisplay} @@ -154,10 +186,14 @@ public final class Display { mGlobal = global; mDisplayId = displayId; mDisplayInfo = displayInfo; - mLayerStack = displayInfo.layerStack; // can never change as long as the display is valid - mName = displayInfo.name; // cannot change as long as the display is valid mCompatibilityInfo = compatibilityInfo; mIsValid = true; + + // Cache properties that cannot change as long as the display is valid. + mLayerStack = displayInfo.layerStack; + mFlags = displayInfo.flags; + mType = displayInfo.type; + mAddress = displayInfo.address; } /** @@ -228,10 +264,34 @@ public final class Display { * @see #FLAG_SECURE */ public int getFlags() { - synchronized (this) { - updateDisplayInfoLocked(); - return mDisplayInfo.flags; - } + return mFlags; + } + + /** + * Gets the display type. + * + * @return The display type. + * + * @see #TYPE_UNKNOWN + * @see #TYPE_BUILT_IN + * @see #TYPE_HDMI + * @see #TYPE_WIFI + * @see #TYPE_OVERLAY + * @hide + */ + public int getType() { + return mType; + } + + /** + * Gets the display address, or null if none. + * Interpretation varies by display type. + * + * @return The display address. + * @hide + */ + public String getAddress() { + return mAddress; } /** @@ -246,10 +306,17 @@ public final class Display { /** * Gets the name of the display. + * <p> + * Note that some displays may be renamed by the user. + * </p> + * * @return The display's name. */ public String getName() { - return mName; + synchronized (this) { + updateDisplayInfoLocked(); + return mDisplayInfo.name; + } } /** @@ -527,5 +594,25 @@ public final class Display { + ", " + mTempMetrics + ", isValid=" + mIsValid; } } + + /** + * @hide + */ + public static String typeToString(int type) { + switch (type) { + case TYPE_UNKNOWN: + return "UNKNOWN"; + case TYPE_BUILT_IN: + return "BUILT_IN"; + case TYPE_HDMI: + return "HDMI"; + case TYPE_WIFI: + return "WIFI"; + case TYPE_OVERLAY: + return "OVERLAY"; + default: + return Integer.toString(type); + } + } } diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index ead5ff4..f3841d5 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -39,6 +39,17 @@ public final class DisplayInfo implements Parcelable { public int flags; /** + * Display type. + */ + public int type; + + /** + * Display address, or null if none. + * Interpretation varies by display type. + */ + public String address; + + /** * The human-readable name of the display. */ public String name; @@ -143,10 +154,12 @@ public final class DisplayInfo implements Parcelable { public float physicalYDpi; public static final Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() { + @Override public DisplayInfo createFromParcel(Parcel source) { return new DisplayInfo(source); } + @Override public DisplayInfo[] newArray(int size) { return new DisplayInfo[size]; } @@ -171,6 +184,9 @@ public final class DisplayInfo implements Parcelable { public boolean equals(DisplayInfo other) { return other != null && layerStack == other.layerStack + && flags == other.flags + && type == other.type + && Objects.equal(address, other.address) && Objects.equal(name, other.name) && appWidth == other.appWidth && appHeight == other.appHeight @@ -195,6 +211,8 @@ public final class DisplayInfo implements Parcelable { public void copyFrom(DisplayInfo other) { layerStack = other.layerStack; flags = other.flags; + type = other.type; + address = other.address; name = other.name; appWidth = other.appWidth; appHeight = other.appHeight; @@ -214,6 +232,8 @@ public final class DisplayInfo implements Parcelable { public void readFromParcel(Parcel source) { layerStack = source.readInt(); flags = source.readInt(); + type = source.readInt(); + address = source.readString(); name = source.readString(); appWidth = source.readInt(); appHeight = source.readInt(); @@ -234,6 +254,8 @@ public final class DisplayInfo implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(layerStack); dest.writeInt(this.flags); + dest.writeInt(type); + dest.writeString(address); dest.writeString(name); dest.writeInt(appWidth); dest.writeInt(appHeight); @@ -294,7 +316,10 @@ public final class DisplayInfo implements Parcelable { + ", rotation " + rotation + ", density " + logicalDensityDpi + ", " + physicalXDpi + " x " + physicalYDpi + " dpi" - + ", layerStack " + layerStack + flagsToString(flags) + "}"; + + ", layerStack " + layerStack + + ", type " + Display.typeToString(type) + + ", address " + address + + flagsToString(flags) + "}"; } private static String flagsToString(int flags) { diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 59f941d..1c613245 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -1526,30 +1526,6 @@ public abstract class HardwareRenderer { } @Override - void destroyLayers(View view) { - if (view != null && isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) { - if (mCanvas != null) { - mCanvas.clearLayerUpdates(); - } - destroyHardwareLayer(view); - GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS); - } - } - - private static void destroyHardwareLayer(View view) { - view.destroyLayer(true); - - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - - int count = group.getChildCount(); - for (int i = 0; i < count; i++) { - destroyHardwareLayer(group.getChildAt(i)); - } - } - } - - @Override boolean safelyRun(Runnable action) { boolean needsContext = true; if (isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) needsContext = false; @@ -1574,6 +1550,35 @@ public abstract class HardwareRenderer { } @Override + void destroyLayers(final View view) { + if (view != null) { + safelyRun(new Runnable() { + @Override + public void run() { + if (mCanvas != null) { + mCanvas.clearLayerUpdates(); + } + destroyHardwareLayer(view); + GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS); + } + }); + } + } + + private static void destroyHardwareLayer(View view) { + view.destroyLayer(true); + + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + + int count = group.getChildCount(); + for (int i = 0; i < count; i++) { + destroyHardwareLayer(group.getChildAt(i)); + } + } + } + + @Override void destroyHardwareResources(final View view) { if (view != null) { safelyRun(new Runnable() { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f5e259e..9d0d4f0 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1864,7 +1864,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Default horizontal layout direction. - * @hide */ private static final int LAYOUT_DIRECTION_DEFAULT = LAYOUT_DIRECTION_INHERIT; @@ -1914,7 +1913,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Default text direction is inherited */ - public static int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT; + private static final int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT; /** * Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED) @@ -2024,7 +2023,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Default text alignment is inherited */ - public static int TEXT_ALIGNMENT_DEFAULT = TEXT_ALIGNMENT_GRAVITY; + private static final int TEXT_ALIGNMENT_DEFAULT = TEXT_ALIGNMENT_GRAVITY; /** * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED) @@ -3224,7 +3223,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mContext = context; mResources = context != null ? context.getResources() : null; mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED; - // Set layout and text direction defaults + // Set some flags defaults mPrivateFlags2 = (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) | (TEXT_DIRECTION_DEFAULT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) | @@ -14198,11 +14197,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ protected void resolveDrawables() { - if (mBackground != null) { - mBackground.setLayoutDirection(getLayoutDirection()); + if (canResolveLayoutDirection()) { + if (mBackground != null) { + mBackground.setLayoutDirection(getLayoutDirection()); + } + mPrivateFlags2 |= PFLAG2_DRAWABLE_RESOLVED; + onResolveDrawables(getLayoutDirection()); } - mPrivateFlags2 |= PFLAG2_DRAWABLE_RESOLVED; - onResolveDrawables(getLayoutDirection()); } /** diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 7f785cd..00723f3 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3384,7 +3384,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (child.isLayoutDirectionInherited()) { child.resetRtlProperties(); - child.resolveRtlPropertiesIfNeeded(); } onViewAdded(child); diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 19b825c..495e46b 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -144,6 +144,8 @@ public class Editor { CharSequence mError; boolean mErrorWasChanged; ErrorPopup mErrorPopup; + private int mLastLayoutDirection = -1; + /** * This flag is set if the TextView tries to display an error before it * is attached to the window (so its position is still unknown). @@ -288,23 +290,30 @@ public class Editor { public void setError(CharSequence error, Drawable icon) { mError = TextUtils.stringOrSpannedString(error); mErrorWasChanged = true; - final Drawables dr = mTextView.mDrawables; - if (dr != null) { - switch (mTextView.getLayoutDirection()) { + final int layoutDirection = mTextView.getLayoutDirection(); + if (mLastLayoutDirection != layoutDirection) { + final Drawables dr = mTextView.mDrawables; + switch (layoutDirection) { default: case View.LAYOUT_DIRECTION_LTR: - mTextView.setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, icon, - dr.mDrawableBottom); + if (dr != null) { + mTextView.setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, icon, + dr.mDrawableBottom); + } else { + mTextView.setCompoundDrawables(null, null, icon, null); + } break; case View.LAYOUT_DIRECTION_RTL: - mTextView.setCompoundDrawables(icon, dr.mDrawableTop, dr.mDrawableRight, - dr.mDrawableBottom); + if (dr != null) { + mTextView.setCompoundDrawables(icon, dr.mDrawableTop, dr.mDrawableRight, + dr.mDrawableBottom); + } else { + mTextView.setCompoundDrawables(icon, null, null, null); + } break; } - } else { - mTextView.setCompoundDrawables(null, null, icon, null); + mLastLayoutDirection = layoutDirection; } - if (mError == null) { if (mErrorPopup != null) { if (mErrorPopup.isShowing()) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8e5ff40..a46481c 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -306,7 +306,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private Layout.Alignment mLayoutAlignment; private int mResolvedTextAlignment; - private boolean mResolvedDrawables; + private int mLastLayoutDirection = -1; /** * On some devices the fading edges add a performance penalty if used @@ -8260,16 +8260,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public void onResolveDrawables(int layoutDirection) { // No need to resolve twice - if (mResolvedDrawables) { + if (mLastLayoutDirection == layoutDirection) { return; } + mLastLayoutDirection = layoutDirection; // No drawable to resolve if (mDrawables == null) { return; } // No relative drawable to resolve if (mDrawables.mDrawableStart == null && mDrawables.mDrawableEnd == null) { - mResolvedDrawables = true; return; } @@ -8307,7 +8307,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; } updateDrawablesLayoutDirection(dr, layoutDirection); - mResolvedDrawables = true; } private void updateDrawablesLayoutDirection(Drawables dr, int layoutDirection) { @@ -8329,7 +8328,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @hide */ protected void resetResolvedDrawables() { - mResolvedDrawables = false; + mLastLayoutDirection = -1; } /** diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 3f40f20..f6ae83c 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -16,10 +16,6 @@ package com.android.internal.widget; -import com.android.internal.R; -import com.android.internal.telephony.ITelephony; -import com.google.android.collect.Lists; - import android.app.ActivityManagerNative; import android.app.admin.DevicePolicyManager; import android.content.ContentResolver; @@ -29,7 +25,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,6 +38,10 @@ import android.util.Log; import android.view.View; import android.widget.Button; +import com.android.internal.R; +import com.android.internal.telephony.ITelephony; +import com.google.android.collect.Lists; + import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -125,6 +124,11 @@ public class LockPatternUtils { */ public static final int FLAG_BIOMETRIC_WEAK_LIVELINESS = 0x1; + /** + * Pseudo-appwidget id we use to represent the default clock status widget + */ + public static final int ID_DEFAULT_STATUS_WIDGET = -2; + protected final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently"; protected final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline"; protected final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen"; @@ -146,6 +150,7 @@ public class LockPatternUtils { private final ContentResolver mContentResolver; private DevicePolicyManager mDevicePolicyManager; private ILockSettings mLockSettingsService; + private int mStickyWidgetIndex = -1; // The current user is set by KeyguardViewMediator and shared by all LockPatternUtils. private static volatile int sCurrentUserId = UserHandle.USER_NULL; @@ -1045,28 +1050,116 @@ 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); - if (appWidgetIdString != null) { - appWidgetId = (int) Integer.decode(appWidgetIdString); + String delims = ","; + if (appWidgetIdString != null && appWidgetIdString.length() > 0) { + 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) { + Log.d(TAG, "Error when parsing widget id " + appWidget); + return null; + } + } + return appWidgetIds; + } + if (appWidgetIdString == null) { + return new int[] { LockPatternUtils.ID_DEFAULT_STATUS_WIDGET }; + } else { + 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; + } } - return new int[] { appWidgetId }; + StringBuilder sb = new StringBuilder(strLength); + + for (int i = 0; i < listLength; i++) { + sb.append(list[i]); + if (i < listLength - 1) { + sb.append(separator); + } + } + + 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); + } + + // TODO: log an error if this returns false + public boolean addAppWidget(int widgetId, int index) { + int[] widgets = getAppWidgets(); + if (widgets == null) { + return false; + } + if (index < 0 || index > widgets.length) { + return false; } + 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 true; + } - return appWidgetId; + public boolean removeAppWidget(int widgetId) { + int[] widgets = getAppWidgets(); + + int[] newWidgets = new int[widgets.length - 1]; + for (int i = 0, j = 0; i < widgets.length; i++) { + if (widgets[i] == widgetId) { + // continue... + } else if (j >= newWidgets.length) { + // we couldn't find the widget + return false; + } else { + newWidgets[j] = widgets[i]; + j++; + } + } + writeAppWidgets(newWidgets); + return true; } private long getLong(String secureSettingKey, long defaultValue) { @@ -1218,4 +1311,12 @@ public class LockPatternUtils { return getBoolean(LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, true); } + public int getStickyWidgetIndex() { + return mStickyWidgetIndex; + } + + public void setStickyWidgetIndex(int stickyWidgetIndex) { + mStickyWidgetIndex = stickyWidgetIndex; + } + } diff --git a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java index 0f49776..b7f64ec 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 7d5516a..6293432 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 08c8b2b..4ab0229 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 0000000..fb64a52 --- /dev/null +++ b/core/res/res/drawable-hdpi/add_widget.png diff --git a/core/res/res/drawable-hdpi/kg_bouncer_bg_white.9.png b/core/res/res/drawable-hdpi/kg_bouncer_bg_white.9.png Binary files differnew file mode 100644 index 0000000..c34fe20 --- /dev/null +++ b/core/res/res/drawable-hdpi/kg_bouncer_bg_white.9.png diff --git a/core/res/res/drawable-hdpi/kg_security_grip.9.png b/core/res/res/drawable-hdpi/kg_security_grip.9.png Binary files differnew file mode 100644 index 0000000..fb1c866 --- /dev/null +++ b/core/res/res/drawable-hdpi/kg_security_grip.9.png diff --git a/core/res/res/drawable-hdpi/kg_security_lock.png b/core/res/res/drawable-hdpi/kg_security_lock.png Binary files differnew file mode 100644 index 0000000..136d3ad --- /dev/null +++ b/core/res/res/drawable-hdpi/kg_security_lock.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 0000000..9eeadc4 --- /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 0000000..bd4640f --- /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 0000000..f1bcf48 --- /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 0000000..ae26787 --- /dev/null +++ b/core/res/res/drawable-mdpi/add_widget.png diff --git a/core/res/res/drawable-mdpi/kg_bouncer_bg_white.9.png b/core/res/res/drawable-mdpi/kg_bouncer_bg_white.9.png Binary files differnew file mode 100644 index 0000000..f636524 --- /dev/null +++ b/core/res/res/drawable-mdpi/kg_bouncer_bg_white.9.png diff --git a/core/res/res/drawable-mdpi/kg_security_grip.9.png b/core/res/res/drawable-mdpi/kg_security_grip.9.png Binary files differnew file mode 100644 index 0000000..25beb2b --- /dev/null +++ b/core/res/res/drawable-mdpi/kg_security_grip.9.png diff --git a/core/res/res/drawable-mdpi/kg_security_lock.png b/core/res/res/drawable-mdpi/kg_security_lock.png Binary files differnew file mode 100644 index 0000000..861760d --- /dev/null +++ b/core/res/res/drawable-mdpi/kg_security_lock.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 0000000..9eeadc4 --- /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 0000000..bd4640f --- /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 0000000..d5a7708 --- /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 0000000..f1bcf48 --- /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 0000000..d5a7708 --- /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 0000000..55174e0 --- /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 0000000..c02700c --- /dev/null +++ b/core/res/res/drawable-xhdpi/add_widget.png diff --git a/core/res/res/drawable-xhdpi/default_wallpaper.jpg b/core/res/res/drawable-xhdpi/default_wallpaper.jpg Binary files differindex 5b8d1d5..da9fa91 100644 --- a/core/res/res/drawable-xhdpi/default_wallpaper.jpg +++ b/core/res/res/drawable-xhdpi/default_wallpaper.jpg diff --git a/core/res/res/drawable-xhdpi/kg_bouncer_bg_white.9.png b/core/res/res/drawable-xhdpi/kg_bouncer_bg_white.9.png Binary files differnew file mode 100644 index 0000000..9c4a603 --- /dev/null +++ b/core/res/res/drawable-xhdpi/kg_bouncer_bg_white.9.png diff --git a/core/res/res/drawable-xhdpi/kg_security_grip.9.png b/core/res/res/drawable-xhdpi/kg_security_grip.9.png Binary files differnew file mode 100644 index 0000000..b5cd134 --- /dev/null +++ b/core/res/res/drawable-xhdpi/kg_security_grip.9.png diff --git a/core/res/res/drawable-xhdpi/kg_security_lock.png b/core/res/res/drawable-xhdpi/kg_security_lock.png Binary files differnew file mode 100644 index 0000000..4544584 --- /dev/null +++ b/core/res/res/drawable-xhdpi/kg_security_lock.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 0000000..9eeadc4 --- /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 0000000..bd4640f --- /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 0000000..55174e0 --- /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 521853f..bb455bd 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:id="@+id/app_widget_container" + 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"/> + + <View android:layout_width="match_parent" + android:layout_height="match_parent" + androidprv:layout_childType="scrim" + android:background="#99000000" /> - </com.android.internal.policy.impl.keyguard.KeyguardSecurityViewFlipper> + <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-land/keyguard_widget_pager.xml b/core/res/res/layout-land/keyguard_widget_pager.xml new file mode 100644 index 0000000..8b7b9a3 --- /dev/null +++ b/core/res/res/layout-land/keyguard_widget_pager.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License") +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- This is the selector widget that allows the user to select an action. --> +<com.android.internal.policy.impl.keyguard.KeyguardWidgetCarousel + xmlns:androidprv="http://schemas.android.com/apk/res/android" + xmlns:android="http://schemas.android.com/apk/res/android" + 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.KeyguardWidgetCarousel> diff --git a/core/res/res/layout-port/keyguard_host_view.xml b/core/res/res/layout-port/keyguard_host_view.xml index 981fe6d..15e9844 100644 --- a/core/res/res/layout-port/keyguard_host_view.xml +++ b/core/res/res/layout-port/keyguard_host_view.xml @@ -21,28 +21,57 @@ 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/kg_security_grip" + androidprv:dragIcon="@drawable/kg_security_lock"> - <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:id="@+id/app_widget_container" + 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:layout_marginLeft="@dimen/kg_edge_swipe_region_size" + android:layout_marginRight="@dimen/kg_edge_swipe_region_size" + android:background="@drawable/kg_bouncer_bg_white" + android:gravity="bottom|center_horizontal"> + <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-port/keyguard_widget_pager.xml b/core/res/res/layout-port/keyguard_widget_pager.xml new file mode 100644 index 0000000..6662f83 --- /dev/null +++ b/core/res/res/layout-port/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-sw600dp-port/keyguard_host_view.xml b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml index 23c1e9c..3953c95 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,54 @@ 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:id="@+id/app_widget_container" + 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 1eef099..930b14e 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 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 0000000..fa811d7 --- /dev/null +++ b/core/res/res/layout/keyguard_add_widget.xml @@ -0,0 +1,40 @@ +<?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" + > + <FrameLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + > + <ImageView + android:id="@+id/keyguard_add_widget_view" + android:clickable="true" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:padding="24dp" + android:src="@drawable/add_widget" /> + </FrameLayout> +</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 68840ab..eeb4178 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 845c265..00f14f5 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 0e851e3..2d8f02d 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 5a6e998..ee01285 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 ad9fdfe..fc126fe 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 81916f7..e28f2ac 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 0000000..9b883af --- /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 1de0f6c..9532a88 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 5a6083a..532322c 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_region.xml b/core/res/res/layout/keyguard_widget_region.xml deleted file mode 100644 index ed10c2b..0000000 --- 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-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index fdd35a1..d63f85d 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -585,7 +585,7 @@ <string name="permdesc_writeDictionary" msgid="8185385716255065291">"Allows the app to write new words into the user dictionary."</string> <string name="permlab_sdcardRead" product="nosdcard" msgid="8235341515605559677">"test access to protected storage"</string> <string name="permlab_sdcardRead" product="default" msgid="8235341515605559677">"test access to protected storage"</string> - <string name="permdesc_sdcardRead" product="nosdcard" msgid="5791957130190763289">"Allows the app to test a permission for USB storage that will be availabe on future devices."</string> + <string name="permdesc_sdcardRead" product="nosdcard" msgid="5791957130190763289">"Allows the app to test a permission for USB storage that will be available on future devices."</string> <string name="permdesc_sdcardRead" product="default" msgid="5914402684685848828">"Allows the app to test a permission for the SD card that will be available on future devices."</string> <string name="permlab_sdcardWrite" product="nosdcard" msgid="8485979062254666748">"modify or delete the contents of your USB storage"</string> <string name="permlab_sdcardWrite" product="default" msgid="8805693630050458763">"modify or delete the contents of your SD card"</string> diff --git a/core/res/res/values-land/bools.xml b/core/res/res/values-land/bools.xml index b0630ad..85c64d9 100644 --- a/core/res/res/values-land/bools.xml +++ b/core/res/res/values-land/bools.xml @@ -15,6 +15,7 @@ --> <resources> + <bool name="kg_enable_camera_default_widget">false</bool> <bool name="kg_share_status_area">false</bool> <bool name="kg_sim_puk_account_full_screen">false</bool> </resources> diff --git a/core/res/res/values-sw600dp/bools.xml b/core/res/res/values-sw600dp/bools.xml index 3753aba..eae4f87 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 564545a..0d01df4 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-sw600dp/integers.xml b/core/res/res/values-sw600dp/integers.xml new file mode 100644 index 0000000..de9829c --- /dev/null +++ b/core/res/res/values-sw600dp/integers.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<resources> + <integer name="kg_carousel_angle">60</integer> +</resources> diff --git a/core/res/res/values-sw720dp/dimens.xml b/core/res/res/values-sw720dp/dimens.xml index 6144961..d6d2b66 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 8615476..8744bfe 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 3550df9..48d4745 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,58 @@ <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" /> + <attr name="dragIcon" 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 f9762b1..d4ead01 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 6a93f30..b19e23d 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 948a3d3..8a7632c 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -242,8 +242,8 @@ <dimen name="notification_subtext_size">12dp</dimen> <!-- Keyguard dimensions --> - <!-- Width of security view in keyguard. --> - <dimen name="kg_glow_pad_size">500dp</dimen> + <!-- TEMP --> + <dimen name="kg_security_panel_height">600dp</dimen> <!-- Height of security view in keyguard. --> <dimen name="kg_security_view_height">0dp</dimen> @@ -305,4 +305,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">24dp</dimen> </resources> diff --git a/core/res/res/values/integers.xml b/core/res/res/values/integers.xml index 6d49a91..91ad5d8 100644 --- a/core/res/res/values/integers.xml +++ b/core/res/res/values/integers.xml @@ -17,6 +17,7 @@ */ --> <resources> + <integer name="kg_carousel_angle">75</integer> <integer name="kg_security_flip_duration">75</integer> <integer name="kg_security_fade_duration">75</integer> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index f8dbd84..47a8fc5 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1721,7 +1721,7 @@ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_sdcardRead" product="default">test access to protected storage</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] --> - <string name="permdesc_sdcardRead" product="nosdcard">Allows the app to test a permission for USB storage that will be availabe on future devices. </string> + <string name="permdesc_sdcardRead" product="nosdcard">Allows the app to test a permission for USB storage that will be available on future devices. </string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_sdcardRead" product="default">Allows the app to test a permission for the SD card that will be available on future devices.</string> @@ -3950,5 +3950,7 @@ <string name="enable_accessibility_canceled">Accessibility canceled.</string> <!-- Text spoken when the current user is switched if accessibility is enabled. [CHAR LIMIT=none] --> <string name="user_switched">Current user <xliff:g id="name" example="Bob">%1$s</xliff:g>.</string> + <!-- Default name of the owner user [CHAR LIMIT=20] --> + <string name="owner_name" msgid="3879126011135546571">Owner</string> </resources> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 4371aec..da26e6a 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -2478,4 +2478,29 @@ 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" + parent="@android:style/Widget.Button"> + <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" + parent="@android:style/TextAppearance"> + <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 8ef91df..60a2e91 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -842,6 +842,7 @@ <java-symbol type="string" name="media_route_status_connecting" /> <java-symbol type="string" name="media_route_status_available" /> <java-symbol type="string" name="media_route_status_not_available" /> + <java-symbol type="string" name="owner_name" /> <java-symbol type="plurals" name="abbrev_in_num_days" /> <java-symbol type="plurals" name="abbrev_in_num_hours" /> @@ -962,6 +963,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 +1088,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 +1179,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 +1194,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 +1212,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 +1234,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 +1293,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 +1318,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" /> @@ -1318,6 +1335,7 @@ <java-symbol type="integer" name="config_lidOpenRotation" /> <java-symbol type="integer" name="config_longPressOnHomeBehavior" /> <java-symbol type="integer" name="kg_security_flip_duration" /> + <java-symbol type="integer" name="kg_carousel_angle" /> <java-symbol type="layout" name="global_actions_item" /> <java-symbol type="layout" name="global_actions_silent_mode" /> <java-symbol type="layout" name="keyguard_screen_glogin_unlock" /> @@ -1334,6 +1352,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 +1422,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/docs/downloads/training/Animations.zip b/docs/downloads/training/Animations.zip Binary files differnew file mode 100644 index 0000000..5063dd1 --- /dev/null +++ b/docs/downloads/training/Animations.zip diff --git a/docs/html/training/animation/anim_card_flip.mp4 b/docs/html/training/animation/anim_card_flip.mp4 Binary files differnew file mode 100755 index 0000000..e885f98 --- /dev/null +++ b/docs/html/training/animation/anim_card_flip.mp4 diff --git a/docs/html/training/animation/anim_card_flip.ogv b/docs/html/training/animation/anim_card_flip.ogv Binary files differnew file mode 100755 index 0000000..33cd86c --- /dev/null +++ b/docs/html/training/animation/anim_card_flip.ogv diff --git a/docs/html/training/animation/anim_card_flip.webm b/docs/html/training/animation/anim_card_flip.webm Binary files differnew file mode 100755 index 0000000..a670d78 --- /dev/null +++ b/docs/html/training/animation/anim_card_flip.webm diff --git a/docs/html/training/animation/anim_crossfade.mp4 b/docs/html/training/animation/anim_crossfade.mp4 Binary files differnew file mode 100644 index 0000000..ced7cc9 --- /dev/null +++ b/docs/html/training/animation/anim_crossfade.mp4 diff --git a/docs/html/training/animation/anim_crossfade.ogv b/docs/html/training/animation/anim_crossfade.ogv Binary files differnew file mode 100644 index 0000000..7ec417a --- /dev/null +++ b/docs/html/training/animation/anim_crossfade.ogv diff --git a/docs/html/training/animation/anim_crossfade.webm b/docs/html/training/animation/anim_crossfade.webm Binary files differnew file mode 100644 index 0000000..21e7228 --- /dev/null +++ b/docs/html/training/animation/anim_crossfade.webm diff --git a/docs/html/training/animation/anim_layout_changes.mp4 b/docs/html/training/animation/anim_layout_changes.mp4 Binary files differnew file mode 100644 index 0000000..0709601 --- /dev/null +++ b/docs/html/training/animation/anim_layout_changes.mp4 diff --git a/docs/html/training/animation/anim_layout_changes.ogv b/docs/html/training/animation/anim_layout_changes.ogv Binary files differnew file mode 100644 index 0000000..75f5250 --- /dev/null +++ b/docs/html/training/animation/anim_layout_changes.ogv diff --git a/docs/html/training/animation/anim_layout_changes.webm b/docs/html/training/animation/anim_layout_changes.webm Binary files differnew file mode 100644 index 0000000..a99a566 --- /dev/null +++ b/docs/html/training/animation/anim_layout_changes.webm diff --git a/docs/html/training/animation/anim_screenslide.mp4 b/docs/html/training/animation/anim_screenslide.mp4 Binary files differnew file mode 100755 index 0000000..3e65026 --- /dev/null +++ b/docs/html/training/animation/anim_screenslide.mp4 diff --git a/docs/html/training/animation/anim_screenslide.ogv b/docs/html/training/animation/anim_screenslide.ogv Binary files differnew file mode 100755 index 0000000..c45ebd4 --- /dev/null +++ b/docs/html/training/animation/anim_screenslide.ogv diff --git a/docs/html/training/animation/anim_screenslide.webm b/docs/html/training/animation/anim_screenslide.webm Binary files differnew file mode 100755 index 0000000..c72adbc --- /dev/null +++ b/docs/html/training/animation/anim_screenslide.webm diff --git a/docs/html/training/animation/anim_zoom.mp4 b/docs/html/training/animation/anim_zoom.mp4 Binary files differnew file mode 100644 index 0000000..4326c35 --- /dev/null +++ b/docs/html/training/animation/anim_zoom.mp4 diff --git a/docs/html/training/animation/anim_zoom.ogv b/docs/html/training/animation/anim_zoom.ogv Binary files differnew file mode 100644 index 0000000..e5793f3 --- /dev/null +++ b/docs/html/training/animation/anim_zoom.ogv diff --git a/docs/html/training/animation/anim_zoom.webm b/docs/html/training/animation/anim_zoom.webm Binary files differnew file mode 100644 index 0000000..b3b7566 --- /dev/null +++ b/docs/html/training/animation/anim_zoom.webm diff --git a/docs/html/training/animation/cardflip.jd b/docs/html/training/animation/cardflip.jd new file mode 100644 index 0000000..ab3eb3a --- /dev/null +++ b/docs/html/training/animation/cardflip.jd @@ -0,0 +1,365 @@ +page.title=Displaying Card Flip Animations +trainingnavtop=true + +@jd:body + <div id="tb-wrapper"> + <div id="tb"> + <h2> + This lesson teaches you to + </h2> + <ol> + <li> + <a href="#animators">Create the Animators</a> + </li> + <li> + <a href="#views">Create the Views</a> + </li> + <li> + <a href="#fragment">Create the Fragment</a> + </li> + <li> + <a href="#animate">Animate the Card Flip</a> + </li> + </ol> + </div> + </div> + <p> This lesson shows you how to do a card flip + animation with custom fragment animations. + Card flips animate between views of content by showing an animation that emulates + a card flipping over. + </p> + <p>Here's what a card flip looks like: + </p> + + <div class="framed-galaxynexus-land-span-8"> + <video class="play-on-hover" autoplay> + <source src="anim_card_flip.mp4" type="video/mp4"> + <source src="anim_card_flip.webm" type="video/webm"> + <source src="anim_card_flip.ogv" type="video/ogg"> + </video> + </div> + <div class="figure-caption"> + Card flip animation + <div class="video-instructions"> </div> + </div> + + <p> + If you want to jump ahead and see a full working example, + <a href="{@docRoot}shareables/training/Animations.zip">download</a> and + run the sample app and select the Card Flip example. See the following + files for the code implementation: + </p> + <ul> + <li> + <code>src/CardFlipActivity.java</code> + </li> + <li> + <code>animator/card_flip_right_in.xml</code> + </li> + <li> + <code>animator/card_flip_right_out.xml</code> + </li> + <li> + <code>animator/card_flip_right_in.xml</code> + </li> + <li> + <code>animator/card_flip_left_out.xml</code> + </li> + <li> + <code>layout/fragment_card_back.xml</code> + </li> + <li> + <code>layout/fragment_card_front.xml</code> + </li> + </ul> + + <h2 id="animate"> + Create the Animators + </h2> + <p> + Create the animations for the card flips. You'll need two animators for when the front + of the card animates out and to the left and in and from the left. You'll also need two animators + for when the back of the card animates in and from the right and out and to the right. + </p> + <h4> + card_flip_left_in.xml + </h4> +<pre> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Before rotating, immediately set the alpha to 0. --> + <objectAnimator + android:valueFrom="1.0" + android:valueTo="0.0" + android:propertyName="alpha" + android:duration="0" /> + + <!-- Rotate. --> + <objectAnimator + android:valueFrom="-180" + android:valueTo="0" + android:propertyName="rotationY" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:duration="@integer/card_flip_time_full" /> + + <!-- Half-way through the rotation (see startOffset), set the alpha to 1. --> + <objectAnimator + android:valueFrom="0.0" + android:valueTo="1.0" + android:propertyName="alpha" + android:startOffset="@integer/card_flip_time_half" + android:duration="1" /> +</set> +</pre> + <h4> + card_flip_left_out.xml + </h4> + <pre> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Rotate. --> + <objectAnimator + android:valueFrom="0" + android:valueTo="180" + android:propertyName="rotationY" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:duration="@integer/card_flip_time_full" /> + + <!-- Half-way through the rotation (see startOffset), set the alpha to 0. --> + <objectAnimator + android:valueFrom="1.0" + android:valueTo="0.0" + android:propertyName="alpha" + android:startOffset="@integer/card_flip_time_half" + android:duration="1" /> +</set> + </pre> + <h4> + card_flip_right_in.xml + </h4> + <pre> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Before rotating, immediately set the alpha to 0. --> + <objectAnimator + android:valueFrom="1.0" + android:valueTo="0.0" + android:propertyName="alpha" + android:duration="0" /> + + <!-- Rotate. --> + <objectAnimator + android:valueFrom="180" + android:valueTo="0" + android:propertyName="rotationY" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:duration="@integer/card_flip_time_full" /> + + <!-- Half-way through the rotation (see startOffset), set the alpha to 1. --> + <objectAnimator + android:valueFrom="0.0" + android:valueTo="1.0" + android:propertyName="alpha" + android:startOffset="@integer/card_flip_time_half" + android:duration="1" /> +</set> + +</pre> + <h4> + card_flip_right_out.xml + </h4> + <pre> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Rotate. --> + <objectAnimator + android:valueFrom="0" + android:valueTo="-180" + android:propertyName="rotationY" + android:interpolator="@android:interpolator/accelerate_decelerate" + android:duration="@integer/card_flip_time_full" /> + + <!-- Half-way through the rotation (see startOffset), set the alpha to 0. --> + <objectAnimator + android:valueFrom="1.0" + android:valueTo="0.0" + android:propertyName="alpha" + android:startOffset="@integer/card_flip_time_half" + android:duration="1" /> +</set> +</pre> + <h2 id="views"> + Create the Views + </h2> + <p> + Each side of the "card" is a separate layout that can contain any content you want, + such as two screens of text, two images, or any combination of views to flip between. You'll then + use the two layouts in the fragments that you'll later animate. The following layouts + create one side of a card that shows text: + </p> + + <pre> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="#a6c" + android:padding="16dp" + android:gravity="bottom"> + + <TextView android:id="@android:id/text1" + style="?android:textAppearanceLarge" + android:textStyle="bold" + android:textColor="#fff" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/card_back_title" /> + + <TextView style="?android:textAppearanceSmall" + android:textAllCaps="true" + android:textColor="#80ffffff" + android:textStyle="bold" + android:lineSpacingMultiplier="1.2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/card_back_description" /> + +</LinearLayout> +</pre> +<p> +and the other side of the card that displays an {@link android.widget.ImageView}: +</p> +<pre> +<ImageView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:src="@drawable/image1" + android:scaleType="centerCrop" + android:contentDescription="@string/description_image_1" /> +</pre> + <h2 id="fragment"> + Create the Fragment + </h2> + <p> + Create fragment classes for the front and back of the card. These classes return the layouts + that you created previously in the {@link android.app.Fragment#onCreateView onCreateView()} method + of each fragment. You can then create instances of this fragment in the parent activity + where you want to show the card. The following example shows nested fragment classes inside + of the parent activity that uses them: + </p> + <pre> +public class CardFlipActivity extends Activity { + ... + /** + * A fragment representing the front of the card. + */ + public class CardFrontFragment extends Fragment { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_card_front, container, false); + } + } + + /** + * A fragment representing the back of the card. + */ + public class CardBackFragment extends Fragment { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_card_back, container, false); + } + } +} +</pre> + <h2 id="animate"> + Animate the Card Flip + </h2> + + <p> Now, you'll need to display the fragments inside of a parent activity. + To do this, first create the layout for your activity. The following example creates a + {@link android.widget.FrameLayout} that you + can add fragments to at runtime:</p> + + <pre> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" /> +</pre> + + <p>In the activity code, set the content view to be the layout that you just created. It's also + good idea to show a default fragment when the activity is created, so the following example + activity shows you how to display the front of the card by default: + </p> + + + +<pre> +public class CardFlipActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_activity_card_flip); + + if (savedInstanceState == null) { + getFragmentManager() + .beginTransaction() + .add(R.id.container, new CardFrontFragment()) + .commit(); + } + } + ... +} +</pre> + <p> + Now that you have the front of the card showing, you can show the back of the card + with the flip animation at an appropriate time. Create a method to show the other + side of the card that does the following things: + </p> + <ul> + <li>Sets the custom animations that you created earlier for the fragment transitions. + </li> + <li>Replaces the currently displayed fragment with a new fragment and animates this event + with the custom animations that you created. + </li> + <li>Adds the previously displayed fragment to the fragment back stack + so when the user presses the <em>Back</em> button, the card flips back over. + </li> + </ul> + <pre> +private void flipCard() { + if (mShowingBack) { + getFragmentManager().popBackStack(); + return; + } + + // Flip to the back. + + mShowingBack = true; + + // Create and commit a new fragment transaction that adds the fragment for the back of + // the card, uses custom animations, and is part of the fragment manager's back stack. + + getFragmentManager() + .beginTransaction() + + // Replace the default fragment animations with animator resources representing + // rotations when switching to the back of the card, as well as animator + // resources representing rotations when flipping back to the front (e.g. when + // the system Back button is pressed). + .setCustomAnimations( + R.animator.card_flip_right_in, R.animator.card_flip_right_out, + R.animator.card_flip_left_in, R.animator.card_flip_left_out) + + // Replace any fragments currently in the container view with a fragment + // representing the next page (indicated by the just-incremented currentPage + // variable). + .replace(R.id.container, new CardBackFragment()) + + // Add this transaction to the back stack, allowing users to press Back + // to get to the front of the card. + .addToBackStack(null) + + // Commit the transaction. + .commit(); +} +</pre>
\ No newline at end of file diff --git a/docs/html/training/animation/crossfade.jd b/docs/html/training/animation/crossfade.jd new file mode 100644 index 0000000..99e879b --- /dev/null +++ b/docs/html/training/animation/crossfade.jd @@ -0,0 +1,208 @@ +page.title=Crossfading Two Views +trainingnavtop=true + + +@jd:body + + <div id="tb-wrapper"> + <div id="tb"> + <h2> + This lesson teaches you to: + </h2> + <ol> + <li> + <a href="#views">Create the Views</a> + </li> + <li> + <a href="#setup">Set up the Animation</a> + </li> + <li> + <a href="#animate">Crossfade the Views</a> + </li> + </ol> + </div> + </div> + + <p> + Crossfade animations (also know as dissolve) gradually fade out one UI component while simultaneously fading in + another. This animation is useful for situations where you want to switch content or views + in your app. Crossfades are very subtle and short but offer a fluid transition from one screen to the + next. When you don't use them, however, transitions often feel abrupt or hurried. + </p> + <p>Here's an example of a crossfade from a progress indicator to some text content. + </p> + +<div class="framed-galaxynexus-land-span-8"> + <video class="play-on-hover" autoplay> + <source src="anim_crossfade.mp4" type="video/mp4"> + <source src="anim_crossfade.webm" type="video/webm"> + <source src="anim_crossfade.ogv" type="video/ogg"> + </video> +</div> +<div class="figure-caption"> +Crossfade animation + <div class="video-instructions"> </div> +</div> + + <p> + If you want to jump ahead and see a full working example, + <a href="{@docRoot}shareables/training/Animations.zip">download</a> + and run the sample app and select the Crossfade example. + See the following files for the code implementation: + </p> + <ul> + <li> + <code>src/CrossfadeActivity.java</code> + </li> + <li> + <code>layout/activity_crossfade.xml</code> + </li> + <li> + <code>menu/activity_crossfade.xml</code> + </li> + </ul> + <h2 id="views"> + Create the Views + </h2> + <p> + Create the two views that you want to crossfade. The following example creates a progress + indicator and a scrollable text view: + </p> + <pre> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView style="?android:textAppearanceMedium" + android:lineSpacingMultiplier="1.2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/lorem_ipsum" + android:padding="16dp" /> + + </ScrollView> + + <ProgressBar android:id="@+id/loading_spinner" + style="?android:progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" /> + +</FrameLayout> +</pre> + <h2 id="setup"> + Set up the Animation + </h2> + <p> + To set up the animation: + </p> + <ol> + <li>Create member variables for the views that you want to crossfade. You need + these references later when modifying the views during the animation. + </li> + <li>For the view that is being faded in, set its visibility to {@link + android.view.View#GONE}. This prevents the view from taking up layout space and omits it + from layout calculations, speeding up processing. + </li> + <li>Cache the <code>{@link android.R.integer#config_shortAnimTime}</code> + system property in a member variable. This property defines a standard + "short" duration for the animation. This duration is ideal for subtle animations or + animations that occur very frequently. {@link android.R.integer#config_longAnimTime} and + {@link android.R.integer#config_mediumAnimTime} are also available if you wish to use them. + </li> + </ol> + <p> + Here's an example using the layout from the previous code snippet as the activity content + view: + </p> + <pre> +public class CrossfadeActivity extends Activity { + + private View mContentView; + private View mLoadingView; + private int mShortAnimationDuration; + + ... + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_crossfade); + + mContentView = findViewById(R.id.content); + mLoadingView = findViewById(R.id.loading_spinner); + + // Initially hide the content view. + mContentView.setVisibility(View.GONE); + + // Retrieve and cache the system's default "short" animation time. + mShortAnimationDuration = getResources().getInteger( + android.R.integer.config_shortAnimTime); + } + +</pre> + <h2 id="animate"> + Crossfade the Views + </h2> + <p> + Now that the views are properly set up, crossfade them by doing the following: + </p> + <ol> + <li>For the view that is fading in, set the alpha value to <code>0</code> and the visibility + to {@link android.view.View#VISIBLE}. (Remember that it was initially set to {@link + android.view.View#GONE}.) This makes the view visible but completely transparent. + </li> + <li>For the view that is fading in, animate its alpha value from <code>0</code> to + <code>1</code>. At the same time, for the view that is fading out, animate the alpha value + from <code>1</code> to <code>0</code>. + </li> + <li>Using {@link android.animation.Animator.AnimatorListener#onAnimationEnd onAnimationEnd()} + in an {@link android.animation.Animator.AnimatorListener}, set the visibility of the view + that was fading out to {@link android.view.View#GONE}. Even though the alpha value is <code>0</code>, + setting the view's visibility to {@link android.view.View#GONE} prevents the view from taking + up layout space and omits it from layout calculations, speeding up processing. + </li> + </ol> + <p> + The following method shows an example of how to do this: + </p> + <pre> +private View mContentView; +private View mLoadingView; +private int mShortAnimationDuration; + +... + +private void crossfade() { + + // Set the content view to 0% opacity but visible, so that it is visible + // (but fully transparent) during the animation. + mContentView.setAlpha(0f); + mContentView.setVisibility(View.VISIBLE); + + // Animate the content view to 100% opacity, and clear any animation + // listener set on the view. + mContentView.animate() + .alpha(1f) + .setDuration(mShortAnimationDuration) + .setListener(null); + + // Animate the loading view to 0% opacity. After the animation ends, + // set its visibility to GONE as an optimization step (it won't + // participate in layout passes, etc.) + mHideView.animate() + .alpha(0f) + .setDuration(mShortAnimationDuration) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mHideView.setVisibility(View.GONE); + } + }); +} +</pre>
\ No newline at end of file diff --git a/docs/html/training/animation/index.jd b/docs/html/training/animation/index.jd new file mode 100644 index 0000000..9cc7e6c --- /dev/null +++ b/docs/html/training/animation/index.jd @@ -0,0 +1,86 @@ +page.title=Adding Animations +trainingnavtop=true +startpage=true + +@jd:body + <div id="tb-wrapper"> + <div id="tb"> + <h2> + Dependencies and prerequisites + </h2> + <ul> + <li>Android 4.0 or later + </li> + <li>Experience building an Android <a href="{@docRoot}guide/topics/ui/index.html">User + Interface</a> + </li> + </ul> + <h2> + You should also read + </h2> + <ul> + <li> + <a href="{@docRoot}guide/topics/graphics/prop-animation.html">Property Animation</a> + </li> + </ul> + <h2> + Try it out + </h2> + <div class="download-box"> + <a href="{@docRoot}shareables/training/Animations.zip" class= + "button">Download the sample app</a> + <p class="filename"> + Animations.zip + </p> + </div> + </div> + </div> + <p> + Animations can add subtle visual cues that notify users about what's going on in your app and + improve their mental model of your app's interface. Animations are especially useful when the + screen changes state, such as when content loads or new actions become available. Animations + can also add a polished look to your app, which gives your app a higher quality feel. + </p> + <p> + Keep in mind though, that overusing animations or using them at the wrong time can be + detrimental, such as when they cause delays. This training class shows you how to + implement some common types of animations that can increase usability and add flair without + annoying your users. + </p> + + <h2> + Lessons + </h2> + <dl> + <dt> + <b><a href="crossfade.html">Crossfading Two Views</a></b> + </dt> + <dd> + Learn how to crossfade between two overlapping views. This lesson shows you how to crossfade a progress + indicator to a view that contains text content. + </dd> + <dt> + <b><a href="screen-slide.html">Using ViewPager for Screen Slides</a></b> + </dt> + <dd> + Learn how to animate between horizontally adjacent screens with a sliding transition. + </dd> + <dt> + <b><a href="cardflip.html">Displaying Card Flip Animations</a></b> + </dt> + <dd> + Learn how to animate between two views with a flipping motion. + </dd> + <dt> + <b><a href="zoom.html">Zooming a View</a></b> + </dt> + <dd> + Learn how to enlarge views with a touch-to-zoom animation. + </dd> + <dt> + <b><a href="layout.html">Animating Layout Changes</a></b> + </dt> + <dd> + Learn how to enable built-in animations when adding, removing, or updating child views in a layout. + </dd> + </dl>
\ No newline at end of file diff --git a/docs/html/training/animation/layout.jd b/docs/html/training/animation/layout.jd new file mode 100644 index 0000000..b8e0077 --- /dev/null +++ b/docs/html/training/animation/layout.jd @@ -0,0 +1,77 @@ +page.title=Animating Layout Changes +trainingnavtop=true + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to:</h2> + <ol> + <li><a href="#views">Create the Layout</a></li> + <li><a href="#add">Add, Update, or Remove Items from the Layout</a></li> + </ol> + +</div> +</div> + + <p>A layout animation is a pre-loaded animation that the system runs each time you make a change + to the layout configuration. All you need to do is set an attribute in the layout to tell the + Android system to animate these layout changes, and system-default animations are carried out for you. + </p> +<p class="note"><strong>Tip</strong>: If you want to supply custom layout animations, +create a {@link android.animation.LayoutTransition} object and supply it to +the layout with the {@link android.view.ViewGroup#setLayoutTransition setLayoutTransition()} +method. +</p> + Here's what a default layout animation looks like when adding items to a list: +</p> + + <div class="framed-galaxynexus-land-span-8"> + <video class="play-on-hover" autoplay> + <source src="anim_layout_changes.mp4" type="video/mp4"> + <source src="anim_layout_changes.webm" type="video/webm"> + <source src="anim_layout_changes.ogv" type="video/ogg"> + </video> + </div> + <div class="figure-caption"> + Layout animation + <div class="video-instructions"> </div> + </div> + +<p>If you want to jump ahead and see a full working example, +<a href="{@docRoot}shareables/training/Animations.zip">download</a> and +run the sample app and select the Crossfade example. See the following files for the +code implementation:</p> +<ol> + <li><code>src/LayoutChangesActivity.java</code></li> + <li><code>layout/activity_layout_changes.xml</code></li> + <li><code>menu/activity_layout_changes.xml</code></li> +</ol> + +<h2 id="views">Create the Layout</h2> +<p>In your activity's layout XML file, set the <code>android:animateLayoutChanges</code> + attribute to <code>true</code> for the layout that you want to enable animations for. + For instance:</p> + +<pre> +<LinearLayout android:id="@+id/container" + android:animateLayoutChanges="true" + ... +/> +</pre> + +<h2 id="activity">Add, Update, or Remove Items from the Layout</h2> +<p> +Now, all you need to do is add, remove, or update items in the layout +and the items are animated automatically: +</p> +<pre> +private ViewGroup mContainerView; +... +private void addItem() { + View newView; + ... + mContainerView.addView(newView, 0); +} +</pre> diff --git a/docs/html/training/animation/screen-slide.jd b/docs/html/training/animation/screen-slide.jd new file mode 100755 index 0000000..8a7af67 --- /dev/null +++ b/docs/html/training/animation/screen-slide.jd @@ -0,0 +1,185 @@ +page.title=Using ViewPager for Screen Slides +trainingnavtop=true + +@jd:body + + <div id="tb-wrapper"> + <div id="tb"> + <h2>This lesson teaches you to</h2> + <ol> + <li><a href="#views">Create the Views</a></li> + <li><a href="#fragment">Create the Fragment</a></li> + <li><a href="#viewpager">Animate the Screen Slide</a></li> + </ol> + </div> + </div> + <p> + Screen slides are transitions between one entire screen to another and are common with UIs + like setup wizards or slideshows. This lesson shows you how to do screen slides with + a {@link android.support.v4.view.ViewPager} provided by the <a href= + "{@docRoot}/tools/extras/support-library.html">support library</a>. + {@link android.support.v4.view.ViewPager}s can animate screen slides + automatically. Here's what a screen slide looks like that transitions from + one screen of content to the next: + </p> + + <div class="framed-galaxynexus-land-span-8"> + <video class="play-on-hover" autoplay> + <source src="anim_screenslide.mp4" type="video/mp4"> + <source src="anim_screenslide.webm" type="video/webm"> + <source src="anim_screenslide.ogv" type="video/ogg"> + </video> + </div> + + <div class="figure-caption"> + Screen slide animation + <div class="video-instructions"> </div> + </div> + +<p>If you want to jump ahead and see a full working example, +<a href="{@docRoot}shareables/training/Animations.zip">download</a> +and run the sample app and select the Screen Slide example. See the +following files for the code implementation:</p> +<ul> + <li><code>src/ScreenSlidePageFragment.java</code></li> + <li><code>src/ScreenSlideActivity.java</code></li> + <li><code>layout/activity_screen_slide.xml</code></li> + <li><code>layout/fragment_screen_slide_page.xml</code></li> +</ul> + +<h2 id="views">Create the Views</h2> + <p>Create a layout file that you'll later use for the content of a fragment. The following example + contains a text view to display some text: + +<pre> +<com.example.android.animationsdemo.ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView style="?android:textAppearanceMedium" + android:padding="16dp" + android:lineSpacingMultiplier="1.2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/lorem_ipsum" /> + +</com.example.android.animationsdemo.ScrollView> +</pre> + +<h2 id="fragment">Create the Fragment</h2> +<p>Create a {@link android.support.v4.app.Fragment} class that returns the layout +that you just created in the {@link android.app.Fragment#onCreateView onCreateView()} + method. You can then create instances of this fragment in the parent activity whenever you need a new page to + display to the user:</p> + + +<pre> +public class ScreenSlidePageFragment extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + ViewGroup rootView = (ViewGroup) inflater.inflate( + R.layout.fragment_screen_slide_page, container, false); + + return rootView; + } +} +</pre> + +<h2 id="viewpager">Screen Slides with ViewPager</h2> + +<p>{@link android.support.v4.view.ViewPager}s have built-in swipe gestures to transition + through pages, and they display screen slide animations by default, so you don't need to create any. {@link android.support.v4.view.ViewPager}s use +{@link android.support.v4.view.PagerAdapter}s as a supply for new pages to display, so the {@link android.support.v4.view.PagerAdapter} will use the +fragment class that you created earlier. + </p> + +<p>To begin, create a layout that contains a {@link android.support.v4.view.ViewPager}:</p> + +<pre> +<android.support.v4.view.ViewPager + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/pager" + android:layout_width="match_parent" + android:layout_height="match_parent" /> +</pre> + +<p>Create an activity that does the following things: +</p> + +<ul> + <li>Sets the content view to be the layout with the {@link android.support.v4.view.ViewPager}.</li> + <li>Create a class that extends the {@link android.support.v13.app.FragmentStatePagerAdapter} abstract class. Implement + the {@link android.support.v4.app.FragmentStatePagerAdapter#getItem getItem()} method to supply + instances of <code>ScreenSlidePageFragment</code> as new pages. The pager adapter also requires that you implement the + {@link android.support.v4.view.PagerAdapter#getCount getCount()} method, which returns the amount of pages the adapter will create (five in the example). + <li>Hooks up the {@link android.support.v4.view.PagerAdapter} to the {@link android.support.v4.view.ViewPager}</code>.</li> + <li>Handle's the device's back button by moving backwards in the virtual stack of fragments. + If the user is already on the first page, go back on the activity back stack.</li> +</ul> + +<pre> +public class ScreenSlidePagerActivity extends FragmentActivity { + /** + * The number of pages (wizard steps) to show in this demo. + */ + private static final int NUM_PAGES = 5; + + /** + * The pager widget, which handles animation and allows swiping horizontally to access previous + * and next wizard steps. + */ + private ViewPager mPager; + + /** + * The pager adapter, which provides the pages to the view pager widget. + */ + private PagerAdapter mPagerAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_screen_slide_pager); + + // Instantiate a ViewPager and a PagerAdapter. + mPager = (ViewPager) findViewById(R.id.pager); + mPagerAdapter = new ScreenSlidePagerAdapter(getFragmentManager()); + mPager.setAdapter(mPagerAdapter); + } + + @Override + public void onBackPressed() { + if (mPager.getCurrentItem() == 0) { + // If the user is currently looking at the first step, allow the system to handle the + // Back button. This calls finish() on this activity and pops the back stack. + super.onBackPressed(); + } else { + // Otherwise, select the previous step. + mPager.setCurrentItem(mPager.getCurrentItem() - 1); + } + } + + /** + * A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in + * sequence. + */ + private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter { + public ScreenSlidePagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + return new ScreenSlidePageFragment(); + } + + @Override + public int getCount() { + return NUM_PAGES; + } + } +} +</pre>
\ No newline at end of file diff --git a/docs/html/training/animation/zoom.jd b/docs/html/training/animation/zoom.jd new file mode 100644 index 0000000..5dc2b6c --- /dev/null +++ b/docs/html/training/animation/zoom.jd @@ -0,0 +1,320 @@ +page.title=Zooming a View +trainingnavtop=true + +@jd:body + + <div id="tb-wrapper"> + <div id="tb"> + <h2> + This lesson teaches you to: + </h2> + <ol> + <li> + <a href="#views">Create the Views</a> + </li> + <li> + <a href="#setup">Set up the Zoom Animation</a> + </li> + <li> + <a href="#animate">Zoom the View</a> + </li> + </ol> + </div> + </div> + <p> + This lesson demonstrates how to do a touch-to-zoom animation, which is useful for apps such as photo + galleries to animate a view from a thumbnail to a full-size image that fills the screen. + </p> + <p>Here's what a touch-to-zoom animation looks like that + expands an image thumbnail to fill the screen: + </p> + + <div class="framed-galaxynexus-land-span-8"> + <video class="play-on-hover" autoplay> + <source src="anim_zoom.mp4" type="video/mp4"> + <source src="anim_zoom.webm" type="video/webm"> + <source src="anim_zoom.ogv" type="video/ogg"> + </video> + </div> + <div class="figure-caption"> + Zoom animation + <div class="video-instructions"> </div> + </div> + + <p> + If you want to jump ahead and see a full working example, + <a href="{@docRoot}shareables/training/Animations.zip">download</a> and + run the sample app and select the + Zoom example. See the following files for the code implementation: + </p> + <ul> + <li> + <code>src/TouchHighlightImageButton.java</code> (a simple helper class that shows a blue + touch highlight when the image button is pressed) + </li> + <li> + <code>src/ZoomActivity.java</code> + </li> + <li> + <code>layout/activity_zoom.xml</code> + </li> + </ul> + <h2 id="views"> + Create the Views + </h2> + <p> + Create a layout file that contains the small and large version of the content that you want + to zoom. The following example creates an {@link android.widget.ImageButton} for clickable image thumbnail + and an {@link android.widget.ImageView} that displays the enlarged view of the image: + </p> + <pre> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="16dp"> + + <ImageButton + android:id="@+id/thumb_button_1" + android:layout_width="100dp" + android:layout_height="75dp" + android:layout_marginRight="1dp" + android:src="@drawable/thumb1" + android:scaleType="centerCrop" + android:contentDescription="@string/description_image_1" /> + + </LinearLayout> + + <!-- This initially-hidden ImageView will hold the expanded/zoomed version of + the images above. Without transformations applied, it takes up the entire + screen. To achieve the "zoom" animation, this view's bounds are animated + from the bounds of the thumbnail button above, to its final laid-out + bounds. + --> + + <ImageView + android:id="@+id/expanded_image" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="invisible" + android:contentDescription="@string/description_zoom_touch_close" /> + +</FrameLayout> +</pre> + <h2 id="setup"> + Set up the Zoom Animation + </h2> + <p> + Once you apply your layout, set up the event handlers that trigger the zoom animation. + The following example adds a {@link android.view.View.OnClickListener} to the {@link + android.widget.ImageButton} to execute the zoom animation when the user + clicks the image button: + </p> + <pre> +public class ZoomActivity extends FragmentActivity { + // Hold a reference to the current animator, + // so that it can be canceled mid-way. + private Animator mCurrentAnimator; + + // The system "short" animation time duration, in milliseconds. This + // duration is ideal for subtle animations or animations that occur + // very frequently. + private int mShortAnimationDuration; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_zoom); + + // Hook up clicks on the thumbnail views. + + final View thumb1View = findViewById(R.id.thumb_button_1); + thumb1View.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + zoomImageFromThumb(thumb1View, R.drawable.image1); + } + }); + + // Retrieve and cache the system's default "short" animation time. + mShortAnimationDuration = getResources().getInteger( + android.R.integer.config_shortAnimTime); + } + ... +} +</pre> + <h2 id="animate"> + Zoom the View + </h2> + <p> + You'll now need to animate from the normal sized view to the zoomed view + when appropriate. In general, you need to animate from the bounds of the normal-sized view to the + bounds of the larger-sized view. The following method shows you how to implement a zoom animation that + zooms from an image thumbnail to an enlarged view by doing the following things: + </p> + <ol> + <li>Assign the high-res image to the hidden "zoomed-in" (enlarged) {@link + android.widget.ImageView}. The following example loads a large image resource on the UI + thread for simplicity. You will want to do this loading in a separate thread to prevent + blocking on the UI thread and then set the bitmap on the UI thread. Ideally, the bitmap + should not be larger than the screen size. + </li> + <li>Calculate the starting and ending bounds for the {@link android.widget.ImageView}. + </li> + <li>Animate each of the four positioning and sizing properties <code>{@link + android.view.View#X}</code>, <code>{@link android.view.View#Y}</code>, ({@link + android.view.View#SCALE_X}, and <code>{@link android.view.View#SCALE_Y}</code>) + simultaneously, from the starting bounds to the ending bounds. These four animations are + added to an {@link android.animation.AnimatorSet} so that they can be started at the same + time. + </li> + <li>Zoom back out by running a similar animation but in reverse when the user touches the + screen when the image is zoomed in. You can do this by adding a {@link + android.view.View.OnClickListener} to the {@link android.widget.ImageView}. When clicked, the + {@link android.widget.ImageView} minimizes back down to the size of the image thumbnail and + sets its visibility to {@link android.view.View#GONE} to hide it. + </li> + </ol> + <pre> +private void zoomImageFromThumb(final View thumbView, int imageResId) { + // If there's an animation in progress, cancel it + // immediately and proceed with this one. + if (mCurrentAnimator != null) { + mCurrentAnimator.cancel(); + } + + // Load the high-resolution "zoomed-in" image. + final ImageView expandedImageView = (ImageView) findViewById( + R.id.expanded_image); + expandedImageView.setImageResource(imageResId); + + // Calculate the starting and ending bounds for the zoomed-in image. + // This step involves lots of math. Yay, math. + final Rect startBounds = new Rect(); + final Rect finalBounds = new Rect(); + final Point globalOffset = new Point(); + + // The start bounds are the global visible rectangle of the thumbnail, + // and the final bounds are the global visible rectangle of the container + // view. Also set the container view's offset as the origin for the + // bounds, since that's the origin for the positioning animation + // properties (X, Y). + thumbView.getGlobalVisibleRect(startBounds); + findViewById(R.id.container) + .getGlobalVisibleRect(finalBounds, globalOffset); + startBounds.offset(-globalOffset.x, -globalOffset.y); + finalBounds.offset(-globalOffset.x, -globalOffset.y); + + // Adjust the start bounds to be the same aspect ratio as the final + // bounds using the "center crop" technique. This prevents undesirable + // stretching during the animation. Also calculate the start scaling + // factor (the end scaling factor is always 1.0). + float startScale; + if ((float) finalBounds.width() / finalBounds.height() + > (float) startBounds.width() / startBounds.height()) { + // Extend start bounds horizontally + startScale = (float) startBounds.height() / finalBounds.height(); + float startWidth = startScale * finalBounds.width(); + float deltaWidth = (startWidth - startBounds.width()) / 2; + startBounds.left -= deltaWidth; + startBounds.right += deltaWidth; + } else { + // Extend start bounds vertically + startScale = (float) startBounds.width() / finalBounds.width(); + float startHeight = startScale * finalBounds.height(); + float deltaHeight = (startHeight - startBounds.height()) / 2; + startBounds.top -= deltaHeight; + startBounds.bottom += deltaHeight; + } + + // Hide the thumbnail and show the zoomed-in view. When the animation + // begins, it will position the zoomed-in view in the place of the + // thumbnail. + thumbView.setAlpha(0f); + expandedImageView.setVisibility(View.VISIBLE); + + // Set the pivot point for SCALE_X and SCALE_Y transformations + // to the top-left corner of the zoomed-in view (the default + // is the center of the view). + expandedImageView.setPivotX(0f); + expandedImageView.setPivotY(0f); + + // Construct and run the parallel animation of the four translation and + // scale properties (X, Y, SCALE_X, and SCALE_Y). + AnimatorSet set = new AnimatorSet(); + set + .play(ObjectAnimator.ofFloat(expandedImageView, View.X, + startBounds.left, finalBounds.left)) + .with(ObjectAnimator.ofFloat(expandedImageView, View.Y, + startBounds.top, finalBounds.top)) + .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, + startScale, 1f)).with(ObjectAnimator.ofFloat(expandedImageView, + View.SCALE_Y, startScale, 1f)); + set.setDuration(mShortAnimationDuration); + set.setInterpolator(new DecelerateInterpolator()); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mCurrentAnimator = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + mCurrentAnimator = null; + } + }); + set.start(); + mCurrentAnimator = set; + + // Upon clicking the zoomed-in image, it should zoom back down + // to the original bounds and show the thumbnail instead of + // the expanded image. + final float startScaleFinal = startScale; + expandedImageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mCurrentAnimator != null) { + mCurrentAnimator.cancel(); + } + + // Animate the four positioning/sizing properties in parallel, + // back to their original values. + AnimatorSet set = new AnimatorSet(); + set.play(ObjectAnimator + .ofFloat(expandedImageView, View.X, startBounds.left)) + .with(ObjectAnimator + .ofFloat(expandedImageView, + View.Y,startBounds.top)) + .with(ObjectAnimator + .ofFloat(expandedImageView, + View.SCALE_X, startScaleFinal)) + .with(ObjectAnimator + .ofFloat(expandedImageView, + View.SCALE_Y, startScaleFinal)); + set.setDuration(mShortAnimationDuration); + set.setInterpolator(new DecelerateInterpolator()); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + thumbView.setAlpha(1f); + expandedImageView.setVisibility(View.GONE); + mCurrentAnimator = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + thumbView.setAlpha(1f); + expandedImageView.setVisibility(View.GONE); + mCurrentAnimator = null; + } + }); + set.start(); + mCurrentAnimator = set; + } + }); +} +</pre>
\ No newline at end of file diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index 4ad1353..4a5b0fa 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -287,6 +287,34 @@ </li> </ul> </li> + + <li class="nav-section"> + <div class="nav-section-header"><a href="<?cs var:toroot ?>training/animation/index.html"> + <span class="en">Adding Animations</span> + </a></div> + <ul> + <li><a href="<?cs var:toroot ?>training/animation/crossfade.html"> + <span class="en">Crossfading Two Views</span> + </a> + </li> + <li><a href="<?cs var:toroot ?>training/animation/screen-slide.html"> + <span class="en">Using ViewPager for Screen Slide</span> + </a> + </li> + <li><a href="<?cs var:toroot ?>training/animation/cardflip.html"> + <span class="en">Displaying Card Flip Animations</span> + </a> + </li> + <li><a href="<?cs var:toroot ?>training/animation/zoom.html"> + <span class="en">Zooming a View</span> + </a> + </li> + <li><a href="<?cs var:toroot ?>training/animation/layout.html"> + <span class="en">Animating Layout Changes</span> + </a> + </li> + </ul> + </li> <li class="nav-section"> <div class="nav-section-header"><a href="<?cs var:toroot ?>training/managing-audio/index.html"> diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index 82ed432..2a5a16e 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -32,7 +32,6 @@ import android.os.ServiceManager; import android.text.TextUtils; import android.util.Log; import android.view.Display; -import android.view.DisplayInfo; import java.util.ArrayList; import java.util.HashMap; @@ -53,7 +52,7 @@ import java.util.concurrent.CopyOnWriteArrayList; public class MediaRouter { private static final String TAG = "MediaRouter"; - static class Static { + static class Static implements DisplayManager.DisplayListener { final Resources mResources; final IAudioService mAudioService; final DisplayManager mDisplayService; @@ -105,6 +104,8 @@ public class MediaRouter { mDefaultAudioVideo = new RouteInfo(mSystemCategory); mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name; mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO; + mDefaultAudioVideo.mPresentationDisplay = choosePresentationDisplayForRoute( + mDefaultAudioVideo, getAllPresentationDisplays()); addRouteStatic(mDefaultAudioVideo); // This will select the active wifi display route if there is one. @@ -115,6 +116,8 @@ public class MediaRouter { appContext.registerReceiver(new VolumeChangeReceiver(), new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION)); + mDisplayService.registerDisplayListener(this, mHandler); + AudioRoutesInfo newAudioRoutes = null; try { newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver); @@ -191,6 +194,39 @@ public class MediaRouter { } } } + + @Override + public void onDisplayAdded(int displayId) { + updatePresentationDisplays(displayId); + } + + @Override + public void onDisplayChanged(int displayId) { + updatePresentationDisplays(displayId); + } + + @Override + public void onDisplayRemoved(int displayId) { + updatePresentationDisplays(displayId); + } + + public Display[] getAllPresentationDisplays() { + return mDisplayService.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION); + } + + private void updatePresentationDisplays(int changedDisplayId) { + final Display[] displays = getAllPresentationDisplays(); + final int count = mRoutes.size(); + for (int i = 0; i < count; i++) { + final RouteInfo info = mRoutes.get(i); + Display display = choosePresentationDisplayForRoute(info, displays); + if (display != info.mPresentationDisplay + || (display != null && display.getDisplayId() == changedDisplayId)) { + info.mPresentationDisplay = display; + dispatchRoutePresentationDisplayChanged(info); + } + } + } } static Static sStatic; @@ -218,6 +254,9 @@ public class MediaRouter { * While remote routing is active the application may use a * {@link android.app.Presentation Presentation} to replace the mirrored view * on the external display with different content.</p> + * + * @see RouteInfo#getPresentationDisplay() + * @see android.app.Presentation */ public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2; @@ -239,6 +278,9 @@ public class MediaRouter { if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) { result.append("ROUTE_TYPE_LIVE_AUDIO "); } + if ((types & ROUTE_TYPE_LIVE_VIDEO) != 0) { + result.append("ROUTE_TYPE_LIVE_VIDEO "); + } if ((types & ROUTE_TYPE_USER) != 0) { result.append("ROUTE_TYPE_USER "); } @@ -671,6 +713,14 @@ public class MediaRouter { } } + static void dispatchRoutePresentationDisplayChanged(RouteInfo info) { + for (CallbackInfo cbi : sStatic.mCallbacks) { + if ((cbi.type & info.mSupportedTypes) != 0) { + cbi.cb.onRoutePresentationDisplayChanged(cbi.router, info); + } + } + } + static void systemVolumeChanged(int newValue) { final RouteInfo selectedRoute = sStatic.mSelectedRoute; if (selectedRoute == null) return; @@ -752,6 +802,9 @@ public class MediaRouter { newRoute.mEnabled = available; newRoute.mName = display.getFriendlyDisplayName(); + + newRoute.mPresentationDisplay = choosePresentationDisplayForRoute(newRoute, + sStatic.getAllPresentationDisplays()); return newRoute; } @@ -827,6 +880,27 @@ public class MediaRouter { return null; } + private static Display choosePresentationDisplayForRoute(RouteInfo route, Display[] displays) { + if ((route.mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) { + if (route.mDeviceAddress != null) { + // Find the indicated Wifi display by its address. + for (Display display : displays) { + if (display.getType() == Display.TYPE_WIFI + && route.mDeviceAddress.equals(display.getAddress())) { + return display; + } + } + return null; + } + + if (route == sStatic.mDefaultAudioVideo && displays.length > 0) { + // Choose the first presentation display from the list. + return displays[0]; + } + } + return null; + } + /** * Information about a media route. */ @@ -845,6 +919,7 @@ public class MediaRouter { int mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; int mPlaybackStream = AudioManager.STREAM_MUSIC; VolumeCallbackInfo mVcb; + Display mPresentationDisplay; String mDeviceAddress; boolean mEnabled = true; @@ -1116,6 +1191,38 @@ public class MediaRouter { } /** + * Gets the {@link Display} that should be used by the application to show + * a {@link android.app.Presentation} on an external display when this route is selected. + * Depending on the route, this may only be valid if the route is currently + * selected. + * <p> + * The preferred presentation display may change independently of the route + * being selected or unselected. For example, the presentation display + * of the default system route may change when an external HDMI display is connected + * or disconnected even though the route itself has not changed. + * </p><p> + * This method may return null if there is no external display associated with + * the route or if the display is not ready to show UI yet. + * </p><p> + * The application should listen for changes to the presentation display + * using the {@link Callback#onRoutePresentationDisplayChanged} callback and + * show or dismiss its {@link android.app.Presentation} accordingly when the display + * becomes available or is removed. + * </p><p> + * This method only makes sense for {@link #ROUTE_TYPE_LIVE_VIDEO live video} routes. + * </p> + * + * @return The preferred presentation display to use when this route is + * selected or null if none. + * + * @see #ROUTE_TYPE_LIVE_VIDEO + * @see android.app.Presentation + */ + public Display getPresentationDisplay() { + return mPresentationDisplay; + } + + /** * @return true if this route is enabled and may be selected */ public boolean isEnabled() { @@ -1156,9 +1263,11 @@ public class MediaRouter { @Override public String toString() { String supportedTypes = typesToString(getSupportedTypes()); - return getClass().getSimpleName() + "{ name=" + getName() + ", status=" + getStatus() + - " category=" + getCategory() + - " supportedTypes=" + supportedTypes + "}"; + return getClass().getSimpleName() + "{ name=" + getName() + + ", status=" + getStatus() + + ", category=" + getCategory() + + ", supportedTypes=" + supportedTypes + + ", presentationDisplay=" + mPresentationDisplay + "}"; } } @@ -1853,6 +1962,21 @@ public class MediaRouter { * @param info The route with altered volume */ public abstract void onRouteVolumeChanged(MediaRouter router, RouteInfo info); + + /** + * Called when a route's presentation display changes. + * <p> + * This method is called whenever the route's presentation display becomes + * available, is removes or has changes to some of its properties (such as its size). + * </p> + * + * @param router the MediaRouter reporting the event + * @param info The route whose presentation display changed + * + * @see RouteInfo#getPresentationDisplay() + */ + public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) { + } } /** diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 02b5326..cfe70dc 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -214,7 +214,7 @@ <activity android:name=".Somnambulator" android:label="@string/start_dreams" - android:icon="@mipmap/ic_daydreams" + android:icon="@mipmap/ic_launcher_dreams" android:theme="@android:style/Theme.Wallpaper.NoTitleBar" android:exported="true" android:excludeFromRecents="true" diff --git a/packages/SystemUI/res/mipmap-hdpi/ic_dreams.png b/packages/SystemUI/res/mipmap-hdpi/ic_dreams.png Binary files differdeleted file mode 100644 index 56cbac1..0000000 --- a/packages/SystemUI/res/mipmap-hdpi/ic_dreams.png +++ /dev/null diff --git a/packages/SystemUI/res/mipmap-hdpi/ic_launcher_dreams.png b/packages/SystemUI/res/mipmap-hdpi/ic_launcher_dreams.png Binary files differindex a335d6d..37185f3 100644 --- a/packages/SystemUI/res/mipmap-hdpi/ic_launcher_dreams.png +++ b/packages/SystemUI/res/mipmap-hdpi/ic_launcher_dreams.png diff --git a/packages/SystemUI/res/mipmap-mdpi/ic_dreams.png b/packages/SystemUI/res/mipmap-mdpi/ic_dreams.png Binary files differdeleted file mode 100644 index ea3d991..0000000 --- a/packages/SystemUI/res/mipmap-mdpi/ic_dreams.png +++ /dev/null diff --git a/packages/SystemUI/res/mipmap-mdpi/ic_launcher_dreams.png b/packages/SystemUI/res/mipmap-mdpi/ic_launcher_dreams.png Binary files differindex ef2e27b..1993b0d 100644 --- a/packages/SystemUI/res/mipmap-mdpi/ic_launcher_dreams.png +++ b/packages/SystemUI/res/mipmap-mdpi/ic_launcher_dreams.png diff --git a/packages/SystemUI/res/mipmap-xhdpi/ic_dreams.png b/packages/SystemUI/res/mipmap-xhdpi/ic_dreams.png Binary files differdeleted file mode 100644 index ddc7f66..0000000 --- a/packages/SystemUI/res/mipmap-xhdpi/ic_dreams.png +++ /dev/null diff --git a/packages/SystemUI/res/mipmap-xhdpi/ic_launcher_dreams.png b/packages/SystemUI/res/mipmap-xhdpi/ic_launcher_dreams.png Binary files differindex 7b42cb4..c92b681 100644 --- a/packages/SystemUI/res/mipmap-xhdpi/ic_launcher_dreams.png +++ b/packages/SystemUI/res/mipmap-xhdpi/ic_launcher_dreams.png diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 69355d5..e5cc356 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -174,7 +174,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Skerm is in landskapsoriëntasie gesluit."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Skerm is in portretoriëntasie gesluit."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Slaap nou"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Vliegtuigmodus"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Laai, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 735da78..57ad48a 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"ማያ ገጽ በወርድ ገፅ አቀማመጥ ተቆልፏል።"</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"ማያ ገጽ በቁም ገፅ አቀማመጥ ተቆልፏል።"</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"አሁን ተኛ"</string> + <string name="start_dreams" msgid="7219575858348719790">"የቀን ህልም"</string> <string name="ethernet_label" msgid="7967563676324087464">"ኤተርኔት"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"የአውሮፕላን ሁነታ"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"ባትሪ በመሙላት ላይ፣ <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index da78d29..c40e26d 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"تم تأمين الشاشة في الاتجاه الأفقي."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"تم تأمين الشاشة في الاتجاه العمودي."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"السكون الآن"</string> + <string name="start_dreams" msgid="7219575858348719790">"حلم اليقظة"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"وضع الطائرة"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"جارٍ الشحن، <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 79bc53a..a4b4b73 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -176,7 +176,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Экран заблакiраваны ў альбомнай арыентацыі."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Экран заблакiраваны ў партрэтнай арыентацыі."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Засыпай"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Рэжым палёту"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Зарадка, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 59ba693..8876a38 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -174,7 +174,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Екранът е заключен в хоризонтална ориентация."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Екранът е заключен във вертикална ориентация."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"В спящ режим сега"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Самолетен режим"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Зарежда се, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index bf10ce2..8d5cf36 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -176,7 +176,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"La pantalla està bloquejada en orientació horitzontal."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"La pantalla està bloquejada en orientació vertical."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Entra en mode repòs"</string> + <string name="start_dreams" msgid="7219575858348719790">"Somnis despert"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Mode d\'avió"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"S\'està carregant, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 5e632fa..a623612 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -176,7 +176,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Obrazovka je uzamčena v orientaci na šířku."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Obrazovka je uzamčena v orientaci na výšku."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Spát nyní"</string> + <string name="start_dreams" msgid="7219575858348719790">"Spořič obrazovky"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Režim V letadle"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Nabíjení, <xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 671f292..ddb6265 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Skærmen er nu låst i liggende retning."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Skærmen er nu låst i stående retning."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Gå i dvale nu"</string> + <string name="start_dreams" msgid="7219575858348719790">"Daydream"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Flytilstand"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Oplader, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 3e767a0..fc18361 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -176,7 +176,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Bildschirm bleibt im Querformat."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Bildschirm bleibt im Hochformat."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Ruhemodus ein"</string> + <string name="start_dreams" msgid="7219575858348719790">"Daydream"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Flugmodus"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Lädt, <xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 0665020..abe6082 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -176,7 +176,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Η οθόνη έχει κλειδωθεί σε οριζόντιο προσανατολισμό."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Η οθόνη έχει κλειδωθεί σε κατακόρυφο προσανατολισμό."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Ενεργ. αναστ. λειτ."</string> + <string name="start_dreams" msgid="7219575858348719790">"Daydream"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Λειτουργία πτήσης"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Φόρτιση, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 906b5c6..890e05e 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Screen is locked in landscape orientation."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Screen is locked in portrait orientation."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Sleep Now"</string> + <string name="start_dreams" msgid="7219575858348719790">"Daydream"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Aeroplane mode"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Charging, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 9bbd5d7..8abf3a8 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -176,7 +176,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"La pantalla está bloqueada en modo horizontal."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"La pantalla está bloqueada en modo vertical."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Activar suspensión"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Modo de avión"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Cargando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 90f93b2..79f0bb0 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"La pantalla está bloqueada en modo horizontal."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"La pantalla está bloqueada en modo vertical."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Suspender"</string> + <string name="start_dreams" msgid="7219575858348719790">"Daydream"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Modo avión"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Cargando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index d77b26a..5339006 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Ekraan on lukustatud horisontaalsuunas."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Ekraan on lukustatud vertikaalsuunas."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Nüüd unerežiimi"</string> + <string name="start_dreams" msgid="7219575858348719790">"Unistus"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Lennurežiim"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Laadimine, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 6accc9f..aa80148 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"صفحه اکنون در جهت افقی قفل است."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"صفحه اکنون در جهت عمودی قفل است."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"اکنون خواب"</string> + <string name="start_dreams" msgid="7219575858348719790">"رویاپردازی"</string> <string name="ethernet_label" msgid="7967563676324087464">"اترنت"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"حالت هواپیما"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"در حال شارژ، <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 3bc0371..d4b82f0 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Ruutu on lukittu vaakasuuntaan."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Ruutu on lukittu pystysuuntaan."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Virransäästötilaan"</string> + <string name="start_dreams" msgid="7219575858348719790">"Unelmoi"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Lentokonetila"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Ladataan (<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>)"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 02eb529..e547478 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -176,7 +176,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"L\'écran est verrouillé en mode paysage."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"L\'écran est verrouillé en mode portrait."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Mettre en veille"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Mode avion"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"En charge (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index e2bc2c3..cdcda79 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"स्क्रीन लैंडस्केप अभिविन्यास में लॉक है."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"स्क्रीन पोर्ट्रेट अभिविन्यास में लॉक है."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"अभी निष्क्रिय करें"</string> + <string name="start_dreams" msgid="7219575858348719790">"Daydream"</string> <string name="ethernet_label" msgid="7967563676324087464">"ईथरनेट"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"हवाई जहाज़ मोड"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"चार्ज हो रही है, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index e318120..cfa5d54 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Zaslon je zaključan u pejzažnoj orijentaciji."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Zaslon je zaključan u portretnoj orijentaciji."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Miruj sad"</string> + <string name="start_dreams" msgid="7219575858348719790">"Sanjarenje"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Način rada u zrakoplovu"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Puni se, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 3dafd2b..27bf2f5 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"A képernyő zárolva van fekvő tájolásban."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"A képernyő zárolva van álló tájolásban."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Alvó mód"</string> + <string name="start_dreams" msgid="7219575858348719790">"Álmodozás"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Repülőgép üzemmód"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Töltés (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index c26cdce..baa7b22 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -174,7 +174,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Layar dikunci dalam orientasi lanskap."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Layar dikunci dalam orientasi potret."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Tidur Sekarang"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Mode pesawat"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Mengisi baterai, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index e9ac98f..812d1ac 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -176,7 +176,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Lo schermo è bloccato in orientamento orizzontale."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Lo schermo è bloccato in orientamento verticale."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Sospendi ora"</string> + <string name="start_dreams" msgid="7219575858348719790">"Daydream"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Modalità aereo"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"In carica (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 0481d63..150375d 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"המסך נעול כעת לרוחב."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"המסך נעול כעת לאורך."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"עבור לשינה עכשיו"</string> + <string name="start_dreams" msgid="7219575858348719790">"Daydream"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"מצב טיסה"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"טוען (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 1b89e13..c427ac4 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -176,7 +176,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"画面は横向きにロックされています。"</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"画面は縦向きにロックされています。"</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"スリープ開始"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"イーサネット"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"機内モード"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"充電中: <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 71d305f..deafa45 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -174,7 +174,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"화면이 가로 방향으로 잠겨 있습니다."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"화면이 세로 방향으로 잠겨 있습니다."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"절전 모드로 전환"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"이더넷"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"비행기 모드"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"충전 중(<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 76de197..02d0c5b 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Užrakintas ekranas yra horizontalios orientacijos."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Užrakintas ekranas yra vertikalios orientacijos."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Įj. miego rež. dabar"</string> + <string name="start_dreams" msgid="7219575858348719790">"Svajonė"</string> <string name="ethernet_label" msgid="7967563676324087464">"Eternetas"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Lėktuvo režimas"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Įkraunama, <xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 21299b2..57e70c8 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Ekrāns tagad ir bloķēts ainavas orientācijā."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Ekrāns tagad ir bloķēts portreta orientācijā."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Ieslēgt miega režīmu"</string> + <string name="start_dreams" msgid="7219575858348719790">"Ekrānsaudzētājs"</string> <string name="ethernet_label" msgid="7967563676324087464">"Tīkls Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Lidojuma režīms"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Notiek uzlāde, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 01751b0..7ce5b62 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -174,7 +174,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Skrin dikunci dalam orientasi landskap."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Skrin dikunci dalam orientasi potret."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Tidur Sekarang"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Mod kapal terbang"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Mengecas, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index ccba8e5..abd7f84 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -174,7 +174,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Skjermen er låst i liggende retning."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Skjermen er låst i stående retning."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Aktiver hvilemodus"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Flymodus"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Lader: <xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 9978b76..c055aec 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Het scherm is nu vergrendeld in liggende stand."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Het scherm is nu vergrendeld in staande stand."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Nu slapen"</string> + <string name="start_dreams" msgid="7219575858348719790">"Dagdroom"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Vliegmodus"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Opladen, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 98f13c7..a05aa8f 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Ekran jest zablokowany w orientacji poziomej."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Ekran jest zablokowany w orientacji pionowej."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Zaśnij teraz"</string> + <string name="start_dreams" msgid="7219575858348719790">"Śnij na jawie"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Tryb samolotowy"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Ładowanie (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 639201c..1ca085c 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -174,16 +174,16 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"O ecrã está bloqueado na orientação horizontal."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"O ecrã está bloqueado na orientação vertical."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Em Suspensão Agora"</string> + <string name="start_dreams" msgid="7219575858348719790">"Sonho"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Modo de avião"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"A carregar, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> <string name="quick_settings_battery_charged_label" msgid="8865413079414246081">"Carregada"</string> <string name="quick_settings_bluetooth_label" msgid="6304190285170721401">"Bluetooth"</string> <string name="quick_settings_bluetooth_multiple_devices_label" msgid="3912245565613684735">"Bluetooth (<xliff:g id="NUMBER">%d</xliff:g> Dispositivos)"</string> - <string name="quick_settings_bluetooth_off_label" msgid="8159652146149219937">"Bluetooth Desat."</string> + <string name="quick_settings_bluetooth_off_label" msgid="8159652146149219937">"Bluetooth desat."</string> <string name="quick_settings_brightness_label" msgid="6968372297018755815">"Brilho"</string> - <string name="quick_settings_rotation_unlocked_label" msgid="336054930362580584">"Rodar Automat."</string> + <string name="quick_settings_rotation_unlocked_label" msgid="336054930362580584">"Rodar automat."</string> <string name="quick_settings_rotation_locked_label" msgid="8058646447242565486">"Rotação Bloqueada"</string> <string name="quick_settings_ime_label" msgid="7073463064369468429">"Método de Introdução"</string> <string name="quick_settings_location_label" msgid="3292451598267467545">"Localização em utilização"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 9eeb01b..68f08dd 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -176,7 +176,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"A tela está bloqueada na orientação paisagem."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"A tela está bloqueada na orientação retrato."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Suspender"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Modo para avião"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Carregando, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-rm/strings.xml b/packages/SystemUI/res/values-rm/strings.xml index ef770a0..606160b 100644 --- a/packages/SystemUI/res/values-rm/strings.xml +++ b/packages/SystemUI/res/values-rm/strings.xml @@ -318,7 +318,7 @@ <skip /> <!-- no translation found for jelly_bean_dream_name (5992026543636816792) --> <skip /> - <!-- no translation found for start_dreams (6170089063982549905) --> + <!-- no translation found for start_dreams (7219575858348719790) --> <skip /> <!-- no translation found for ethernet_label (7967563676324087464) --> <skip /> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 616ddb7..085f607 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -174,7 +174,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Ecranul este blocat în orientarea de tip peisaj."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Ecranul este blocat în orientarea de tip portret."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Activaţi mod inactiv"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Mod Avion"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Se încarcă, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 6e10de4..2596f88 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -176,7 +176,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Выбрана только альбомная ориентация экрана."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Выбрана только книжная ориентация экрана."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Спящий режим"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Режим полета"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Зарядка (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 9151512..ea70c10 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -176,7 +176,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Obrazovka je uzamknutá v orientácii na šírku."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Obrazovka je uzamknutá v orientácii na výšku."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Spať"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Režim V lietadle"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Nabíjanie, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 7be851c..fb8812a 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -174,7 +174,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Zaslon je zaklenjen v ležeči usmerjenosti."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Zaslon je zaklenjen v pokončni usmerjenosti."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Stanje pripravljenosti"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Način za letalo"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Polnjenje, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 9d9da87..42ab38a 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Екран је закључан у хоризонталном положају."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Екран је закључан у вертикалном положају."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Спавај одмах"</string> + <string name="start_dreams" msgid="7219575858348719790">"Сањарење"</string> <string name="ethernet_label" msgid="7967563676324087464">"Етернет"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Режим рада у авиону"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Пуњење, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index bae649d..7bf9d4c 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Bildskärmens riktning är nu låst i liggande format."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Bildskärmens riktning är nu låst i stående format."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Viloläge nu"</string> + <string name="start_dreams" msgid="7219575858348719790">"Dagdröm"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Flygplansläge"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Laddar, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 3b29b39..fba3da5 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -172,7 +172,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Skrini imefungwa sasa katika uelekezo wa mandhari."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Skrini imefungwa katika uelekeo wa picha."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Lala Sasa"</string> + <string name="start_dreams" msgid="7219575858348719790">"Ndoto ya mchana"</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Modi ya ndege"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Inachaji, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 668504e..2d6f5f2 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -174,7 +174,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"ขณะนี้หน้าจอถูกล็อกให้วางในแนวนอน"</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"ขณะนี้หน้าจอถูกล็อกให้วางในแนวตั้ง"</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"เข้าสู่โหมดสลีปเลย"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"อีเทอร์เน็ต"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"โหมดใช้งานบนเครื่องบิน"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"กำลังชาร์จ, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 312d8fd..52b3758 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -174,7 +174,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Naka-lock ang screen sa pahigang oryentasyon."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Naka-lock ang screen sa patayong oryentasyon."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Mag-sleep Ngayon"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Airplane mode"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Nagcha-charge, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 5012bc5..08111ef 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -174,7 +174,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Ekran yatay yönde kilitlendi."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Ekran dikey yönde kilitlendi."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Şimdi Uyu"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Uçak modu"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Şarj oluyor, <xliff:g id="PERCENT">%%</xliff:g><xliff:g id="NUMBER">%d</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index ba36be6..71a2bca 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Екран заблоковано в альбомній орієнтації."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Екран заблоковано в книжковій орієнтації."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Перейти в режим сну"</string> + <string name="start_dreams" msgid="7219575858348719790">"Заставка \"Видіння\""</string> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Режим польоту"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Заряджається, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index ce90afc..87ee67b 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -174,7 +174,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Màn hình hiện bị khóa theo hướng ngang."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Màn hình hiện bị khóa theo hướng dọc."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Ngủ bây giờ"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Chế độ trên máy bay"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Đang sạc, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index ba0d9c8..45be2e9 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -176,7 +176,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"屏幕锁定为横向模式。"</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"屏幕锁定为纵向模式。"</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"果冻豆大乱舞"</string> - <string name="start_dreams" msgid="6170089063982549905">"立即休眠"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"以太网"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"飞行模式"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"正在充电:<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index a77dd8c..d64ca20 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -176,7 +176,8 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"螢幕已鎖定為橫向模式。"</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"螢幕已鎖定為垂直模式。"</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"立即休眠"</string> + <!-- no translation found for start_dreams (7219575858348719790) --> + <skip /> <string name="ethernet_label" msgid="7967563676324087464">"乙太網路"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"飛航模式"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"充電中 (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 8ad1881..3b772be 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -174,7 +174,7 @@ <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"Isikrini sikhiyelwe ngomumo we-landscape."</string> <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"Isikrini sikhiyelwe ngomumo we-portrait."</string> <string name="jelly_bean_dream_name" msgid="5992026543636816792">"I-BeanFlinger"</string> - <string name="start_dreams" msgid="6170089063982549905">"Lala manje"</string> + <string name="start_dreams" msgid="7219575858348719790">"Ukuphupha emini"</string> <string name="ethernet_label" msgid="7967563676324087464">"I-Ethernet"</string> <string name="quick_settings_airplane_mode_label" msgid="5510520633448831350">"Isimo sendiza"</string> <string name="quick_settings_battery_charging_label" msgid="490074774465309209">"Iyashaja <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> diff --git a/packages/SystemUI/src/com/android/systemui/BeanBagDream.java b/packages/SystemUI/src/com/android/systemui/BeanBagDream.java index a367367..39e4727 100644 --- a/packages/SystemUI/src/com/android/systemui/BeanBagDream.java +++ b/packages/SystemUI/src/com/android/systemui/BeanBagDream.java @@ -1,3 +1,19 @@ +/* + * 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.systemui; import android.service.dreams.DreamService; diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 0a7dd7c..9da883a 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -401,6 +401,7 @@ public class ImageWallpaper extends WallpaperService { Throwable exception = null; try { mWallpaperManager.forgetLoadedWallpaper(); // force reload + mBackground = null; mBackground = mWallpaperManager.getBitmap(); } catch (RuntimeException e) { exception = e; diff --git a/packages/SystemUI/src/com/android/systemui/Somnambulator.java b/packages/SystemUI/src/com/android/systemui/Somnambulator.java index 1f00bc1..0dd6d92 100644 --- a/packages/SystemUI/src/com/android/systemui/Somnambulator.java +++ b/packages/SystemUI/src/com/android/systemui/Somnambulator.java @@ -43,7 +43,7 @@ public class Somnambulator extends Activity { | Intent.FLAG_ACTIVITY_NEW_TASK); Intent resultIntent = new Intent(); resultIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, - Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_daydreams)); + Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher_dreams)); resultIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); resultIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.start_dreams)); setResult(RESULT_OK, resultIntent); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RotationToggle.java b/packages/SystemUI/src/com/android/systemui/statusbar/RotationToggle.java index 5dd45a4..735ee25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/RotationToggle.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/RotationToggle.java @@ -1,3 +1,19 @@ +/* + * 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.systemui.statusbar; import android.content.Context; 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 eef5446..983328d 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/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java index c9a137c..4b07b00 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -1,3 +1,19 @@ +/* + * 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.systemui.statusbar.phone; import java.util.ArrayList; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java index 241ac3e..8a54347 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java @@ -1,3 +1,19 @@ +/* + * 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.systemui.statusbar.phone; import android.content.Context; @@ -63,4 +79,4 @@ public class PanelHolder extends FrameLayout { public void setBar(PanelBar panelBar) { mBar = panelBar; } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 6184e30..7035006 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -1,3 +1,19 @@ +/* + * 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.systemui.statusbar.phone; import android.animation.ObjectAnimator; @@ -320,7 +336,7 @@ public class PanelView extends FrameLayout { final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY); if (deltaY < mFlingGestureMinDistPx || vel < mFlingExpandMinVelocityPx - || mJustPeeked) { + ) { vel = 0; } @@ -328,10 +344,8 @@ public class PanelView extends FrameLayout { vel = -vel; } - if (DEBUG) LOG("gesture: dy=%f vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f", + if (DEBUG) LOG("gesture: dy=%f vel=(%f,%f) vlinear=%f", deltaY, - mVelocityTracker.getXVelocity(), - mVelocityTracker.getYVelocity(), xVel, yVel, vel); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CurrentUserTracker.java index 7a2f25a..225ebc1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CurrentUserTracker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CurrentUserTracker.java @@ -1,3 +1,19 @@ +/* + * 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.systemui.statusbar.policy; import android.app.ActivityManager; 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 0000000..db7e231 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/CameraWidgetFrame.java @@ -0,0 +1,219 @@ +/* + * 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; + +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 boolean mRendered; + + private final Runnable mLaunchCameraRunnable = new Runnable() { + @Override + public void run() { + mLaunchCameraStart = SystemClock.uptimeMillis(); + 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() { + if (mRendered) return; + + try { + int width = getRootView().getWidth(); + int height = getRootView().getHeight(); + if (DEBUG) Log.d(TAG, String.format("render [%sx%s] %s", + width, height, Integer.toHexString(hashCode()))); + if (width == 0 || height == 0) { + return; + } + 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); + mRendered = true; + } catch (Throwable t) { + Log.w(TAG, "Error rendering camera widget", t); + removeAllViews(); + View genericView = inflateGenericWidgetView(mContext); + addView(genericView); + } + } + + 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 %sms 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 0000000..605a738 --- /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 0000000..020fdba --- /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/EmergencyButton.java b/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java index 203ba3c..cab4ed9 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java @@ -23,7 +23,6 @@ import android.os.SystemClock; import android.telephony.TelephonyManager; import android.util.AttributeSet; import android.view.View; -import android.view.View.OnClickListener; import android.widget.Button; import com.android.internal.telephony.IccCardConstants.State; diff --git a/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java b/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java index 3fe16cf..000acb1 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/FaceUnlock.java @@ -147,7 +147,7 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback { public boolean stop() { if (DEBUG) Log.d(TAG, "stop()"); if (mHandler.getLooper() != Looper.myLooper()) { - Log.e(TAG, "stop() called off of the UI thread"); + Log.e(TAG, "stop() called from non-UI thread"); } boolean mWasRunning = mIsRunning; 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 0000000..2eb10fe --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardAbsKeyInputView.java @@ -0,0 +1,237 @@ +/* + * 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.os.CountDownTimer; +import android.os.SystemClock; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.HapticFeedbackConstants; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +import com.android.internal.R; +import com.android.internal.widget.LockPatternUtils; + +/** + * 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 ebca4ac..ea7a8e7 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 0000000..a224a42 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardActivityLauncher.java @@ -0,0 +1,174 @@ +/* + * 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 { + // 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 0000000..29124c4 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardCircleFramedDrawable.java @@ -0,0 +1,161 @@ +/* + * 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.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 04ab871..4aa6b05 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 b86e5b8..b846729 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; @@ -60,7 +61,7 @@ public class KeyguardHostView extends KeyguardViewBase { private static final String TAG = "KeyguardViewHost"; // Use this to debug all of keyguard - public static boolean DEBUG; + public static boolean DEBUG = KeyguardViewMediator.DEBUG; // also referenced in SecuritySettings.java static final int APPWIDGET_HOST_ID = 0x4B455947; @@ -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,21 +153,41 @@ 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(); - updateSecurityViews(); - setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_BACK); + addWidgetsFromSettings(); - if (KeyguardUpdateMonitor.getInstance(mContext).getIsFirstBoot()) { - showPrimarySecurityScreen(false); + mViewStateManager = new KeyguardViewStateManager(); + SlidingChallengeLayout slider = + (SlidingChallengeLayout) findViewById(R.id.sliding_layout); + if (slider != null) { + slider.setOnChallengeScrolledListener(mViewStateManager); + } + mAppWidgetContainer.setViewStateManager(mViewStateManager); + mAppWidgetContainer.setLockPatternUtils(mLockPatternUtils); + + 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(); + + if (!(mContext instanceof Activity)) { + setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_BACK); } + + showPrimarySecurityScreen(false); + + updateSecurityViews(); } private void updateSecurityViews() { @@ -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) { @@ -240,14 +249,22 @@ public class KeyguardHostView extends KeyguardViewBase { mViewMediatorCallback.onUserActivityTimeoutChanged(); } } + + @Override + public void onPageSwitch(int newPageIndex) { + if (!isCameraOrAdd(newPageIndex)) { + if (DEBUG) Log.d(TAG, "Setting sticky widget index: " + newPageIndex); + mLockPatternUtils.setStickyWidgetIndex(newPageIndex); + } + } }; @Override 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 +334,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; } @@ -418,7 +433,8 @@ public class KeyguardHostView extends KeyguardViewBase { void showPrimarySecurityScreen(boolean turningOff) { SecurityMode securityMode = mSecurityModel.getSecurityMode(); if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")"); - if (!turningOff && KeyguardUpdateMonitor.getInstance(mContext).isAlternateUnlockEnabled()) { + if (!turningOff && KeyguardUpdateMonitor.getInstance(mContext).isAlternateUnlockEnabled() + && !KeyguardUpdateMonitor.getInstance(mContext).getIsFirstBoot()) { // If we're not turning off, then allow biometric alternate. // We'll reload it when the device comes back on. securityMode = mSecurityModel.getAlternateFor(securityMode); @@ -465,6 +481,7 @@ public class KeyguardHostView extends KeyguardViewBase { switch (mCurrentSecuritySelection) { case Pattern: case Password: + case PIN: case Account: case Biometric: finish = true; @@ -504,6 +521,8 @@ public class KeyguardHostView extends KeyguardViewBase { if (mViewMediatorCallback != null) { mViewMediatorCallback.keyguardDone(true); } + } else { + mViewStateManager.showBouncer(true); } } @@ -607,7 +626,6 @@ public class KeyguardHostView extends KeyguardViewBase { break; } } - boolean simPukFullScreen = getResources().getBoolean(R.bool.kg_sim_puk_account_full_screen); int layoutId = getLayoutIdFor(securityMode); if (view == null && layoutId != 0) { final LayoutInflater inflater = LayoutInflater.from(mContext); @@ -624,14 +642,9 @@ public class KeyguardHostView extends KeyguardViewBase { if (navigationText != null) { view.setSecurityMessageDisplay(new KeyguardNavigationManager(navigationText)); } else { - view.setSecurityMessageDisplay(mKeyguardStatusViewManager); - } - } - - if (securityMode == SecurityMode.SimPin || securityMode == SecurityMode.SimPuk || - securityMode == SecurityMode.Account) { - if (simPukFullScreen) { - mAppWidgetRegion.setVisibility(View.GONE); + if (mKeyguardStatusViewManager != null) { + view.setSecurityMessageDisplay(mKeyguardStatusViewManager); + } } } @@ -658,6 +671,15 @@ public class KeyguardHostView extends KeyguardViewBase { KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection); KeyguardSecurityView newView = getSecurityView(securityMode); + // Enter full screen mode if we're in SIM or Account screen + boolean fullScreenEnabled = getResources().getBoolean( + com.android.internal.R.bool.kg_sim_puk_account_full_screen); + boolean isSimOrAccount = securityMode == SecurityMode.SimPin + || securityMode == SecurityMode.SimPuk + || securityMode == SecurityMode.Account; + mAppWidgetContainer.setVisibility( + isSimOrAccount && fullScreenEnabled ? View.GONE : View.VISIBLE); + // Emulate Activity life cycle if (oldView != null) { oldView.onPause(); @@ -698,7 +720,7 @@ public class KeyguardHostView extends KeyguardViewBase { @Override public void onScreenTurnedOn() { - if (DEBUG) Log.d(TAG, "screen on"); + if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode())); showPrimarySecurityScreen(false); getSecurityView(mCurrentSecuritySelection).onResume(); @@ -706,18 +728,23 @@ 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 public void onScreenTurnedOff() { - if (DEBUG) Log.d(TAG, "screen off"); + if (DEBUG) Log.d(TAG, "screen off, instance " + Integer.toHexString(hashCode())); showPrimarySecurityScreen(true); getSecurityView(mCurrentSecuritySelection).onPause(); } @Override public void show() { - onScreenTurnedOn(); + if (DEBUG) Log.d(TAG, "show()"); + showPrimarySecurityScreen(false); } private boolean isSecure() { @@ -726,6 +753,7 @@ public class KeyguardHostView extends KeyguardViewBase { case Pattern: return mLockPatternUtils.isLockPatternEnabled(); case Password: + case PIN: return mLockPatternUtils.isLockPasswordEnabled(); case SimPin: case SimPuk: @@ -760,6 +788,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 +805,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 +819,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 +830,99 @@ 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); + Log.w(TAG, "AppWidgetInfo for app widget id " + appId + " was null, deleting"); + mLockPatternUtils.removeAppWidget(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); + slider.showChallenge(true); + } + View v = mAppWidgetContainer.getChildAt(mAppWidgetContainer.getCurrentPage()); + if (v instanceof CameraWidgetFrame) { + 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); + 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(); } @@ -857,12 +964,15 @@ public class KeyguardHostView extends KeyguardViewBase { }); } - mKeyguardStatusViewManager = ((KeyguardStatusView) - findViewById(R.id.keyguard_status_view_face_palm)).getManager(); + KeyguardStatusView ksv = (KeyguardStatusView) + findViewById(R.id.keyguard_status_view_face_palm); + if (ksv != null) { + mKeyguardStatusViewManager = ksv.getManager(); + } } - private void maybePopulateWidgets() { + private void addWidgetsFromSettings() { DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); if (dpm != null) { @@ -874,27 +984,29 @@ 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] == LockPatternUtils.ID_DEFAULT_STATUS_WIDGET) { + LayoutInflater inflater = LayoutInflater.from(mContext); + View statusWidget = inflater.inflate(R.layout.keyguard_status_view, null, true); + mAppWidgetContainer.addWidget(statusWidget, addPageIndex + 1); + } else { + // 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); + } } } } @@ -957,47 +1069,77 @@ public class KeyguardHostView extends KeyguardViewBase { } private void showAppropriateWidgetPage() { - - // The following sets the priority for showing widgets. Transport should be shown if - // music is playing, followed by the multi-user widget if enabled, followed by the - // status widget. - final int pageToShow; - if (mTransportControl.isMusicPlaying() || mTransportState == TRANSPORT_VISIBLE) { + boolean music = mTransportControl.isMusicPlaying() || mTransportState == TRANSPORT_VISIBLE; + if (music) { mTransportState = TRANSPORT_VISIBLE; - pageToShow = mAppWidgetContainer.indexOfChild(mTransportControl); - } else { - UserManager mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - final View multiUserView = findViewById(R.id.keyguard_multi_user_selector); - final int multiUserPosition = mAppWidgetContainer.indexOfChild(multiUserView); - if (multiUserPosition != -1 && mUm.getUsers(true).size() > 1) { - pageToShow = multiUserPosition; - } else { - final View statusView = findViewById(R.id.keyguard_status_view); - pageToShow = mAppWidgetContainer.indexOfChild(statusView); - } - if (mTransportState == TRANSPORT_VISIBLE) { - mTransportState = TRANSPORT_INVISIBLE; - } + } else if (mTransportState == TRANSPORT_VISIBLE) { + mTransportState = TRANSPORT_INVISIBLE; } + int pageToShow = getAppropriateWidgetPage(); mAppWidgetContainer.setCurrentPage(pageToShow); } - private void inflateAndAddUserSelectorWidgetIfNecessary() { + private boolean isCameraOrAdd(int pageIndex) { + View v = mAppWidgetContainer.getChildAt(pageIndex); + return v.getId() == R.id.keyguard_add_widget || v instanceof CameraWidgetFrame; + } + + private int getAppropriateWidgetPage() { + // assumes at least one widget (besides camera + add) + + boolean music = mTransportControl.isMusicPlaying() || mTransportState == TRANSPORT_VISIBLE; + // if music playing, show transport + if (music) { + if (DEBUG) Log.d(TAG, "Music playing, show transport"); + return mAppWidgetContainer.indexOfChild(mTransportControl); + } + + // if multi-user applicable, show it + UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + View multiUserView = findViewById(R.id.keyguard_multi_user_selector); + int multiUserPosition = mAppWidgetContainer.indexOfChild(multiUserView); + if (multiUserPosition != -1 && userManager.getUsers(true).size() > 1) { + if (DEBUG) Log.d(TAG, "Multi-user applicable, show it"); + return multiUserPosition; + } + + // if we have a sticky widget, show it + int stickyWidgetIndex = mLockPatternUtils.getStickyWidgetIndex(); + if (stickyWidgetIndex > -1 + && stickyWidgetIndex < mAppWidgetContainer.getChildCount() + && !isCameraOrAdd(stickyWidgetIndex)) { + if (DEBUG) Log.d(TAG, "Sticky widget found, show it"); + return stickyWidgetIndex; + } + + // if we have a status view, show it + View statusView = findViewById(R.id.keyguard_status_view); + int statusViewIndex = mAppWidgetContainer.indexOfChild(statusView); + if (statusViewIndex > -1) { + if (DEBUG) Log.d(TAG, "Status widget found, show it"); + return mAppWidgetContainer.indexOfChild(statusView); + } + + // else the right-most (except for camera) + int rightMost = mAppWidgetContainer.getChildCount() - 1; + if (mAppWidgetContainer.getChildAt(rightMost) instanceof CameraWidgetFrame) { + rightMost--; + } + if (DEBUG) Log.d(TAG, "Show right-most"); + return rightMost; + } + + 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 +1154,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 0000000..0fc54cd --- /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 3c972bc..a21ebe3 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,18 @@ 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 +37,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 +91,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 +123,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 +176,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,37 +194,23 @@ 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) { + if (!pressed || isClickable()) { super.setPressed(pressed); - updateVisualsForActive(pressed || mActive, false, 0, mActive, null); - } else { - mTempPressedStateHolder = pressed; + mFramed.setPressed(pressed); + mUserImage.invalidate(); } } 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 246c255..728e87c 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserSelectorView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserSelectorView.java @@ -20,26 +20,26 @@ import android.app.ActivityManagerNative; import android.content.Context; import android.content.pm.UserInfo; 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.WindowManagerGlobal; +import android.view.ViewGroup; 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 +55,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 +74,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,27 +103,57 @@ 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; + } + + private void setAllClickable(boolean clickable) + { + for(int i = 0; i < mUsersGrid.getChildCount(); i++) { + View v = mUsersGrid.getChildAt(i); + v.setClickable(clickable); + v.setPressed(false); + } + } + + @Override public void onClick(View v) { if (!(v instanceof KeyguardMultiUserAvatar)) return; final KeyguardMultiUserAvatar avatar = (KeyguardMultiUserAvatar) v; - if (mActiveUserAvatar == avatar) { - // If they click the currently active user, show the unlock hint - mCallback.showUnlockHint(); - 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() { - @Override - public void run() { - try { - ActivityManagerNative.getDefault().switchUser(avatar.getUserInfo().id); - } catch (RemoteException re) { - Log.e(TAG, "Couldn't switch user " + re); + if (avatar.isClickable()) { // catch race conditions + if (mActiveUserAvatar == avatar) { + // If they click the currently active user, show the unlock hint + mCallback.showUnlockHint(); + return; + } else { + // Reset the previously active user to appear inactive + mCallback.hideSecurityView(FADE_OUT_ANIMATION_DURATION); + setAllClickable(false); + mActiveUserAvatar.setActive(false, true, new Runnable() { + @Override + public void run() { + 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); + } + } else { + setAllClickable(true); + } + } + }); } - } - }); + }); + } } } } 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 0000000..5cdf4d3 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java @@ -0,0 +1,104 @@ +/* + * 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.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.text.method.DigitsKeyListener; +import android.util.AttributeSet; +import android.view.View; +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() { + } +} 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 be2dc8f..b6334f0 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPasswordView.java @@ -16,70 +16,35 @@ package com.android.internal.policy.impl.keyguard; -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; - -import com.android.internal.R; -import com.android.internal.widget.LockPatternUtils; -import java.util.List; - import android.app.admin.DevicePolicyManager; +import android.content.Context; 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.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.util.AttributeSet; +import android.view.View; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; +import com.android.internal.R; import com.android.internal.widget.PasswordEntryKeyboardHelper; +import com.android.internal.widget.PasswordEntryKeyboardView; + +import java.util.List; /** - * 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 +54,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 +110,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 +186,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 dcf40bf..408a9c8 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 0000000..f6a90c5 --- /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 59e2ca9..7a69586 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 19bcae9..c3684c4 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 c4e1607..9cdbc2d 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 1d26def..eba9a76 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 7878e46..018a1aa 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 562d893..d0585b9 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/KeyguardStatusView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java index 00cd5b9..f2cb522 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusView.java @@ -20,6 +20,8 @@ import android.content.Context; import android.util.AttributeSet; import android.widget.GridLayout; +import com.android.internal.widget.LockPatternUtils; + public class KeyguardStatusView extends GridLayout { @SuppressWarnings("unused") private KeyguardStatusViewManager mStatusViewManager; @@ -36,6 +38,10 @@ public class KeyguardStatusView extends GridLayout { super(context, attrs, defStyle); } + public int getAppWidgetId() { + return LockPatternUtils.ID_DEFAULT_STATUS_WIDGET; + } + @Override protected void onFinishInflate() { super.onFinishInflate(); 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 5b85064..2837a66 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardStatusViewManager.java @@ -16,15 +16,6 @@ package com.android.internal.policy.impl.keyguard; -import com.android.internal.R; -import com.android.internal.telephony.IccCardConstants; -import com.android.internal.widget.DigitalClock; -import com.android.internal.widget.LockPatternUtils; - -import java.util.Date; - -import libcore.util.MutableInt; - import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; @@ -39,6 +30,10 @@ import android.util.Log; import android.view.View; import android.widget.TextView; +import com.android.internal.R; +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.widget.LockPatternUtils; + import java.util.Date; import libcore.util.MutableInt; @@ -78,7 +73,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 +116,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 1cd796c..0000000 --- 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/KeyguardTransportControlView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java index 6a3c7c1..89f220a 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java @@ -16,17 +16,15 @@ package com.android.internal.policy.impl.keyguard; -import java.lang.ref.WeakReference; - import android.app.PendingIntent; import android.app.PendingIntent.CanceledException; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.media.AudioManager; +import android.media.IRemoteControlDisplay; import android.media.MediaMetadataRetriever; import android.media.RemoteControlClient; -import android.media.IRemoteControlDisplay; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -42,11 +40,12 @@ import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.View.OnClickListener; -import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import com.android.internal.R; + +import java.lang.ref.WeakReference; /** * This is the widget responsible for showing music controls in keyguard. */ @@ -264,7 +263,7 @@ public class KeyguardTransportControlView extends KeyguardWidgetFrame implements @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - int dim = Math.min(MAXDIM, Math.max(getWidth(), getHeight())); +// int dim = Math.min(MAXDIM, Math.max(getWidth(), getHeight())); // Log.v(TAG, "setting max bitmap size: " + dim + "x" + dim); // mAudioManager.remoteControlDisplayUsesBitmapSize(mIRCD, dim, dim); } 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 d8e1c1a..316825a 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 3d65e68..8c9ac8b 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 3191f4a..9e3424d 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 b66c883..9fa14f5 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java @@ -48,7 +48,7 @@ import com.android.internal.widget.LockPatternUtils; * reported to this class by the current {@link KeyguardViewBase}. */ public class KeyguardViewManager { - private final static boolean DEBUG = false; + private final static boolean DEBUG = KeyguardViewMediator.DEBUG; private static String TAG = "KeyguardViewManager"; public static boolean USE_UPPER_CASE = true; @@ -65,7 +65,6 @@ public class KeyguardViewManager { private FrameLayout mKeyguardHost; private KeyguardHostView mKeyguardView; - private boolean mScreenOn = false; private LockPatternUtils mLockPatternUtils; public interface ShowListener { @@ -96,7 +95,7 @@ public class KeyguardViewManager { boolean enableScreenRotation = shouldEnableScreenRotation(); - maybeCreateKeyguardLocked(enableScreenRotation, options); + maybeCreateKeyguardLocked(enableScreenRotation, false, options); maybeEnableScreenRotation(enableScreenRotation); // Disable common aspects of the system/status/navigation bars that are not appropriate or @@ -104,7 +103,7 @@ public class KeyguardViewManager { // activities. Other disabled bits are handled by the KeyguardViewMediator talking // directly to the status bar service. final int visFlags = View.STATUS_BAR_DISABLE_HOME; - if (DEBUG) Log.v(TAG, "KGVM: Set visibility on " + mKeyguardHost + " to " + visFlags); + if (DEBUG) Log.v(TAG, "show:setSystemUiVisibility(" + Integer.toHexString(visFlags)+")"); mKeyguardHost.setSystemUiVisibility(visFlags); mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); @@ -127,7 +126,12 @@ public class KeyguardViewManager { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - maybeCreateKeyguardLocked(shouldEnableScreenRotation(), null); + if (mKeyguardHost.getVisibility() == View.VISIBLE) { + // only propagate configuration messages if we're currently showing + maybeCreateKeyguardLocked(shouldEnableScreenRotation(), true, null); + } else { + if (DEBUG) Log.v(TAG, "onConfigurationChanged: view not visible"); + } } @Override @@ -145,7 +149,8 @@ public class KeyguardViewManager { SparseArray<Parcelable> mStateContainer = new SparseArray<Parcelable>(); - private void maybeCreateKeyguardLocked(boolean enableScreenRotation, Bundle options) { + private void maybeCreateKeyguardLocked(boolean enableScreenRotation, boolean force, + Bundle options) { final boolean isActivity = (mContext instanceof Activity); // for test activity if (mKeyguardHost != null) { @@ -189,7 +194,9 @@ public class KeyguardViewManager { mViewManager.addView(mKeyguardHost, lp); } - inflateKeyguardView(options); + if (force || mKeyguardView == null) { + inflateKeyguardView(options); + } updateUserActivityTimeoutInWindowLayoutParams(); mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); @@ -230,11 +237,6 @@ public class KeyguardViewManager { mKeyguardView.showNextSecurityScreenIfPresent(); } } - - if (mScreenOn) { - mKeyguardView.show(); - mKeyguardView.requestFocus(); - } } public void updateUserActivityTimeout() { @@ -295,12 +297,11 @@ public class KeyguardViewManager { if (DEBUG) Log.d(TAG, "reset()"); // User might have switched, check if we need to go back to keyguard // TODO: It's preferable to stay and show the correct lockscreen or unlock if none - maybeCreateKeyguardLocked(shouldEnableScreenRotation(), options); + maybeCreateKeyguardLocked(shouldEnableScreenRotation(), true, options); } public synchronized void onScreenTurnedOff() { if (DEBUG) Log.d(TAG, "onScreenTurnedOff()"); - mScreenOn = false; if (mKeyguardView != null) { mKeyguardView.onScreenTurnedOff(); } @@ -309,7 +310,6 @@ public class KeyguardViewManager { public synchronized void onScreenTurnedOn( final KeyguardViewManager.ShowListener showListener) { if (DEBUG) Log.d(TAG, "onScreenTurnedOn()"); - mScreenOn = true; if (mKeyguardView != null) { mKeyguardView.onScreenTurnedOn(); 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 cb70922..bc12e96 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java @@ -46,7 +46,6 @@ import android.util.EventLog; import android.util.Log; import android.view.KeyEvent; import android.view.WindowManager; -import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicy; import com.android.internal.telephony.IccCardConstants; @@ -96,7 +95,7 @@ import com.android.internal.widget.LockPatternUtils; */ public class KeyguardViewMediator { private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000; - private final static boolean DEBUG = false; + final static boolean DEBUG = false; private final static boolean DBG_WAKE = false; private final static String TAG = "KeyguardViewMediator"; @@ -520,11 +519,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 +628,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 +770,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); @@ -1306,13 +1308,6 @@ public class KeyguardViewMediator { // (like recents). Temporary enable/disable (e.g. the "back" button) are // done in KeyguardHostView. flags |= StatusBarManager.DISABLE_RECENT; - if (!mScreenOn) { - // Disable all navbar buttons on screen off. The navigation bar will hide - // these immediately to avoid seeing the end of layout transition animations - // if quickly turning back on. - flags |= StatusBarManager.DISABLE_HOME; - flags |= StatusBarManager.DISABLE_BACK; - } if (isSecure() || !ENABLE_INSECURE_STATUS_BAR_EXPAND) { // showing secure lockscreen; disable expanding. flags |= StatusBarManager.DISABLE_EXPAND; @@ -1328,7 +1323,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 0000000..c163b97 --- /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/KeyguardWidgetCarousel.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetCarousel.java new file mode 100644 index 0000000..02c32d4 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetCarousel.java @@ -0,0 +1,82 @@ +/* + * 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 com.android.internal.R; + +public class KeyguardWidgetCarousel extends KeyguardWidgetPager { + + private float mAdjacentPagesAngle; + private static float CAMERA_DISTANCE = 10000; + + public KeyguardWidgetCarousel(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public KeyguardWidgetCarousel(Context context) { + this(context, null, 0); + } + + public KeyguardWidgetCarousel(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mAdjacentPagesAngle = context.getResources().getInteger(R.integer.kg_carousel_angle); + } + + protected float getMaxScrollProgress() { + return 1.5f; + } + + 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); + if (!isReordering(false)) { + child.setBackgroundAlphaMultiplier( + backgroundAlphaInterpolator(Math.abs(scrollProgress))); + } else { + child.setBackgroundAlphaMultiplier(1f); + } + } + } + } + } + + @Override + protected void screenScrolled(int screenCenter) { + updatePageAlphaValues(screenCenter); + for (int i = 0; i < getChildCount(); i++) { + KeyguardWidgetFrame v = getWidgetPageAt(i); + if (v == mDragView) continue; + if (v != null) { + float scrollProgress = getScrollProgress(screenCenter, v, i); + int width = v.getMeasuredWidth(); + float pivotX = (width / 2f) + scrollProgress * (width / 2f); + float pivotY = v.getMeasuredHeight() / 2; + float rotationY = - mAdjacentPagesAngle * scrollProgress; + v.setCameraDistance(CAMERA_DISTANCE); + v.setPivotX(pivotX); + v.setPivotY(pivotY); + v.setRotationY(rotationY); + } + } + } +} 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 311eec6..9e1189c 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java @@ -16,20 +16,20 @@ package com.android.internal.policy.impl.keyguard; +import android.appwidget.AppWidgetHostView; 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.os.PowerManager; -import android.os.SystemClock; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.MotionEvent; +import android.view.View; import android.widget.FrameLayout; import com.android.internal.R; @@ -48,8 +48,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 +66,115 @@ 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); + } + + public int getContentAppWidgetId() { + View content = getContent(); + if (content instanceof AppWidgetHostView) { + return ((AppWidgetHostView) content).getAppWidgetId(); + } else { + return ((KeyguardStatusView) content).getAppWidgetId(); + } } private void drawGradientOverlay(Canvas c) { @@ -99,6 +183,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 +269,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 +280,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 1e65665..63e7fdd 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java @@ -15,29 +15,52 @@ */ 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; +import android.content.res.Resources; 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 { +import com.android.internal.widget.LockPatternUtils; + +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; - private static float TRANSITION_PIVOT = 0.65f; private static float TRANSITION_MAX_ROTATION = 30; private static final boolean PERFORM_OVERSCROLL_ROTATION = true; - private AccelerateInterpolator mAlphaInterpolator = new AccelerateInterpolator(0.9f); - private DecelerateInterpolator mLeftScreenAlphaInterpolator = new DecelerateInterpolator(4); + + private KeyguardViewStateManager mViewStateManager; + private LockPatternUtils mLockPatternUtils; + + // 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 int mPage = 0; + private Callbacks mCallbacks; + + private boolean mCameraWidgetEnabled; public KeyguardWidgetPager(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -52,38 +75,195 @@ public class KeyguardWidgetPager extends PagedView { if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); } + + setPageSwitchListener(this); + + Resources r = getResources(); + mCameraWidgetEnabled = r.getBoolean(R.bool.kg_enable_camera_default_widget); + } + + public void setViewStateManager(KeyguardViewStateManager viewStateManager) { + mViewStateManager = viewStateManager; + } + + public void setLockPatternUtils(LockPatternUtils l) { + mLockPatternUtils = l; + } + + @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(); + mCallbacks.onPageSwitch(newPageIndex); + } + 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 onPageSwitch(int newPageIndex); + } + + public void addWidget(View widget) { + addWidget(widget, -1); + } + + + public void onRemoveView(View v) { + int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId(); + mLockPatternUtils.removeAppWidget(appWidgetId); + } + + public void onAddView(View v, int index) { + int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId(); + getVisiblePages(mTempVisiblePagesRange); + boundByReorderablePages(true, mTempVisiblePagesRange); + // Subtract from the index to take into account pages before the reorderable + // pages (e.g. the "add widget" page) + mLockPatternUtils.addAppWidget(appWidgetId, index - mTempVisiblePagesRange[0]); } /* - * 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 +271,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 +303,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,74 +320,59 @@ public class KeyguardWidgetPager extends PagedView { acceleratedOverScroll(amount); } - // In apps customize, we have a scrolling effect which emulates pulling cards off of a stack. + float backgroundAlphaInterpolator(float r) { + return Math.min(1f, 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); + if (!isReordering(false)) { + child.setBackgroundAlphaMultiplier( + backgroundAlphaInterpolator(Math.abs(scrollProgress))); + } else { + child.setBackgroundAlphaMultiplier(1f); + } + } + } + } + } + @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 = - 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 alpha = 1.0f; v.setCameraDistance(mDensity * CAMERA_DISTANCE); - int pageWidth = v.getMeasuredWidth(); - int pageHeight = v.getMeasuredHeight(); if (PERFORM_OVERSCROLL_ROTATION) { if (i == 0 && scrollProgress < 0) { - // Overscroll to the left - v.setPivotX(TRANSITION_PIVOT * pageWidth); + // Over scroll to the left v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); - if (v instanceof KeyguardWidgetFrame) { - ((KeyguardWidgetFrame) v).setOverScrollAmount(Math.abs(scrollProgress), - true); - } - scale = 1.0f; + v.setOverScrollAmount(Math.abs(scrollProgress), true); alpha = 1.0f; // On the first page, we don't want the page to have any lateral motion - translationX = 0; } else if (i == getChildCount() - 1 && scrollProgress > 0) { - // Overscroll to the right - v.setPivotX((1 - TRANSITION_PIVOT) * pageWidth); + // Over scroll to the right 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); v.setAlpha(alpha); // If the view has 0 alpha, we set it to be invisible so as to prevent @@ -215,4 +385,130 @@ public class KeyguardWidgetPager extends PagedView { } } } + @Override + void boundByReorderablePages(boolean isReordering, int[] range) { + if (isReordering) { + if (isAddWidgetPageVisible()) { + range[0]++; + } + if (isMusicWidgetVisible()) { + range[1]--; + } + if (isCameraWidgetVisible()) { + range[1]--; + } + } + } + + /* + * 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() { + return mCameraWidgetEnabled; + } + + @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 4ff6f27..0000000 --- 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 0000000..3ccc7ea --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/MultiPaneChallengeLayout.java @@ -0,0 +1,491 @@ +/* + * 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 - widthUsed) * lp.centerWithinArea + 0.5f), + MeasureSpec.EXACTLY); + adjustedHeightSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(heightSpec) - heightUsed, MeasureSpec.EXACTLY); + } else { + adjustedWidthSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(widthSpec) - widthUsed, MeasureSpec.EXACTLY); + adjustedHeightSpec = MeasureSpec.makeMeasureSpec( + (int) ((height - heightUsed) * lp.centerWithinArea + 0.5f), + MeasureSpec.EXACTLY); + } + } else { + adjustedWidthSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(widthSpec) - widthUsed, MeasureSpec.EXACTLY); + adjustedHeightSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(heightSpec) - heightUsed, MeasureSpec.EXACTLY); + } + if (lp.maxWidth >= 0) { + adjustedWidthSpec = MeasureSpec.makeMeasureSpec( + Math.min(lp.maxWidth, MeasureSpec.getSize(adjustedWidthSpec)), + MeasureSpec.EXACTLY); + } + if (lp.maxHeight >= 0) { + adjustedHeightSpec = MeasureSpec.makeMeasureSpec( + Math.min(lp.maxHeight, MeasureSpec.getSize(adjustedHeightSpec)), + MeasureSpec.EXACTLY); + } + + measureChildWithMargins(child, adjustedWidthSpec, 0, adjustedHeightSpec, 0); + } + } + + @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 0000000..060cc03 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java @@ -0,0 +1,116 @@ +/* + * 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.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 86c05b1..3562071 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,16 @@ 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; @@ -41,6 +45,8 @@ import android.view.ViewParent; 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; @@ -52,7 +58,7 @@ import java.util.ArrayList; * An abstraction of the original Workspace which supports browsing through a * sequential list of "pages" */ -public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener { +public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener { private static final String TAG = "WidgetPagedView"; private static final boolean DEBUG = false; protected static final int INVALID_PAGE = -1; @@ -60,7 +66,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 +84,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 +108,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 +126,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 +135,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 +167,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 +193,47 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL protected static final int sScrollIndicatorFadeOutDuration = 650; protected static final int sScrollIndicatorFlashDuration = 650; + // The viewport whether the pages are to be contained (the actual view may be larger than the + // viewport) + private Rect mViewport = new Rect(); + + // 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; + private boolean mIsFlingingToDelete = false; + public interface PageSwitchListener { void onPageSwitch(View newPage, int newPageIndex); } @@ -205,24 +251,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 +272,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(); @@ -243,12 +279,76 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mDensity = getResources().getDisplayMetrics().density; + // Scale the fling-to-delete threshold by the density + mFlingToDeleteThresholdVelocity = + (int) (mFlingToDeleteThresholdVelocity * mDensity); + mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity); mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity); mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity); 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 getViewportWidth() { + return mViewport.width(); + } + int getViewportHeight() { + return mViewport.height(); + } + + // Convenience methods to get the offset ASSUMING that we are centering the pages in the + // PagedView both horizontally and vertically + int getViewportOffsetX() { + return (getMeasuredWidth() - getViewportWidth()) / 2; + } + int getViewportOffsetY() { + return (getMeasuredHeight() - getViewportHeight()) / 2; + } + public void setPageSwitchListener(PageSwitchListener pageSwitchListener) { mPageSwitchListener = pageSwitchListener; if (mPageSwitchListener != null) { @@ -328,6 +428,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 +504,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,10 +566,20 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL return; } - final int widthMode = MeasureSpec.getMode(widthMeasureSpec); - final int widthSize = MeasureSpec.getSize(widthMeasureSpec); - final int heightMode = MeasureSpec.getMode(heightMeasureSpec); - final int heightSize = MeasureSpec.getSize(heightMeasureSpec); + // 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 + View parent = (View) getParent(); + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + // NOTE: We multiply by 1.5f to account for the fact that depending on the offset of the + // viewport, we can be at most one and a half screens offset once we scale down + int parentWidthSize = (int) (1.5f * parent.getMeasuredWidth()); + int parentHeightSize = parent.getMeasuredHeight(); + int scaledWidthSize = (int) (parentWidthSize / mMinScale); + int scaledHeightSize = (int) (parentHeightSize / mMinScale); + mViewport.set(0, 0, widthSize, heightSize); if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -481,13 +603,29 @@ 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.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize); + if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize); + if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding); + if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding); 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 +634,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(): " + getViewportWidth() + ", " + getChildWidth(0)); // Calculate the variable page spacing if necessary @@ -547,19 +684,21 @@ 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 offsetX = getViewportOffsetX(); + int offsetY = getViewportOffsetY(); + + // Update the viewport offsets + mViewport.offset(offsetX, offsetY); + + int childLeft = offsetX + getRelativeChildOffset(0); for (int i = 0; i < childCount; i++) { final View child = getPageAt(i); + int childTop = offsetY + 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, @@ -577,22 +716,6 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL } protected void screenScrolled(int screenCenter) { - if (isScrollingIndicatorEnabled()) { - updateScrollingIndicator(); - } - boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX; - - if (mFadeInAdjacentScreens && !isInOverscroll) { - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - if (child != null) { - float scrollProgress = getScrollProgress(screenCenter, child, i); - float alpha = 1 - Math.abs(scrollProgress); - child.setAlpha(alpha); - } - } - invalidate(); - } } @Override @@ -606,7 +729,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 +782,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL } else { final int padding = getPaddingLeft() + getPaddingRight(); final int offset = getPaddingLeft() + - (getMeasuredWidth() - padding - getChildWidth(index)) / 2; + (getViewportWidth() - padding - getChildWidth(index)) / 2; if (mChildRelativeOffsets != null) { mChildRelativeOffsets[index] = offset; } @@ -676,33 +799,47 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL return (int) (maxWidth * mLayoutScale + 0.5f); } + void boundByReorderablePages(boolean isReordering, int[] range) { + // Do nothing + } + + // 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 = getViewportWidth(); int leftScreen = 0; int rightScreen = 0; + int offsetX = getViewportOffsetX() + 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 +848,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL @Override protected void dispatchDraw(Canvas canvas) { - int halfScreenSize = getMeasuredWidth() / 2; + int halfScreenSize = getViewportWidth() / 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 +865,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 +875,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 +981,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 < getViewportOffsetX() + 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 > (getViewportOffsetX() + getViewportWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing)); } @Override @@ -912,12 +1043,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 = getViewportOffsetX() + mEdgeSwipeRegionSize; + int rightEdgeBoundary = getMeasuredWidth() - getViewportOffsetX() - mEdgeSwipeRegionSize; + if ((mDownMotionX <= leftEdgeBoundary || mDownMotionX >= rightEdgeBoundary)) { + mDownEventOnEdge = true; + } /* * If being flinged and user touches the screen, initiate drag; @@ -935,12 +1077,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 +1093,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 +1123,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,38 +1151,27 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL mTotalMotionX += Math.abs(mLastMotionX - x); mLastMotionX = x; mLastMotionXRemainder = 0; - mTouchX = getScrollX(); + mTouchX = getViewportOffsetX() + 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 getMaxScrollProgress() { + return 1.0f; } protected float getScrollProgress(int screenCenter, View v, int page) { - final int halfScreenSize = getMeasuredWidth() / 2; + final int halfScreenSize = getViewportWidth() / 2; int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing; int delta = screenCenter - (getChildOffset(page) - getRelativeChildOffset(page) + halfScreenSize); float scrollProgress = delta / (totalDistance * 1.0f); - scrollProgress = Math.min(scrollProgress, 1.0f); - scrollProgress = Math.max(scrollProgress, -1.0f); + scrollProgress = Math.min(scrollProgress, getMaxScrollProgress()); + scrollProgress = Math.max(scrollProgress, - getMaxScrollProgress()); return scrollProgress; } @@ -1045,7 +1183,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL } protected void acceleratedOverScroll(float amount) { - int screenSize = getMeasuredWidth(); + int screenSize = getViewportWidth(); // We want to reach the max over scroll effect when the user has // over scrolled half the size of the screen @@ -1070,7 +1208,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL } protected void dampedOverScroll(float amount) { - int screenSize = getMeasuredWidth(); + int screenSize = getViewportWidth(); float f = (amount / screenSize); @@ -1130,9 +1268,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 = getViewportOffsetX() + mEdgeSwipeRegionSize; + int rightEdgeBoundary = getMeasuredWidth() - getViewportOffsetX() - mEdgeSwipeRegionSize; + if ((mDownMotionX <= leftEdgeBoundary || mDownMotionX >= rightEdgeBoundary)) { + mDownEventOnEdge = true; + } + if (mTouchState == TOUCH_STATE_SCROLLING) { pageBeginMoving(); } @@ -1164,6 +1315,107 @@ 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 * + getViewportWidth()); + int leftBufferEdge = (int) (mapPointFromSelfToParent(mViewport.left, 0)[0] + + bufferSize); + int rightBufferEdge = (int) (mapPointFromSelfToParent(mViewport.right, 0)[0] + - bufferSize); + + if (DEBUG) Log.d(TAG, "leftBufferEdge: " + leftBufferEdge); + if (DEBUG) Log.d(TAG, "rightBufferEdge: " + rightBufferEdge); + if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX); + if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY); + if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX); + if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY); + + 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 = getViewportOffsetX() + getChildOffset(i); + int newX = getViewportOffsetX() + 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); + onRemoveView(mDragView); + addView(mDragView, pageUnderPointIndex); + onAddView(mDragView, pageUnderPointIndex); + mSidePageHoverIndex = -1; + } + }; + postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT); + } + } else { + removeCallbacks(mSidePageHoverRunnable); + mSidePageHoverIndex = -1; + } } else { determineScrollingStart(ev); } @@ -1232,21 +1484,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 +1517,20 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL return true; } + //public abstract void onFlingToDelete(View v); + public abstract void onRemoveView(View v); + public abstract void onAddView(View v, int index); + + 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 +1593,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 +1624,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 = getViewportOffsetX() + getScrollX() + (getViewportWidth() / 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 = getViewportOffsetX() + getChildOffset(i) + halfChildWidth; int distanceFromScreenCenter = Math.abs(childCenter - screenCenter); if (distanceFromScreenCenter < minDistanceFromScreenCenter) { minDistanceFromScreenCenter = distanceFromScreenCenter; @@ -1397,11 +1681,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 = getViewportWidth() / 2; if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage)); if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): " - + getMeasuredWidth() + ", " + getChildWidth(whichPage)); + + getViewportWidth() + ", " + getChildWidth(whichPage)); final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); int delta = newX - mUnboundedScrollX; int duration = 0; @@ -1435,20 +1719,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(): " + getViewportWidth() + ", " + 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 +1752,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 +1762,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 +1801,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 +1939,7 @@ public class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeL if (!isScrollingIndicatorEnabled()) return; if (mScrollIndicator == null) return; int numPages = getChildCount(); - int pageWidth = getMeasuredWidth(); + int pageWidth = getViewportWidth(); int lastChildIndex = Math.max(0, getChildCount() - 1); int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex); int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight; @@ -1675,6 +1961,302 @@ 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; + } + + /* + * 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 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 = -(getViewportOffsetX() + getChildOffset(i)); + } else { + oldX = getViewportOffsetX() + getChildOffset(i - 1); + } + int newX = getViewportOffsetX() + 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); + onRemoveView(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 0000000..506b79d --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/SlidingChallengeLayout.java @@ -0,0 +1,960 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy.impl.keyguard; + +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.FloatProperty; +import android.util.Log; +import android.util.Property; +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"; + private static final boolean DEBUG = false; + + // Drawn to show the drag handle in closed state; crossfades to the challenge view + // when challenge is fully visible + private Drawable mHandleDrawable; + private Drawable mFrameDrawable; + private Drawable mDragIconDrawable; + + // 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 mGestureStartY; // where did you touch the screen to start this gesture? + private int mGestureStartChallengeBottom; // where was the challenge at that time? + private int mDragHandleSize; // handle hitrect extension into the challenge view + private int mDragHandleHeadroom; // extend the handle's hitrect this far above the line + private int mDragHandleEdgeSlop; + private int mChallengeBottomBound; // Number of pixels from the top of the challenge view + // that should remain on-screen + float mHandleAlpha; + float mFrameAlpha; + private ObjectAnimator mHandleAnimation; + private ObjectAnimator mFrameAnimation; + + static final Property<SlidingChallengeLayout, Float> HANDLE_ALPHA = + new FloatProperty<SlidingChallengeLayout>("handleAlpha") { + @Override + public void setValue(SlidingChallengeLayout view, float value) { + view.mHandleAlpha = value; + view.invalidate(); + } + + @Override + public Float get(SlidingChallengeLayout view) { + return view.mHandleAlpha; + } + }; + + static final Property<SlidingChallengeLayout, Float> FRAME_ALPHA = + new FloatProperty<SlidingChallengeLayout>("frameAlpha") { + @Override + public void setValue(SlidingChallengeLayout view, float value) { + if (view.mFrameDrawable != null) { + view.mFrameAlpha = value; + view.mFrameDrawable.setAlpha((int) (value * 0xFF)); + view.mFrameDrawable.invalidateSelf(); + } + } + + @Override + public Float get(SlidingChallengeLayout view) { + return view.mFrameAlpha; + } + }; + + private static final int DRAG_HANDLE_DEFAULT_SIZE = 32; // dp + private static final int HANDLE_ANIMATE_DURATION = 200; // ms + + // 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); + setDragDrawables(a.getDrawable(R.styleable.SlidingChallengeLayout_dragHandle), + a.getDrawable(R.styleable.SlidingChallengeLayout_dragIcon)); + + 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 setDragDrawables(Drawable handle, Drawable icon) { + final float density = getResources().getDisplayMetrics().density; + final int defaultSize = (int) (DRAG_HANDLE_DEFAULT_SIZE * density + 0.5f); + final int handleHeight = handle != null ? handle.getIntrinsicHeight() : 0; + final int iconHeight = icon != null ? icon.getIntrinsicHeight() : 0; + mDragHandleSize = Math.max(handleHeight > 0 ? handleHeight : defaultSize, + iconHeight > 0 ? iconHeight : defaultSize); + + // top half of the lock icon, plus another 25% to be sure + mDragHandleHeadroom = (int) (iconHeight * 0.75f); + mChallengeBottomBound = (mDragHandleSize + mDragHandleHeadroom + handleHeight) / 2; + + mHandleDrawable = handle; + mDragIconDrawable = icon; + } + + public void setDragIconDrawable(Drawable d) { + mDragIconDrawable = d; + } + + public void showHandle(boolean visible) { + if (visible) { + if (mHandleAnimation != null) { + mHandleAnimation.cancel(); + mHandleAnimation = null; + } + mHandleAlpha = 1.f; + invalidate(); + } else { + animateHandle(false); + } + } + + void animateHandle(boolean visible) { + if (mHandleAnimation != null) { + mHandleAnimation.cancel(); + mHandleAnimation = null; + } + final float targetAlpha = visible ? 1.f : 0.f; + if (targetAlpha == mHandleAlpha) { + return; + } + mHandleAnimation = ObjectAnimator.ofFloat(this, HANDLE_ALPHA, targetAlpha); + mHandleAnimation.setInterpolator(sHandleFadeInterpolator); + mHandleAnimation.setDuration(HANDLE_ANIMATE_DURATION); + mHandleAnimation.start(); + } + + void animateFrame(boolean visible, boolean full) { + if (mFrameDrawable == null) return; + + if (mFrameAnimation != null) { + mFrameAnimation.cancel(); + mFrameAnimation = null; + } + final float targetAlpha = visible ? (full ? 1.f : 0.5f) : 0.f; + if (targetAlpha == mFrameAlpha) { + return; + } + + mFrameAnimation = ObjectAnimator.ofFloat(this, FRAME_ALPHA, targetAlpha); + mFrameAnimation.setInterpolator(sHandleFadeInterpolator); + mFrameAnimation.setDuration(HANDLE_ANIMATE_DURATION); + mFrameAnimation.start(); + } + + 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; + + animateHandle(state == SCROLL_STATE_IDLE && !mChallengeShowing); + animateFrame(state != SCROLL_STATE_IDLE, false); + 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; + } + final int sy = mChallengeView.getBottom(); + final 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) { + mChallengeShowing = showChallenge; + } + + /** + * @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(VISIBLE); + } + animateFrame(true, true); + if (mBouncerListener != null) { + mBouncerListener.onBouncerStateChanged(true); + } + } + + @Override + public void hideBouncer() { + if (!mIsBouncing) return; + setChallengeShowing(false); + mIsBouncing = false; + if (mScrimView != null) { + mScrimView.setVisibility(GONE); + } + animateFrame(false, false); + 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: + mGestureStartY = ev.getY(); + mBlockDrag = false; + 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 (!mIsBouncing && + (isInDragHandle(x, y) || crossedDragHandle(x, y, mGestureStartY) || + (isInChallengeView(x, y) && mScrollState == SCROLL_STATE_SETTLING)) && + mActivePointerId == INVALID_POINTER) { + mActivePointerId = ev.getPointerId(i); + mGestureStartY = ev.getY(); + mGestureStartChallengeBottom = getChallengeBottom(); + 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_DOWN: + mBlockDrag = false; + mGestureStartY = ev.getY(); + break; + + 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 && !mIsBouncing) { + 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, mGestureStartY) || + (isInChallengeView(x, y) && mScrollState == SCROLL_STATE_SETTLING)) + && mActivePointerId == INVALID_POINTER) { + mGestureStartY = y; + mActivePointerId = ev.getPointerId(i); + mGestureStartChallengeBottom = getChallengeBottom(); + 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 = ev.getY(index); + final float pos = Math.min(y - mGestureStartY, + getLayoutBottom() - mChallengeBottomBound); + + moveChallengeTo(mGestureStartChallengeBottom + (int) pos); + } + break; + } + return true; + } + + /** + * We only want to add additional vertical space to the drag handle when the panel is fully + * closed. + */ + private int getDragHandleHeadroom() { + return isChallengeShowing() ? 0 : mDragHandleHeadroom; + } + + 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() - getDragHandleHeadroom() && + x < getWidth() - mDragHandleEdgeSlop && + y < mChallengeView.getTop() + mDragHandleSize; + } + + private boolean crossedDragHandle(float x, float y, float initialY) { + final int challengeTop = mChallengeView.getTop(); + return x >= 0 && + x < getWidth() && + initialY < (challengeTop - getDragHandleHeadroom()) && + 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); + } + // We're going to play silly games with the frame's background drawable later. + mFrameDrawable = mChallengeView.getBackground(); + } 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 - mChallengeBottomBound) + * (1 - mChallengeOffset)); + child.setAlpha(mChallengeOffset / 2 + 0.5f); + 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(); + } + }); + if (mFrameDrawable != null) { + mFrameDrawable.setAlpha(0); + } + 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); + + final Paint debugPaint; + if (DEBUG) { + debugPaint = new Paint(); + debugPaint.setColor(0x40FF00CC); + // show the isInDragHandle() rect + c.drawRect(mDragHandleEdgeSlop, + mChallengeView.getTop() - getDragHandleHeadroom(), + getWidth() - mDragHandleEdgeSlop, + mChallengeView.getTop() + mDragHandleSize, + debugPaint); + } + + if (mChallengeView != null && mHandleAlpha > 0 && mHandleDrawable != null) { + final int top = mChallengeView.getTop(); + final int handleHeight = mHandleDrawable.getIntrinsicHeight(); + final int challengeLeft = mChallengeView.getLeft(); + final int challengeRight = mChallengeView.getRight(); + mHandleDrawable.setBounds(challengeLeft, top, challengeRight, top + handleHeight); + mHandleDrawable.setAlpha((int) (mHandleAlpha * 0xFF)); + mHandleDrawable.draw(c); + + if (DEBUG) { + // now show the actual drag handle + debugPaint.setStyle(Paint.Style.STROKE); + debugPaint.setStrokeWidth(1); + debugPaint.setColor(0xFF80FF00); + c.drawRect(challengeLeft, top, challengeRight, top + handleHeight, debugPaint); + } + + if (mDragIconDrawable != null) { + final int iconWidth = mDragIconDrawable.getIntrinsicWidth(); + final int iconHeight = mDragIconDrawable.getIntrinsicHeight(); + final int iconLeft = (challengeLeft + challengeRight - iconWidth) / 2; + final int iconTop = top + (handleHeight - iconHeight) / 2; + mDragIconDrawable.setBounds(iconLeft, iconTop, iconLeft + iconWidth, + iconTop + iconHeight); + mDragIconDrawable.setAlpha((int) (mHandleAlpha * 0xFF)); + mDragIconDrawable.draw(c); + + if (DEBUG) { + debugPaint.setColor(0xFF00FF00); + c.drawRect(iconLeft, iconTop, iconLeft + iconWidth, + iconTop + iconHeight, debugPaint); + } + } + } + } + + /** + * 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 layoutBottom = getLayoutBottom(); + final int challengeHeight = mChallengeView.getHeight(); + + bottom = Math.max(layoutBottom, + Math.min(bottom, layoutBottom + challengeHeight - mChallengeBottomBound)); + + float offset = 1.f - (float) (bottom - layoutBottom) / + (challengeHeight - mChallengeBottomBound); + mChallengeOffset = offset; + if (offset > 0 && !mChallengeShowing) { + setChallengeShowing(true); + } + + mChallengeView.layout(mChallengeView.getLeft(), + bottom - mChallengeView.getHeight(), mChallengeView.getRight(), bottom); + + mChallengeView.setAlpha(offset / 2 + 0.5f); + if (mScrollListener != null) { + mScrollListener.onScrollPositionChanged(offset, mChallengeView.getTop()); + } + postInvalidateOnAnimation(); + return true; + } + + /** + * The bottom edge of this SlidingChallengeLayout's coordinate system; will coincide with + * the bottom edge of mChallengeView when the challenge is fully opened. + */ + private int getLayoutBottom() { + final int bottomMargin = (mChallengeView == null) + ? 0 + : ((LayoutParams) mChallengeView.getLayoutParams()).bottomMargin; + final int layoutBottom = getHeight() - getPaddingBottom() - bottomMargin; + return layoutBottom; + } + + /** + * The bottom edge of mChallengeView; essentially, where the sliding challenge 'is'. + */ + private int getChallengeBottom() { + if (mChallengeView == null) return 0; + + return mChallengeView.getBottom(); + } + + /** + * 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); + if (!show) { + // Block any drags in progress so that callers can use this to disable dragging + // for other touch interactions. + mBlockDrag = true; + } + } + + 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 layoutBottom = getLayoutBottom(); + animateChallengeTo(show ? layoutBottom : + layoutBottom + mChallengeView.getHeight() - mChallengeBottomBound, velocity); + } + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams ? new LayoutParams((LayoutParams) p) : + p instanceof MarginLayoutParams ? new LayoutParams((MarginLayoutParams) p) : + new LayoutParams(p); + } + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams; + } + + public static class LayoutParams extends MarginLayoutParams { + public 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(); + } + } +} diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java index f960833..440f8e1 100644 --- a/services/java/com/android/server/AlarmManagerService.java +++ b/services/java/com/android/server/AlarmManagerService.java @@ -130,12 +130,14 @@ class AlarmManagerService extends IAlarmManager.Stub { PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - mTimeTickSender = PendingIntent.getBroadcast(context, 0, + mTimeTickSender = PendingIntent.getBroadcastAsUser(context, 0, new Intent(Intent.ACTION_TIME_TICK).addFlags( - Intent.FLAG_RECEIVER_REGISTERED_ONLY), 0); + Intent.FLAG_RECEIVER_REGISTERED_ONLY), 0, + UserHandle.ALL); Intent intent = new Intent(Intent.ACTION_DATE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - mDateChangeSender = PendingIntent.getBroadcast(context, 0, intent, 0); + mDateChangeSender = PendingIntent.getBroadcastAsUser(context, 0, intent, + Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL); // now that we have initied the driver schedule the alarm mClockReceiver= new ClockReceiver(); diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 679a22a..0e51c47 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -1561,7 +1561,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString); intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]); intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode()); - mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); + } finally { + Binder.restoreCallingIdentity(ident); + } return true; } } diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 37dee19..eb833eb 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -405,7 +405,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run mLastLocation.clear(); for (LocationProviderInterface p : mProviders) { updateProviderListenersLocked(p.getName(), false, mCurrentUserId); - p.switchUser(userId); + if (!LocationManager.FUSED_PROVIDER.equals(p.getName())) { + p.switchUser(userId); + } } mCurrentUserId = userId; updateProvidersLocked(); @@ -664,9 +666,27 @@ public class LocationManagerService extends ILocationManager.Stub implements Run mProvidersByName.remove(provider.getName()); } + /** + * Returns true if the specified UID is SYSTEM_UID or matches the current user. + * + * @param uid the uid + * @return true if uid is SYSTEM_UID or matches the current user + */ + private boolean isCurrentUserOrSystemLocked(int uid) { + return uid == Process.SYSTEM_UID || UserHandle.getUserId(uid) == mCurrentUserId; + } + + /** + * Returns the first UID in the current user's range. + * + * @return the first UID in the current user's range + */ + private int getCurrentUidBaseLocked() { + return UserHandle.getUid(mCurrentUserId, 0); + } - private boolean isAllowedBySettingsLocked(String provider, int userId) { - if (userId != mCurrentUserId) { + private boolean isAllowedBySettingsLocked(String provider, int uid) { + if (!isCurrentUserOrSystemLocked(uid)) { return false; } if (mEnabledProviders.contains(provider)) { @@ -675,6 +695,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (mDisabledProviders.contains(provider)) { return false; } + if (uid == Process.SYSTEM_UID) { + return true; + } + // Use system settings ContentResolver resolver = mContext.getContentResolver(); @@ -828,8 +852,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run public List<String> getProviders(Criteria criteria, boolean enabledOnly) { int allowedResolutionLevel = getCallerAllowedResolutionLevel(); ArrayList<String> out; - int callingUserId = UserHandle.getCallingUserId(); - long identity = Binder.clearCallingIdentity(); + final int callingUid = Binder.getCallingUid(); + final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { out = new ArrayList<String>(mProviders.size()); @@ -839,7 +863,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run continue; } if (allowedResolutionLevel >= getMinimumResolutionLevelForProviderUse(name)) { - if (enabledOnly && !isAllowedBySettingsLocked(name, callingUserId)) { + if (enabledOnly && !isAllowedBySettingsLocked(name, callingUid)) { continue; } if (criteria != null && !LocationProvider.propertiesMeetCriteria( @@ -915,7 +939,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run LocationProviderInterface p = mProviders.get(i); boolean isEnabled = p.isEnabled(); String name = p.getName(); - boolean shouldBeEnabled = isAllowedBySettingsLocked(name, mCurrentUserId); + boolean shouldBeEnabled = isAllowedBySettingsLocked(name, getCurrentUidBaseLocked()); if (isEnabled && !shouldBeEnabled) { updateProviderListenersLocked(name, false, mCurrentUserId); changesMade = true; @@ -943,7 +967,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run final int N = records.size(); for (int i = 0; i < N; i++) { UpdateRecord record = records.get(i); - if (UserHandle.getUserId(record.mReceiver.mUid) == userId) { + if (isCurrentUserOrSystemLocked(record.mReceiver.mUid)) { // Sends a notification message to the receiver if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) { if (deadReceivers == null) { @@ -982,7 +1006,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (records != null) { for (UpdateRecord record : records) { - if (UserHandle.getUserId(record.mReceiver.mUid) == mCurrentUserId) { + if (isCurrentUserOrSystemLocked(record.mReceiver.mUid)) { LocationRequest locationRequest = record.mRequest; providerRequest.locationRequests.add(locationRequest); if (locationRequest.getInterval() < providerRequest.interval) { @@ -1000,7 +1024,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // under that threshold. long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2; for (UpdateRecord record : records) { - if (UserHandle.getUserId(record.mReceiver.mUid) == mCurrentUserId) { + if (isCurrentUserOrSystemLocked(record.mReceiver.mUid)) { LocationRequest locationRequest = record.mRequest; if (locationRequest.getInterval() <= thresholdInterval) { worksource.add(record.mReceiver.mUid); @@ -1223,7 +1247,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run oldRecord.disposeLocked(false); } - boolean isProviderEnabled = isAllowedBySettingsLocked(name, UserHandle.getUserId(uid)); + boolean isProviderEnabled = isAllowedBySettingsLocked(name, uid); if (isProviderEnabled) { applyRequirementsLocked(name); } else { @@ -1278,9 +1302,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } // update provider + int currentUidBase = getCurrentUidBaseLocked(); for (String provider : providers) { // If provider is already disabled, don't need to do anything - if (!isAllowedBySettingsLocked(provider, mCurrentUserId)) { + if (!isAllowedBySettingsLocked(provider, currentUidBase)) { continue; } @@ -1298,7 +1323,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run request.getProvider()); // no need to sanitize this request, as only the provider name is used - long identity = Binder.clearCallingIdentity(); + final int callingUid = Binder.getCallingUid(); + final long identity = Binder.clearCallingIdentity(); try { if (mBlacklist.isBlacklisted(packageName)) { if (D) Log.d(TAG, "not returning last loc for blacklisted app: " + @@ -1314,7 +1340,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run LocationProviderInterface provider = mProvidersByName.get(name); if (provider == null) return null; - if (!isAllowedBySettingsLocked(name, mCurrentUserId)) return null; + if (!isAllowedBySettingsLocked(name, callingUid)) { + return null; + } Location location = mLastLocation.get(name); if (location == null) { @@ -1471,13 +1499,14 @@ public class LocationManagerService extends ILocationManager.Stub implements Run provider); if (LocationManager.FUSED_PROVIDER.equals(provider)) return false; - long identity = Binder.clearCallingIdentity(); + final int callingUid = Binder.getCallingUid(); + final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) return false; - return isAllowedBySettingsLocked(provider, mCurrentUserId); + return isAllowedBySettingsLocked(provider, callingUid); } } finally { Binder.restoreCallingIdentity(identity); @@ -1616,10 +1645,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run Receiver receiver = r.mReceiver; boolean receiverDead = false; - int receiverUserId = UserHandle.getUserId(receiver.mUid); - if (receiverUserId != mCurrentUserId) { + final int receiverUid = receiver.mUid; + if (!isCurrentUserOrSystemLocked(receiverUid)) { if (D) { - Log.d(TAG, "skipping loc update for background user " + receiverUserId + + Log.d(TAG, "skipping loc update for background uid " + receiverUid + " (current user: " + mCurrentUserId + ", app: " + receiver.mPackageName + ")"); } @@ -1716,7 +1745,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } synchronized (mLock) { - if (isAllowedBySettingsLocked(provider, mCurrentUserId)) { + if (isAllowedBySettingsLocked(provider, getCurrentUidBaseLocked())) { handleLocationChangedLocked(location, passive); } } diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 7c482f5..6b277c7 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -558,8 +558,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) { synchronized (mLock) { // Automation service is not bound, so pretend it died to perform clean up. - if (mUiAutomationService != null - && mUiAutomationService.mServiceInterface == serviceClient) { + if (mUiAutomationService != null && mUiAutomationService.mServiceInterface != null + && serviceClient != null && mUiAutomationService.mServiceInterface + .asBinder() == serviceClient.asBinder()) { mUiAutomationService.binderDied(); } } diff --git a/services/java/com/android/server/display/DisplayDeviceInfo.java b/services/java/com/android/server/display/DisplayDeviceInfo.java index e76bf44..247d8a0 100644 --- a/services/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/java/com/android/server/display/DisplayDeviceInfo.java @@ -17,6 +17,7 @@ package com.android.server.display; import android.util.DisplayMetrics; +import android.view.Display; import android.view.Surface; import libcore.util.Objects; @@ -138,6 +139,17 @@ final class DisplayDeviceInfo { */ public int rotation = Surface.ROTATION_0; + /** + * Display type. + */ + public int type; + + /** + * Display address, or null if none. + * Interpretation varies by display type. + */ + public String address; + public void setAssumedDensityForExternalDisplay(int width, int height) { densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080; // Technically, these values should be smaller than the apparent density @@ -162,7 +174,9 @@ final class DisplayDeviceInfo { && yDpi == other.yDpi && flags == other.flags && touch == other.touch - && rotation == other.rotation; + && rotation == other.rotation + && type == other.type + && Objects.equal(address, other.address); } @Override @@ -181,6 +195,8 @@ final class DisplayDeviceInfo { flags = other.flags; touch = other.touch; rotation = other.rotation; + type = other.type; + address = other.address; } // For debugging purposes @@ -191,6 +207,8 @@ final class DisplayDeviceInfo { + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi" + ", touch " + touchToString(touch) + flagsToString(flags) + ", rotation " + rotation + + ", type " + Display.typeToString(type) + + ", address " + address + "}"; } diff --git a/services/java/com/android/server/display/HeadlessDisplayAdapter.java b/services/java/com/android/server/display/HeadlessDisplayAdapter.java index 919733d..7a104d7 100644 --- a/services/java/com/android/server/display/HeadlessDisplayAdapter.java +++ b/services/java/com/android/server/display/HeadlessDisplayAdapter.java @@ -19,6 +19,7 @@ package com.android.server.display; import android.content.Context; import android.os.Handler; import android.util.DisplayMetrics; +import android.view.Display; /** * Provides a fake default display for headless systems. @@ -63,6 +64,7 @@ final class HeadlessDisplayAdapter extends DisplayAdapter { mInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY | DisplayDeviceInfo.FLAG_SECURE | DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS; + mInfo.type = Display.TYPE_BUILT_IN; mInfo.touch = DisplayDeviceInfo.TOUCH_NONE; } return mInfo; diff --git a/services/java/com/android/server/display/LocalDisplayAdapter.java b/services/java/com/android/server/display/LocalDisplayAdapter.java index fa56b83..b37d57f 100644 --- a/services/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/java/com/android/server/display/LocalDisplayAdapter.java @@ -22,6 +22,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.SystemProperties; import android.util.SparseArray; +import android.view.Display; import android.view.DisplayEventReceiver; import android.view.Surface; import android.view.Surface.PhysicalDisplayInfo; @@ -139,11 +140,13 @@ final class LocalDisplayAdapter extends DisplayAdapter { com.android.internal.R.string.display_manager_built_in_display_name); mInfo.flags |= DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY | DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT; + mInfo.type = Display.TYPE_BUILT_IN; mInfo.densityDpi = (int)(mPhys.density * 160 + 0.5f); mInfo.xDpi = mPhys.xDpi; mInfo.yDpi = mPhys.yDpi; mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL; } else { + mInfo.type = Display.TYPE_HDMI; mInfo.name = getContext().getResources().getString( com.android.internal.R.string.display_manager_hdmi_display_name); mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL; diff --git a/services/java/com/android/server/display/LogicalDisplay.java b/services/java/com/android/server/display/LogicalDisplay.java index aa62aee..1583137 100644 --- a/services/java/com/android/server/display/LogicalDisplay.java +++ b/services/java/com/android/server/display/LogicalDisplay.java @@ -189,6 +189,8 @@ final class LogicalDisplay { if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_SECURE) != 0) { mBaseDisplayInfo.flags |= Display.FLAG_SECURE; } + mBaseDisplayInfo.type = deviceInfo.type; + mBaseDisplayInfo.address = deviceInfo.address; mBaseDisplayInfo.name = deviceInfo.name; mBaseDisplayInfo.appWidth = deviceInfo.width; mBaseDisplayInfo.appHeight = deviceInfo.height; diff --git a/services/java/com/android/server/display/OverlayDisplayAdapter.java b/services/java/com/android/server/display/OverlayDisplayAdapter.java index c35fd98..36e9f74 100644 --- a/services/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/java/com/android/server/display/OverlayDisplayAdapter.java @@ -27,6 +27,7 @@ import android.os.IBinder; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Slog; +import android.view.Display; import android.view.Gravity; import android.view.Surface; @@ -240,6 +241,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { mInfo.xDpi = mDensityDpi; mInfo.yDpi = mDensityDpi; mInfo.flags = 0; + mInfo.type = Display.TYPE_OVERLAY; mInfo.touch = DisplayDeviceInfo.TOUCH_NONE; } return mInfo; diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java index 2ea83ee..45fff30 100644 --- a/services/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/java/com/android/server/display/WifiDisplayAdapter.java @@ -39,6 +39,7 @@ import android.os.Message; import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; +import android.view.Display; import android.view.Surface; import java.io.PrintWriter; @@ -293,9 +294,10 @@ final class WifiDisplayAdapter extends DisplayAdapter { float refreshRate = 60.0f; // TODO: get this for real String name = display.getFriendlyDisplayName(); + String address = display.getDeviceAddress(); IBinder displayToken = Surface.createDisplay(name, secure); mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height, - refreshRate, deviceFlags, surface); + refreshRate, deviceFlags, address, surface); sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED); scheduleUpdateNotificationLocked(); @@ -515,12 +517,13 @@ final class WifiDisplayAdapter extends DisplayAdapter { private final int mHeight; private final float mRefreshRate; private final int mFlags; + private final String mAddress; private Surface mSurface; private DisplayDeviceInfo mInfo; public WifiDisplayDevice(IBinder displayToken, String name, - int width, int height, float refreshRate, int flags, + int width, int height, float refreshRate, int flags, String address, Surface surface) { super(WifiDisplayAdapter.this, displayToken); mName = name; @@ -528,6 +531,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mHeight = height; mRefreshRate = refreshRate; mFlags = flags; + mAddress = address; mSurface = surface; } @@ -555,6 +559,8 @@ final class WifiDisplayAdapter extends DisplayAdapter { mInfo.height = mHeight; mInfo.refreshRate = mRefreshRate; mInfo.flags = mFlags; + mInfo.type = Display.TYPE_WIFI; + mInfo.address = mAddress; mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL; mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight); } diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java index fb93d05..e05442b 100644 --- a/services/java/com/android/server/pm/UserManagerService.java +++ b/services/java/com/android/server/pm/UserManagerService.java @@ -26,6 +26,7 @@ import android.app.IStopUserCallback; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.graphics.Bitmap; @@ -75,6 +76,7 @@ public class UserManagerService extends IUserManager.Stub { private static final String ATTR_SERIAL_NO = "serialNumber"; private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber"; private static final String ATTR_PARTIAL = "partial"; + private static final String ATTR_USER_VERSION = "version"; private static final String TAG_USERS = "users"; private static final String TAG_USER = "user"; @@ -84,6 +86,8 @@ public class UserManagerService extends IUserManager.Stub { private static final int MIN_USER_ID = 10; + private static final int USER_VERSION = 1; + private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms private final Context mContext; @@ -104,6 +108,7 @@ public class UserManagerService extends IUserManager.Stub { // This resets on a reboot. Otherwise it keeps incrementing so that user ids are // not reused in quick succession private int mNextUserId = MIN_USER_ID; + private int mUserVersion = 0; private static UserManagerService sInstance; @@ -432,12 +437,17 @@ public class UserManagerService extends IUserManager.Stub { if (lastSerialNumber != null) { mNextSerialNumber = Integer.parseInt(lastSerialNumber); } + String versionNumber = parser.getAttributeValue(null, ATTR_USER_VERSION); + if (versionNumber != null) { + mUserVersion = Integer.parseInt(versionNumber); + } } while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { String id = parser.getAttributeValue(null, ATTR_ID); UserInfo user = readUser(Integer.parseInt(id)); + if (user != null) { mUsers.put(user.id, user); if (user.isGuest()) { @@ -450,6 +460,7 @@ public class UserManagerService extends IUserManager.Stub { } } updateUserIdsLocked(); + upgradeIfNecessary(); } catch (IOException ioe) { fallbackToSingleUserLocked(); } catch (XmlPullParserException pe) { @@ -464,9 +475,35 @@ public class UserManagerService extends IUserManager.Stub { } } + /** + * This fixes an incorrect initialization of user name for the owner. + * TODO: Remove in the next release. + */ + private void upgradeIfNecessary() { + int userVersion = mUserVersion; + if (userVersion < 1) { + // Assign a proper name for the owner, if not initialized correctly before + UserInfo user = mUsers.get(UserHandle.USER_OWNER); + if ("Primary".equals(user.name)) { + user.name = mContext.getResources().getString(com.android.internal.R.string.owner_name); + writeUserLocked(user); + } + userVersion = 1; + } + + if (userVersion < USER_VERSION) { + Slog.w(LOG_TAG, "User version " + mUserVersion + " didn't upgrade as expected to " + + USER_VERSION); + } else { + mUserVersion = userVersion; + writeUserListLocked(); + } + } + private void fallbackToSingleUserLocked() { // Create the primary user - UserInfo primary = new UserInfo(0, "Primary", null, + UserInfo primary = new UserInfo(0, + mContext.getResources().getString(com.android.internal.R.string.owner_name), null, UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY | UserInfo.FLAG_INITIALIZED); mUsers.put(0, primary); mNextSerialNumber = MIN_USER_ID; @@ -547,6 +584,7 @@ public class UserManagerService extends IUserManager.Stub { serializer.startTag(null, TAG_USERS); serializer.attribute(null, ATTR_NEXT_SERIAL_NO, Integer.toString(mNextSerialNumber)); + serializer.attribute(null, ATTR_USER_VERSION, Integer.toString(mUserVersion)); for (int i = 0; i < mUsers.size(); i++) { UserInfo user = mUsers.valueAt(i); diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java index 4abd8f5..317fec0 100644 --- a/services/java/com/android/server/power/DisplayPowerController.java +++ b/services/java/com/android/server/power/DisplayPowerController.java @@ -648,10 +648,10 @@ final class DisplayPowerController { mUsingScreenAutoBrightness = false; } if (mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_DIM) { - // Dim slowly by at least some minimum amount. + // Dim quickly by at least some minimum amount. target = Math.min(target - SCREEN_DIM_MINIMUM_REDUCTION, mScreenBrightnessDimConfig); - slow = true; + slow = false; } else if (wasDim) { // Brighten quickly. slow = false; diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java index 03a0434..039319d 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java @@ -1566,9 +1566,17 @@ public class WifiP2pService extends IWifiP2pManager.Stub { WifiP2pManager.ERROR); } break; - /* The supplicant misses the group removed event at times and just - * sends a network disconnect event */ - case WifiMonitor.NETWORK_DISCONNECTION_EVENT: + /* We do not listen to NETWORK_DISCONNECTION_EVENT for group removal + * handling since supplicant actually tries to reconnect after a temporary + * disconnect until group idle time out. Eventually, a group removal event + * will come when group has been removed. + * + * When there are connectivity issues during temporary disconnect, the application + * will also just remove the group. + * + * Treating network disconnection as group removal causes race conditions since + * supplicant would still maintain the group at that stage. + */ case WifiMonitor.P2P_GROUP_REMOVED_EVENT: if (DBG) logd(getName() + " group removed"); handleGroupRemoved(); |
