diff options
342 files changed, 7516 insertions, 4702 deletions
diff --git a/api/current.txt b/api/current.txt index e91244074522..eb888afc10dd 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4830,17 +4830,17 @@ package android.app { field public android.app.Notification.Action[] actions; field public android.media.AudioAttributes audioAttributes; field public deprecated int audioStreamType; - field public android.widget.RemoteViews bigContentView; + field public deprecated android.widget.RemoteViews bigContentView; field public java.lang.String category; field public int color; field public android.app.PendingIntent contentIntent; - field public android.widget.RemoteViews contentView; + field public deprecated android.widget.RemoteViews contentView; field public int defaults; field public android.app.PendingIntent deleteIntent; field public android.os.Bundle extras; field public int flags; field public android.app.PendingIntent fullScreenIntent; - field public android.widget.RemoteViews headsUpContentView; + field public deprecated android.widget.RemoteViews headsUpContentView; field public deprecated int icon; field public int iconLevel; field public deprecated android.graphics.Bitmap largeIcon; @@ -4931,14 +4931,22 @@ package android.app { method public android.app.Notification.Builder extend(android.app.Notification.Extender); method public android.os.Bundle getExtras(); method public deprecated android.app.Notification getNotification(); + method public android.widget.RemoteViews makeBigContentView(); + method public android.widget.RemoteViews makeContentView(); + method public android.widget.RemoteViews makeHeadsUpContentView(); + method public static android.app.Notification.Builder recoverBuilder(android.content.Context, android.app.Notification); + method public android.app.Notification.Builder setActions(android.app.Notification.Action...); method public android.app.Notification.Builder setAutoCancel(boolean); method public android.app.Notification.Builder setCategory(java.lang.String); method public android.app.Notification.Builder setColor(int); - method public android.app.Notification.Builder setContent(android.widget.RemoteViews); + method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews); method public android.app.Notification.Builder setContentInfo(java.lang.CharSequence); method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent); method public android.app.Notification.Builder setContentText(java.lang.CharSequence); method public android.app.Notification.Builder setContentTitle(java.lang.CharSequence); + method public android.app.Notification.Builder setCustomBigContentView(android.widget.RemoteViews); + method public android.app.Notification.Builder setCustomContentView(android.widget.RemoteViews); + method public android.app.Notification.Builder setCustomHeadsUpContentView(android.widget.RemoteViews); method public android.app.Notification.Builder setDefaults(int); method public android.app.Notification.Builder setDeleteIntent(android.app.PendingIntent); method public android.app.Notification.Builder setExtras(android.os.Bundle); @@ -9409,6 +9417,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct"; field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand"; field public static final java.lang.String FEATURE_FINGERPRINT = "android.hardware.fingerprint"; + field public static final java.lang.String FEATURE_FREEFORM_WINDOW_MANAGEMENT = "android.software.freeform_window_management"; field public static final java.lang.String FEATURE_GAMEPAD = "android.hardware.gamepad"; field public static final java.lang.String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors"; field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen"; @@ -22153,6 +22162,7 @@ package android.opengl { field public static final int GL_ACTIVE_PROGRAM = 33369; // 0x8259 field public static final int GL_ACTIVE_RESOURCES = 37621; // 0x92f5 field public static final int GL_ACTIVE_VARIABLES = 37637; // 0x9305 + field public static final int GL_ALL_BARRIER_BITS = -1; // 0xffffffff field public static final int GL_ALL_SHADER_BITS = -1; // 0xffffffff field public static final int GL_ARRAY_SIZE = 37627; // 0x92fb field public static final int GL_ARRAY_STRIDE = 37630; // 0x92fe @@ -22176,6 +22186,7 @@ package android.opengl { field public static final int GL_DISPATCH_INDIRECT_BUFFER_BINDING = 37103; // 0x90ef field public static final int GL_DRAW_INDIRECT_BUFFER = 36671; // 0x8f3f field public static final int GL_DRAW_INDIRECT_BUFFER_BINDING = 36675; // 0x8f43 + field public static final int GL_ELEMENT_ARRAY_BARRIER_BIT = 2; // 0x2 field public static final int GL_FRAGMENT_SHADER_BIT = 2; // 0x2 field public static final int GL_FRAMEBUFFER_BARRIER_BIT = 1024; // 0x400 field public static final int GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS = 37652; // 0x9314 @@ -22265,6 +22276,7 @@ package android.opengl { field public static final int GL_SAMPLE_MASK = 36433; // 0x8e51 field public static final int GL_SAMPLE_MASK_VALUE = 36434; // 0x8e52 field public static final int GL_SAMPLE_POSITION = 36432; // 0x8e50 + field public static final int GL_SHADER_IMAGE_ACCESS_BARRIER_BIT = 32; // 0x20 field public static final int GL_SHADER_STORAGE_BARRIER_BIT = 8192; // 0x2000 field public static final int GL_SHADER_STORAGE_BLOCK = 37606; // 0x92e6 field public static final int GL_SHADER_STORAGE_BUFFER = 37074; // 0x90d2 @@ -22310,6 +22322,7 @@ package android.opengl { field public static final int GL_UNSIGNED_INT_IMAGE_3D = 36964; // 0x9064 field public static final int GL_UNSIGNED_INT_IMAGE_CUBE = 36966; // 0x9066 field public static final int GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE = 37130; // 0x910a + field public static final int GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT = 1; // 0x1 field public static final int GL_VERTEX_ATTRIB_BINDING = 33492; // 0x82d4 field public static final int GL_VERTEX_ATTRIB_RELATIVE_OFFSET = 33493; // 0x82d5 field public static final int GL_VERTEX_BINDING_BUFFER = 36687; // 0x8f4f @@ -36464,6 +36477,7 @@ package android.view { method public boolean performHapticFeedback(int); method public boolean performHapticFeedback(int, int); method public boolean performLongClick(); + method public boolean performLongClick(float, float); method public void playSoundEffect(int); method public boolean post(java.lang.Runnable); method public boolean postDelayed(java.lang.Runnable, long); @@ -39199,6 +39213,7 @@ package android.webkit { method public abstract android.net.Uri getUrl(); method public abstract boolean hasGesture(); method public abstract boolean isForMainFrame(); + method public abstract boolean isRedirect(); } public class WebResourceResponse { @@ -39561,7 +39576,8 @@ package android.webkit { method public deprecated android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, java.lang.String); method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, android.webkit.WebResourceRequest); method public boolean shouldOverrideKeyEvent(android.webkit.WebView, android.view.KeyEvent); - method public boolean shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String); + method public deprecated boolean shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String); + method public boolean shouldOverrideUrlLoading(android.webkit.WebView, android.webkit.WebResourceRequest); field public static final int ERROR_AUTHENTICATION = -4; // 0xfffffffc field public static final int ERROR_BAD_URL = -12; // 0xfffffff4 field public static final int ERROR_CONNECT = -6; // 0xfffffffa diff --git a/api/system-current.txt b/api/system-current.txt index b996af313a07..d288ad1a4443 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4947,17 +4947,17 @@ package android.app { field public android.app.Notification.Action[] actions; field public android.media.AudioAttributes audioAttributes; field public deprecated int audioStreamType; - field public android.widget.RemoteViews bigContentView; + field public deprecated android.widget.RemoteViews bigContentView; field public java.lang.String category; field public int color; field public android.app.PendingIntent contentIntent; - field public android.widget.RemoteViews contentView; + field public deprecated android.widget.RemoteViews contentView; field public int defaults; field public android.app.PendingIntent deleteIntent; field public android.os.Bundle extras; field public int flags; field public android.app.PendingIntent fullScreenIntent; - field public android.widget.RemoteViews headsUpContentView; + field public deprecated android.widget.RemoteViews headsUpContentView; field public deprecated int icon; field public int iconLevel; field public deprecated android.graphics.Bitmap largeIcon; @@ -5048,14 +5048,22 @@ package android.app { method public android.app.Notification.Builder extend(android.app.Notification.Extender); method public android.os.Bundle getExtras(); method public deprecated android.app.Notification getNotification(); + method public android.widget.RemoteViews makeBigContentView(); + method public android.widget.RemoteViews makeContentView(); + method public android.widget.RemoteViews makeHeadsUpContentView(); + method public static android.app.Notification.Builder recoverBuilder(android.content.Context, android.app.Notification); + method public android.app.Notification.Builder setActions(android.app.Notification.Action...); method public android.app.Notification.Builder setAutoCancel(boolean); method public android.app.Notification.Builder setCategory(java.lang.String); method public android.app.Notification.Builder setColor(int); - method public android.app.Notification.Builder setContent(android.widget.RemoteViews); + method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews); method public android.app.Notification.Builder setContentInfo(java.lang.CharSequence); method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent); method public android.app.Notification.Builder setContentText(java.lang.CharSequence); method public android.app.Notification.Builder setContentTitle(java.lang.CharSequence); + method public android.app.Notification.Builder setCustomBigContentView(android.widget.RemoteViews); + method public android.app.Notification.Builder setCustomContentView(android.widget.RemoteViews); + method public android.app.Notification.Builder setCustomHeadsUpContentView(android.widget.RemoteViews); method public android.app.Notification.Builder setDefaults(int); method public android.app.Notification.Builder setDeleteIntent(android.app.PendingIntent); method public android.app.Notification.Builder setExtras(android.os.Bundle); @@ -9703,6 +9711,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct"; field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand"; field public static final java.lang.String FEATURE_FINGERPRINT = "android.hardware.fingerprint"; + field public static final java.lang.String FEATURE_FREEFORM_WINDOW_MANAGEMENT = "android.software.freeform_window_management"; field public static final java.lang.String FEATURE_GAMEPAD = "android.hardware.gamepad"; field public static final java.lang.String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors"; field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen"; @@ -24098,6 +24107,7 @@ package android.opengl { field public static final int GL_ACTIVE_PROGRAM = 33369; // 0x8259 field public static final int GL_ACTIVE_RESOURCES = 37621; // 0x92f5 field public static final int GL_ACTIVE_VARIABLES = 37637; // 0x9305 + field public static final int GL_ALL_BARRIER_BITS = -1; // 0xffffffff field public static final int GL_ALL_SHADER_BITS = -1; // 0xffffffff field public static final int GL_ARRAY_SIZE = 37627; // 0x92fb field public static final int GL_ARRAY_STRIDE = 37630; // 0x92fe @@ -24121,6 +24131,7 @@ package android.opengl { field public static final int GL_DISPATCH_INDIRECT_BUFFER_BINDING = 37103; // 0x90ef field public static final int GL_DRAW_INDIRECT_BUFFER = 36671; // 0x8f3f field public static final int GL_DRAW_INDIRECT_BUFFER_BINDING = 36675; // 0x8f43 + field public static final int GL_ELEMENT_ARRAY_BARRIER_BIT = 2; // 0x2 field public static final int GL_FRAGMENT_SHADER_BIT = 2; // 0x2 field public static final int GL_FRAMEBUFFER_BARRIER_BIT = 1024; // 0x400 field public static final int GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS = 37652; // 0x9314 @@ -24210,6 +24221,7 @@ package android.opengl { field public static final int GL_SAMPLE_MASK = 36433; // 0x8e51 field public static final int GL_SAMPLE_MASK_VALUE = 36434; // 0x8e52 field public static final int GL_SAMPLE_POSITION = 36432; // 0x8e50 + field public static final int GL_SHADER_IMAGE_ACCESS_BARRIER_BIT = 32; // 0x20 field public static final int GL_SHADER_STORAGE_BARRIER_BIT = 8192; // 0x2000 field public static final int GL_SHADER_STORAGE_BLOCK = 37606; // 0x92e6 field public static final int GL_SHADER_STORAGE_BUFFER = 37074; // 0x90d2 @@ -24255,6 +24267,7 @@ package android.opengl { field public static final int GL_UNSIGNED_INT_IMAGE_3D = 36964; // 0x9064 field public static final int GL_UNSIGNED_INT_IMAGE_CUBE = 36966; // 0x9066 field public static final int GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE = 37130; // 0x910a + field public static final int GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT = 1; // 0x1 field public static final int GL_VERTEX_ATTRIB_BINDING = 33492; // 0x82d4 field public static final int GL_VERTEX_ATTRIB_RELATIVE_OFFSET = 33493; // 0x82d5 field public static final int GL_VERTEX_BINDING_BUFFER = 36687; // 0x8f4f @@ -38761,6 +38774,7 @@ package android.view { method public boolean performHapticFeedback(int); method public boolean performHapticFeedback(int, int); method public boolean performLongClick(); + method public boolean performLongClick(float, float); method public void playSoundEffect(int); method public boolean post(java.lang.Runnable); method public boolean postDelayed(java.lang.Runnable, long); @@ -41559,6 +41573,7 @@ package android.webkit { method public abstract android.net.Uri getUrl(); method public abstract boolean hasGesture(); method public abstract boolean isForMainFrame(); + method public abstract boolean isRedirect(); } public class WebResourceResponse { @@ -41968,7 +41983,8 @@ package android.webkit { method public deprecated android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, java.lang.String); method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, android.webkit.WebResourceRequest); method public boolean shouldOverrideKeyEvent(android.webkit.WebView, android.view.KeyEvent); - method public boolean shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String); + method public deprecated boolean shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String); + method public boolean shouldOverrideUrlLoading(android.webkit.WebView, android.webkit.WebResourceRequest); field public static final int ERROR_AUTHENTICATION = -4; // 0xfffffffc field public static final int ERROR_BAD_URL = -12; // 0xfffffff4 field public static final int ERROR_CONNECT = -6; // 0xfffffffa diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index 3f8e3111cd37..2960cdc9084e 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -283,7 +283,7 @@ public final class Pm { } else if (args.length == 2) { if (args[0].equalsIgnoreCase("-p")) { validCommand = true; - return displayPackageFilePath(args[1], UserHandle.USER_OWNER); + return displayPackageFilePath(args[1], UserHandle.USER_SYSTEM); } } return 1; @@ -767,7 +767,7 @@ public final class Pm { } private int runPath() { - int userId = UserHandle.USER_OWNER; + int userId = UserHandle.USER_SYSTEM; String option = nextOption(); if (option != null && option.equals("--user")) { String optionData = nextOptionData(); @@ -1650,7 +1650,7 @@ public final class Pm { } private int runClear() { - int userId = UserHandle.USER_OWNER; + int userId = UserHandle.USER_SYSTEM; String option = nextOption(); if (option != null && option.equals("--user")) { String optionData = nextOptionData(); @@ -1722,7 +1722,7 @@ public final class Pm { } private int runSetEnabledSetting(int state) { - int userId = UserHandle.USER_OWNER; + int userId = UserHandle.USER_SYSTEM; String option = nextOption(); if (option != null && option.equals("--user")) { String optionData = nextOptionData(); @@ -1771,7 +1771,7 @@ public final class Pm { } private int runSetHiddenSetting(boolean state) { - int userId = UserHandle.USER_OWNER; + int userId = UserHandle.USER_SYSTEM; String option = nextOption(); if (option != null && option.equals("--user")) { String optionData = nextOptionData(); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 6ae32d082928..fce1b2e7241a 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1200,6 +1200,12 @@ public class ActivityManager { public int id; /** + * The stack that currently contains this task. + * @hide + */ + public int stackId; + + /** * The component launched as the first activity in the task. This can * be considered the "application" of this task. */ @@ -1248,6 +1254,7 @@ public class ActivityManager { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); + dest.writeInt(stackId); ComponentName.writeToParcel(baseActivity, dest); ComponentName.writeToParcel(topActivity, dest); if (thumbnail != null) { @@ -1264,6 +1271,7 @@ public class ActivityManager { public void readFromParcel(Parcel source) { id = source.readInt(); + stackId = source.readInt(); baseActivity = ComponentName.readFromParcel(source); topActivity = ComponentName.readFromParcel(source); if (source.readInt() != 0) { diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 933f98d968dc..0266a377349b 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -16,6 +16,8 @@ package android.app; +import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; + import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; @@ -23,10 +25,12 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.IRemoteCallback; +import android.os.Parcelable; import android.os.RemoteException; import android.os.ResultReceiver; import android.util.Pair; import android.util.Slog; +import android.view.AppTransitionAnimationSpec; import android.view.View; import android.view.Window; @@ -127,6 +131,17 @@ public class ActivityOptions { public static final String KEY_ANIM_START_LISTENER = "android:activity.animStartListener"; /** + * Descriptions of app transition animations to be played during the activity launch. + */ + private static final String KEY_ANIM_SPECS = "android:activity.animSpecs"; + + /** + * Where the docked stack should be positioned. + * @hide + */ + private static final String KEY_DOCK_CREATE_MODE = "android:activity.dockCreateMode"; + + /** * For Activity transitions, the calling Activity's TransitionListener used to * notify the called Activity when the shared element and the exit transitions * complete. @@ -190,6 +205,8 @@ public class ActivityOptions { private int mResultCode; private int mExitCoordinatorIndex; private PendingIntent mUsageTimeReport; + private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; + private AppTransitionAnimationSpec mAnimSpecs[]; /** * Create an ActivityOptions specifying a custom animation to run when @@ -503,6 +520,18 @@ public class ActivityOptions { return opts; } + /** @hide */ + public static ActivityOptions makeThumbnailAspectScaleDownAnimation(View source, + AppTransitionAnimationSpec[] specs, Handler handler, + OnAnimationStartedListener listener) { + ActivityOptions opts = new ActivityOptions(); + opts.mPackageName = source.getContext().getPackageName(); + opts.mAnimationType = ANIM_THUMBNAIL_ASPECT_SCALE_DOWN; + opts.mAnimSpecs = specs; + opts.setOnAnimationStartedListener(handler, listener); + return opts; + } + /** * Create an ActivityOptions to transition between Activities using cross-Activity scene * animations. This method carries the position of one shared element to the started Activity. @@ -688,6 +717,14 @@ public class ActivityOptions { mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX); break; } + mDockCreateMode = opts.getInt(KEY_DOCK_CREATE_MODE, DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT); + if (opts.containsKey(KEY_ANIM_SPECS)) { + Parcelable[] specs = opts.getParcelableArray(KEY_ANIM_SPECS); + mAnimSpecs = new AppTransitionAnimationSpec[specs.length]; + for (int i = specs.length - 1; i >= 0; i--) { + mAnimSpecs[i] = (AppTransitionAnimationSpec) specs[i]; + } + } } /** @hide */ @@ -800,12 +837,23 @@ public class ActivityOptions { } /** @hide */ + public AppTransitionAnimationSpec[] getAnimSpecs() { return mAnimSpecs; } + + /** @hide */ public static void abort(Bundle options) { if (options != null) { (new ActivityOptions(options)).abort(); } } + /** @hide */ + public int getDockCreateMode() { return mDockCreateMode; } + + /** @hide */ + public void setDockCreateMode(int dockCreateMode) { + mDockCreateMode = dockCreateMode; + } + /** * Update the current values in this ActivityOptions from those supplied * in <var>otherOptions</var>. Any values @@ -880,6 +928,7 @@ public class ActivityOptions { mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex; break; } + mAnimSpecs = otherOptions.mAnimSpecs; } /** @@ -945,6 +994,10 @@ public class ActivityOptions { b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex); break; } + b.putInt(KEY_DOCK_CREATE_MODE, mDockCreateMode); + if (mAnimSpecs != null) { + b.putParcelableArray(KEY_ANIM_SPECS, mAnimSpecs); + } return b; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index db18722f9448..49edff411342 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -251,15 +251,26 @@ public class Notification implements Parcelable public RemoteViews tickerView; /** - * The view that will represent this notification in the expanded status bar. + * The view that will represent this notification in the notification list (which is pulled + * down from the status bar). + * + * As of N, this field is not used. The notification view is determined by the inputs to + * {@link Notification.Builder}; a custom RemoteViews can optionally be + * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}. */ + @Deprecated public RemoteViews contentView; /** * A large-format version of {@link #contentView}, giving the Notification an * opportunity to show more detail. The system UI may choose to show this * instead of the normal content view at its discretion. + * + * As of N, this field is not used. The expanded notification view is determined by the + * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be + * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}. */ + @Deprecated public RemoteViews bigContentView; @@ -268,7 +279,12 @@ public class Notification implements Parcelable * opportunity to add action buttons to contentView. At its discretion, the system UI may * choose to show this as a heads-up notification, which will pop up so the user can see * it without leaving their current activity. + * + * As of N, this field is not used. The heads-up notification view is determined by the + * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be + * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}. */ + @Deprecated public RemoteViews headsUpContentView; /** @@ -867,6 +883,11 @@ public class Notification implements Parcelable public static final String EXTRA_ORIGINATING_USERID = "android.originatingUserId"; /** + * @hide + */ + public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo"; + + /** * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification should not be * displayed in the heads up space. * @@ -1707,8 +1728,6 @@ public class Notification implements Parcelable extras.remove(Notification.EXTRA_LARGE_ICON_BIG); extras.remove(Notification.EXTRA_PICTURE); extras.remove(Notification.EXTRA_BIG_TEXT); - // Prevent light notifications from being rebuilt. - extras.remove(Builder.EXTRA_NEEDS_REBUILD); } } @@ -1901,21 +1920,13 @@ public class Notification implements Parcelable @Deprecated public void setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { - Notification.Builder builder = new Notification.Builder(context); - - // First, ensure that key pieces of information that may have been set directly - // are preserved - builder.setWhen(this.when); - builder.setSmallIcon(this.icon); - builder.setPriority(this.priority); - builder.setTicker(this.tickerText); - builder.setNumber(this.number); - builder.setColor(this.color); - builder.mFlags = this.flags; - builder.setSound(this.sound, this.audioStreamType); - builder.setDefaults(this.defaults); - builder.setVibrate(this.vibrate); - builder.setDeleteIntent(this.deleteIntent); + if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){ + Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.", + new Throwable()); + } + + // ensure that any information already set directly is preserved + final Notification.Builder builder = new Notification.Builder(context, this); // now apply the latestEventInfo fields if (contentTitle != null) { @@ -1925,7 +1936,8 @@ public class Notification implements Parcelable builder.setContentText(contentText); } builder.setContentIntent(contentIntent); - builder.buildInto(this); + + builder.build(); // callers expect this notification to be ready to use } @Override @@ -2080,15 +2092,6 @@ public class Notification implements Parcelable /** * @hide */ - public boolean isValid() { - // Would like to check for icon!=0 here, too, but NotificationManagerService accepts that - // for legacy reasons. - return contentView != null || extras.getBoolean(Builder.EXTRA_REBUILD_CONTENT_VIEW); - } - - /** - * @hide - */ public boolean isGroupSummary() { return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0; } @@ -2125,99 +2128,14 @@ public class Notification implements Parcelable private static final int MAX_ACTION_BUTTONS = 3; private static final float LARGE_TEXT_SCALE = 1.3f; - /** - * @hide - */ - public static final String EXTRA_NEEDS_REBUILD = "android.rebuild"; - - /** - * @hide - */ - public static final String EXTRA_REBUILD_LARGE_ICON = "android.rebuild.largeIcon"; - /** - * @hide - */ - public static final String EXTRA_REBUILD_CONTENT_VIEW = "android.rebuild.contentView"; - /** - * @hide - */ - public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT = - "android.rebuild.contentViewActionCount"; - /** - * @hide - */ - public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW - = "android.rebuild.bigView"; - /** - * @hide - */ - public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT - = "android.rebuild.bigViewActionCount"; - /** - * @hide - */ - public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW - = "android.rebuild.hudView"; - /** - * @hide - */ - public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT - = "android.rebuild.hudViewActionCount"; - - /** - * The ApplicationInfo of the package that created the notification, used to create - * a context to rebuild the notification via a Builder. - * @hide - */ - private static final String EXTRA_REBUILD_CONTEXT_APPLICATION_INFO = - "android.rebuild.applicationInfo"; - - // Whether to enable stripping (at post time) & rebuilding (at listener receive time) of - // memory intensive resources. - private static final boolean STRIP_AND_REBUILD = true; - private Context mContext; - - private long mWhen; - private Icon mSmallIcon, mLargeIcon; - private int mSmallIconLevel; - private int mNumber; - private CharSequence mContentTitle; - private CharSequence mContentText; - private CharSequence mContentInfo; - private CharSequence mSubText; - private PendingIntent mContentIntent; - private RemoteViews mContentView; - private PendingIntent mDeleteIntent; - private PendingIntent mFullScreenIntent; - private CharSequence mTickerText; - private RemoteViews mTickerView; - private Uri mSound; - private int mAudioStreamType; - private AudioAttributes mAudioAttributes; - private long[] mVibrate; - private int mLedArgb; - private int mLedOnMs; - private int mLedOffMs; - private int mDefaults; - private int mFlags; - private int mProgressMax; - private int mProgress; - private boolean mProgressIndeterminate; - private String mCategory; - private String mGroupKey; - private String mSortKey; - private Bundle mExtras; - private int mPriority; - private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS); - private boolean mUseChronometer; + private Notification mN; + private Bundle mUserExtras = new Bundle(); private Style mStyle; - private boolean mShowWhen = true; - private int mVisibility = VISIBILITY_PRIVATE; - private Notification mPublicVersion = null; - private final NotificationColorUtil mColorUtil; - private ArrayList<String> mPeople; - private int mColor = COLOR_DEFAULT; + private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS); + private ArrayList<String> mPersonList = new ArrayList<String>(); + private NotificationColorUtil mColorUtil; + private boolean mColorUtilInited = false; private List<Topic> mTopics = new ArrayList<>(); /** @@ -2226,25 +2144,6 @@ public class Notification implements Parcelable private int mOriginatingUserId; /** - * Contains extras related to rebuilding during the build phase. - */ - private Bundle mRebuildBundle = new Bundle(); - /** - * Contains the notification to rebuild when this Builder is in "rebuild" mode. - * Null otherwise. - */ - private Notification mRebuildNotification = null; - - /** - * Whether the build notification has three lines. This is used to make the top padding for - * both the contracted and expanded layout consistent. - * - * <p> - * This field is only valid during the build phase. - */ - private boolean mHasThreeLines; - - /** * Constructs a new Builder with the defaults: * @@ -2264,61 +2163,67 @@ public class Notification implements Parcelable * object. */ public Builder(Context context) { - /* - * Important compatibility note! - * Some apps out in the wild create a Notification.Builder in their Activity subclass - * constructor for later use. At this point Activities - themselves subclasses of - * ContextWrapper - do not have their inner Context populated yet. This means that - * any calls to Context methods from within this constructor can cause NPEs in existing - * apps. Any data populated from mContext should therefore be populated lazily to - * preserve compatibility. - */ + this(context, null); + } + + /** + * @hide + */ + public Builder(Context context, Notification toAdopt) { mContext = context; - // Set defaults to match the defaults of a Notification - mWhen = System.currentTimeMillis(); - mAudioStreamType = STREAM_DEFAULT; - mAudioAttributes = AUDIO_ATTRIBUTES_DEFAULT; - mPriority = PRIORITY_DEFAULT; - mPeople = new ArrayList<String>(); + if (toAdopt == null) { + mN = new Notification(); + mN.extras.putBoolean(EXTRA_SHOW_WHEN, true); + mN.priority = PRIORITY_DEFAULT; + mN.visibility = VISIBILITY_PRIVATE; + } else { + mN = toAdopt; + if (mN.actions != null) { + Collections.addAll(mActions, mN.actions); + } - mColorUtil = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP ? - NotificationColorUtil.getInstance(mContext) : null; - } + if (mN.extras.containsKey(EXTRA_PEOPLE)) { + Collections.addAll(mPersonList, mN.extras.getStringArray(EXTRA_PEOPLE)); + } - /** - * Creates a Builder for rebuilding the given Notification. - * <p> - * Call {@link #rebuild()} to retrieve the rebuilt version of 'n'. - */ - private Builder(Context context, Notification n) { - this(context); - mRebuildNotification = n; - restoreFromNotification(n); - - Style style = null; - Bundle extras = n.extras; - String templateClass = extras.getString(EXTRA_TEMPLATE); - if (!TextUtils.isEmpty(templateClass)) { - Class<? extends Style> styleClass = getNotificationStyleClass(templateClass); - if (styleClass == null) { - Log.d(TAG, "Unknown style class: " + styleClass); - return; + if (mN.getTopics() != null) { + Collections.addAll(mTopics, mN.getTopics()); } - try { - Constructor<? extends Style> constructor = styleClass.getConstructor(); - constructor.setAccessible(true); - style = constructor.newInstance(); - style.restoreFromExtras(extras); - } catch (Throwable t) { - Log.e(TAG, "Could not create Style", t); - return; + String templateClass = mN.extras.getString(EXTRA_TEMPLATE); + if (!TextUtils.isEmpty(templateClass)) { + final Class<? extends Style> styleClass + = getNotificationStyleClass(templateClass); + if (styleClass == null) { + Log.d(TAG, "Unknown style class: " + templateClass); + } else { + try { + final Constructor<? extends Style> ctor = styleClass.getConstructor(); + ctor.setAccessible(true); + final Style style = ctor.newInstance(); + style.restoreFromExtras(mN.extras); + + if (style != null) { + setStyle(style); + } + } catch (Throwable t) { + Log.e(TAG, "Could not create Style", t); + } + } } + } - if (style != null) { - setStyle(style); + } + + private NotificationColorUtil getColorUtil() { + if (!mColorUtilInited) { + mColorUtilInited = true; + if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) { + mColorUtil = NotificationColorUtil.getInstance(mContext); + } } + return mColorUtil; } /** @@ -2329,7 +2234,7 @@ public class Notification implements Parcelable * @see Notification#when */ public Builder setWhen(long when) { - mWhen = when; + mN.when = when; return this; } @@ -2338,7 +2243,7 @@ public class Notification implements Parcelable * in the content view. */ public Builder setShowWhen(boolean show) { - mShowWhen = show; + mN.extras.putBoolean(EXTRA_SHOW_WHEN, show); return this; } @@ -2354,7 +2259,7 @@ public class Notification implements Parcelable * @see Notification#when */ public Builder setUsesChronometer(boolean b) { - mUseChronometer = b; + mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b); return this; } @@ -2390,7 +2295,7 @@ public class Notification implements Parcelable * @see Notification#iconLevel */ public Builder setSmallIcon(@DrawableRes int icon, int level) { - mSmallIconLevel = level; + mN.iconLevel = level; return setSmallIcon(icon); } @@ -2403,7 +2308,10 @@ public class Notification implements Parcelable * @see Notification#icon */ public Builder setSmallIcon(Icon icon) { - mSmallIcon = icon; + mN.setSmallIcon(icon); + if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { + mN.icon = icon.getResId(); + } return this; } @@ -2411,7 +2319,7 @@ public class Notification implements Parcelable * Set the first line of text in the platform notification template. */ public Builder setContentTitle(CharSequence title) { - mContentTitle = safeCharSequence(title); + mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title)); return this; } @@ -2419,7 +2327,7 @@ public class Notification implements Parcelable * Set the second line of text in the platform notification template. */ public Builder setContentText(CharSequence text) { - mContentText = safeCharSequence(text); + mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text)); return this; } @@ -2429,7 +2337,7 @@ public class Notification implements Parcelable * same location in the standard template. */ public Builder setSubText(CharSequence text) { - mSubText = safeCharSequence(text); + mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text)); return this; } @@ -2439,7 +2347,7 @@ public class Notification implements Parcelable * font size for readability. */ public Builder setNumber(int number) { - mNumber = number; + mN.number = number; return this; } @@ -2450,7 +2358,7 @@ public class Notification implements Parcelable * right (to the right of a smallIcon if it has been placed there). */ public Builder setContentInfo(CharSequence info) { - mContentInfo = safeCharSequence(info); + mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info)); return this; } @@ -2460,19 +2368,52 @@ public class Notification implements Parcelable * The platform template will represent this using a {@link ProgressBar}. */ public Builder setProgress(int max, int progress, boolean indeterminate) { - mProgressMax = max; - mProgress = progress; - mProgressIndeterminate = indeterminate; + mN.extras.putInt(EXTRA_PROGRESS, progress); + mN.extras.putInt(EXTRA_PROGRESS_MAX, max); + mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate); return this; } /** * Supply a custom RemoteViews to use instead of the platform template. * - * @see Notification#contentView + * Use {@link #setCustomContentView(RemoteViews)} instead. */ + @Deprecated public Builder setContent(RemoteViews views) { - mContentView = views; + return setCustomContentView(views); + } + + /** + * Supply custom RemoteViews to use instead of the platform template. + * + * This will override the layout that would otherwise be constructed by this Builder + * object. + */ + public Builder setCustomContentView(RemoteViews contentView) { + mN.contentView = contentView; + return this; + } + + /** + * Supply custom RemoteViews to use instead of the platform template in the expanded form. + * + * This will override the expanded layout that would otherwise be constructed by this + * Builder object. + */ + public Builder setCustomBigContentView(RemoteViews contentView) { + mN.bigContentView = contentView; + return this; + } + + /** + * Supply custom RemoteViews to use instead of the platform template in the heads up dialog. + * + * This will override the heads-up layout that would otherwise be constructed by this + * Builder object. + */ + public Builder setCustomHeadsUpContentView(RemoteViews contentView) { + mN.headsUpContentView = contentView; return this; } @@ -2488,7 +2429,7 @@ public class Notification implements Parcelable * @see Notification#contentIntent Notification.contentIntent */ public Builder setContentIntent(PendingIntent intent) { - mContentIntent = intent; + mN.contentIntent = intent; return this; } @@ -2498,7 +2439,7 @@ public class Notification implements Parcelable * @see Notification#deleteIntent */ public Builder setDeleteIntent(PendingIntent intent) { - mDeleteIntent = intent; + mN.deleteIntent = intent; return this; } @@ -2523,7 +2464,7 @@ public class Notification implements Parcelable * @see Notification#fullScreenIntent */ public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { - mFullScreenIntent = intent; + mN.fullScreenIntent = intent; setFlag(FLAG_HIGH_PRIORITY, highPriority); return this; } @@ -2534,7 +2475,7 @@ public class Notification implements Parcelable * @see Notification#tickerText */ public Builder setTicker(CharSequence tickerText) { - mTickerText = safeCharSequence(tickerText); + mN.tickerText = safeCharSequence(tickerText); return this; } @@ -2544,8 +2485,8 @@ public class Notification implements Parcelable */ @Deprecated public Builder setTicker(CharSequence tickerText, RemoteViews views) { - mTickerText = safeCharSequence(tickerText); - mTickerView = views; // we'll save it for you anyway + setTicker(tickerText); + // views is ignored return this; } @@ -2568,7 +2509,8 @@ public class Notification implements Parcelable * badge atop the large icon). */ public Builder setLargeIcon(Icon icon) { - mLargeIcon = icon; + mN.mLargeIcon = icon; + mN.extras.putParcelable(EXTRA_LARGE_ICON, icon); return this; } @@ -2585,8 +2527,8 @@ public class Notification implements Parcelable * @see Notification#sound */ public Builder setSound(Uri sound) { - mSound = sound; - mAudioAttributes = AUDIO_ATTRIBUTES_DEFAULT; + mN.sound = sound; + mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; return this; } @@ -2603,8 +2545,8 @@ public class Notification implements Parcelable */ @Deprecated public Builder setSound(Uri sound, int streamType) { - mSound = sound; - mAudioStreamType = streamType; + mN.sound = sound; + mN.audioStreamType = streamType; return this; } @@ -2619,8 +2561,8 @@ public class Notification implements Parcelable * @see Notification#sound */ public Builder setSound(Uri sound, AudioAttributes audioAttributes) { - mSound = sound; - mAudioAttributes = audioAttributes; + mN.sound = sound; + mN.audioAttributes = audioAttributes; return this; } @@ -2637,7 +2579,7 @@ public class Notification implements Parcelable * @see Notification#vibrate */ public Builder setVibrate(long[] pattern) { - mVibrate = pattern; + mN.vibrate = pattern; return this; } @@ -2654,9 +2596,9 @@ public class Notification implements Parcelable * @see Notification#ledOffMS */ public Builder setLights(@ColorInt int argb, int onMs, int offMs) { - mLedArgb = argb; - mLedOnMs = onMs; - mLedOffMs = offMs; + mN.ledARGB = argb; + mN.ledOnMS = onMs; + mN.ledOffMS = offMs; return this; } @@ -2724,7 +2666,7 @@ public class Notification implements Parcelable * For all default values, use {@link #DEFAULT_ALL}. */ public Builder setDefaults(int defaults) { - mDefaults = defaults; + mN.defaults = defaults; return this; } @@ -2734,7 +2676,7 @@ public class Notification implements Parcelable * @see Notification#priority */ public Builder setPriority(@Priority int pri) { - mPriority = pri; + mN.priority = pri; return this; } @@ -2744,7 +2686,7 @@ public class Notification implements Parcelable * @see Notification#category */ public Builder setCategory(String category) { - mCategory = category; + mN.category = category; return this; } @@ -2771,7 +2713,7 @@ public class Notification implements Parcelable * @see Notification#EXTRA_PEOPLE */ public Builder addPerson(String uri) { - mPeople.add(uri); + mPersonList.add(uri); return this; } @@ -2787,7 +2729,7 @@ public class Notification implements Parcelable * @return this object for method chaining */ public Builder setGroup(String groupKey) { - mGroupKey = groupKey; + mN.mGroupKey = groupKey; return this; } @@ -2816,7 +2758,7 @@ public class Notification implements Parcelable * @see String#compareTo(String) */ public Builder setSortKey(String sortKey) { - mSortKey = sortKey; + mN.mSortKey = sortKey; return this; } @@ -2829,11 +2771,7 @@ public class Notification implements Parcelable */ public Builder addExtras(Bundle extras) { if (extras != null) { - if (mExtras == null) { - mExtras = new Bundle(extras); - } else { - mExtras.putAll(extras); - } + mUserExtras.putAll(extras); } return this; } @@ -2851,7 +2789,9 @@ public class Notification implements Parcelable * @see Notification#extras */ public Builder setExtras(Bundle extras) { - mExtras = extras; + if (extras != null) { + mUserExtras = extras; + } return this; } @@ -2866,10 +2806,13 @@ public class Notification implements Parcelable * @see Notification#extras */ public Bundle getExtras() { - if (mExtras == null) { - mExtras = new Bundle(); - } - return mExtras; + return mUserExtras; + } + + private Bundle getAllExtras() { + final Bundle saveExtras = (Bundle) mUserExtras.clone(); + saveExtras.putAll(mN.extras); + return saveExtras; } /** @@ -2918,6 +2861,21 @@ public class Notification implements Parcelable } /** + * Alter the complete list of actions attached to this notification. + * @see #addAction(Action). + * + * @param actions + * @return + */ + public Builder setActions(Action... actions) { + mActions.clear(); + for (int i = 0; i < actions.length; i++) { + mActions.add(actions[i]); + } + return this; + } + + /** * Add a rich notification style to be applied at build time. * * @param style Object responsible for modifying the notification style. @@ -2927,6 +2885,9 @@ public class Notification implements Parcelable mStyle = style; if (mStyle != null) { mStyle.setBuilder(this); + mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName()); + } else { + mN.extras.remove(EXTRA_TEMPLATE); } } return this; @@ -2941,7 +2902,7 @@ public class Notification implements Parcelable * @return The same Builder. */ public Builder setVisibility(int visibility) { - mVisibility = visibility; + mN.visibility = visibility; return this; } @@ -2952,7 +2913,12 @@ public class Notification implements Parcelable * @return The same Builder. */ public Builder setPublicVersion(Notification n) { - mPublicVersion = n; + if (n != null) { + mN.publicVersion = new Notification(); + n.cloneInto(mN.publicVersion, /*heavy=*/ true); + } else { + mN.publicVersion = null; + } return this; } @@ -2970,9 +2936,9 @@ public class Notification implements Parcelable */ public void setFlag(int mask, boolean value) { if (value) { - mFlags |= mask; + mN.flags |= mask; } else { - mFlags &= ~mask; + mN.flags &= ~mask; } } @@ -2984,7 +2950,7 @@ public class Notification implements Parcelable * @return The same Builder. */ public Builder setColor(@ColorInt int argb) { - mColor = argb; + mN.color = argb; return this; } @@ -3075,7 +3041,6 @@ public class Notification implements Parcelable contentView.setViewVisibility(R.id.overflow_divider, View.GONE); contentView.setViewVisibility(R.id.progress, View.GONE); contentView.setViewVisibility(R.id.chronometer, View.GONE); - contentView.setViewVisibility(R.id.time, View.GONE); } private RemoteViews applyStandardTemplate(int resId) { @@ -3093,39 +3058,43 @@ public class Notification implements Parcelable boolean showLine3 = false; boolean showLine2 = false; boolean contentTextInLine2 = false; + final Bundle ex = mN.extras; - if (mLargeIcon != null) { - contentView.setImageViewIcon(R.id.icon, mLargeIcon); - processLargeLegacyIcon(mLargeIcon, contentView); - contentView.setImageViewIcon(R.id.right_icon, mSmallIcon); + if (mN.mLargeIcon != null) { + contentView.setImageViewIcon(R.id.icon, mN.mLargeIcon); + processLargeLegacyIcon(mN.mLargeIcon, contentView); + contentView.setImageViewIcon(R.id.right_icon, mN.mSmallIcon); contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); - processSmallRightIcon(mSmallIcon, contentView); + processSmallRightIcon(mN.mSmallIcon, contentView); } else { // small icon at left - contentView.setImageViewIcon(R.id.icon, mSmallIcon); + contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); contentView.setViewVisibility(R.id.icon, View.VISIBLE); - processSmallIconAsLarge(mSmallIcon, contentView); + processSmallIconAsLarge(mN.mSmallIcon, contentView); } - if (mContentTitle != null) { - contentView.setTextViewText(R.id.title, processLegacyText(mContentTitle)); + if (ex.getCharSequence(EXTRA_TITLE) != null) { + contentView.setTextViewText(R.id.title, + processLegacyText(ex.getCharSequence(EXTRA_TITLE))); } - if (mContentText != null) { - contentView.setTextViewText(R.id.text, processLegacyText(mContentText)); + if (ex.getCharSequence(EXTRA_TEXT) != null) { + contentView.setTextViewText(R.id.text, + processLegacyText(ex.getCharSequence(EXTRA_TEXT))); showLine3 = true; } - if (mContentInfo != null) { - contentView.setTextViewText(R.id.info, processLegacyText(mContentInfo)); + if (ex.getCharSequence(EXTRA_INFO_TEXT) != null) { + contentView.setTextViewText(R.id.info, + processLegacyText(ex.getCharSequence(EXTRA_INFO_TEXT))); contentView.setViewVisibility(R.id.info, View.VISIBLE); showLine3 = true; - } else if (mNumber > 0) { + } else if (mN.number > 0) { final int tooBig = mContext.getResources().getInteger( R.integer.status_bar_notification_info_maxnum); - if (mNumber > tooBig) { + if (mN.number > tooBig) { contentView.setTextViewText(R.id.info, processLegacyText( mContext.getResources().getString( R.string.status_bar_notification_info_overflow))); } else { NumberFormat f = NumberFormat.getIntegerInstance(); - contentView.setTextViewText(R.id.info, processLegacyText(f.format(mNumber))); + contentView.setTextViewText(R.id.info, processLegacyText(f.format(mN.number))); } contentView.setViewVisibility(R.id.info, View.VISIBLE); showLine3 = true; @@ -3134,10 +3103,12 @@ public class Notification implements Parcelable } // Need to show three lines? - if (mSubText != null) { - contentView.setTextViewText(R.id.text, processLegacyText(mSubText)); - if (mContentText != null) { - contentView.setTextViewText(R.id.text2, processLegacyText(mContentText)); + if (ex.getCharSequence(EXTRA_SUB_TEXT) != null) { + contentView.setTextViewText(R.id.text, + processLegacyText(ex.getCharSequence(EXTRA_SUB_TEXT))); + if (ex.getCharSequence(EXTRA_TEXT) != null) { + contentView.setTextViewText(R.id.text2, + processLegacyText(ex.getCharSequence(EXTRA_TEXT))); contentView.setViewVisibility(R.id.text2, View.VISIBLE); showLine2 = true; contentTextInLine2 = true; @@ -3146,15 +3117,18 @@ public class Notification implements Parcelable } } else { contentView.setViewVisibility(R.id.text2, View.GONE); - if (hasProgress && (mProgressMax != 0 || mProgressIndeterminate)) { + final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0); + final int progress = ex.getInt(EXTRA_PROGRESS, 0); + final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE); + if (hasProgress && (max != 0 || ind)) { contentView.setViewVisibility(R.id.progress, View.VISIBLE); contentView.setProgressBar( - R.id.progress, mProgressMax, mProgress, mProgressIndeterminate); + R.id.progress, max, progress, ind); contentView.setProgressBackgroundTintList( R.id.progress, ColorStateList.valueOf(mContext.getColor( R.color.notification_progress_background_color))); - if (mColor != COLOR_DEFAULT) { - ColorStateList colorStateList = ColorStateList.valueOf(mColor); + if (mN.color != COLOR_DEFAULT) { + ColorStateList colorStateList = ColorStateList.valueOf(mN.color); contentView.setProgressTintList(R.id.progress, colorStateList); contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList); } @@ -3170,20 +3144,21 @@ public class Notification implements Parcelable } if (showsTimeOrChronometer()) { - if (mUseChronometer) { + if (ex.getBoolean(EXTRA_SHOW_CHRONOMETER)) { contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); contentView.setLong(R.id.chronometer, "setBase", - mWhen + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); + mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); contentView.setBoolean(R.id.chronometer, "setStarted", true); } else { contentView.setViewVisibility(R.id.time, View.VISIBLE); - contentView.setLong(R.id.time, "setTime", mWhen); + contentView.setLong(R.id.time, "setTime", mN.when); } } // Adjust padding depending on line count and font size. - contentView.setViewPadding(R.id.line1, 0, calculateTopPadding(mContext, - mHasThreeLines, mContext.getResources().getConfiguration().fontScale), + contentView.setViewPadding(R.id.line1, 0, + calculateTopPadding(mContext, hasThreeLines(), + mContext.getResources().getConfiguration().fontScale), 0, 0); // We want to add badge to first line of text. @@ -3196,7 +3171,8 @@ public class Notification implements Parcelable // Note getStandardView may hide line 3 again. contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE); - contentView.setViewVisibility(R.id.overflow_divider, showLine3 ? View.VISIBLE : View.GONE); + contentView.setViewVisibility(R.id.overflow_divider, + showLine3 ? View.VISIBLE : View.GONE); return contentView; } @@ -3205,7 +3181,7 @@ public class Notification implements Parcelable * otherwise */ private boolean showsTimeOrChronometer() { - return mWhen != 0 && mShowWhen; + return mN.when != 0 && mN.extras.getBoolean(EXTRA_SHOW_WHEN); } /** @@ -3216,15 +3192,19 @@ public class Notification implements Parcelable * is going to have one or two lines */ private boolean hasThreeLines() { - boolean contentTextInLine2 = mSubText != null && mContentText != null; + final CharSequence subText = mN.extras.getCharSequence(EXTRA_SUB_TEXT); + final CharSequence text = mN.extras.getCharSequence(EXTRA_TEXT); + boolean contentTextInLine2 = subText != null && text != null; boolean hasProgress = mStyle == null || mStyle.hasProgress(); // If we have content text in line 2, badge goes into line 2, or line 3 otherwise boolean badgeInLine3 = getProfileBadgeDrawable() != null && !contentTextInLine2; - boolean hasLine3 = mContentText != null || mContentInfo != null || mNumber > 0 - || badgeInLine3; - boolean hasLine2 = (mSubText != null && mContentText != null) || - (hasProgress && mSubText == null - && (mProgressMax != 0 || mProgressIndeterminate)); + boolean hasLine3 = text != null || mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null + || mN.number > 0 || badgeInLine3; + final Bundle ex = mN.extras; + final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0); + final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE); + boolean hasLine2 = (subText != null && text != null) || + (hasProgress && subText == null && (max != 0 || ind)); return hasLine2 && hasLine3; } @@ -3271,29 +3251,48 @@ public class Notification implements Parcelable return big; } - private RemoteViews makeContentView() { - if (mContentView != null) { - return mContentView; - } else { - return applyStandardTemplate(getBaseLayoutResource()); - } - } - - private RemoteViews makeTickerView() { - if (mTickerView != null) { - return mTickerView; + /** + * Construct a RemoteViews for the final 1U notification layout. In order: + * 1. Custom contentView from the caller + * 2. Style's proposed content view + * 3. Standard template view + */ + public RemoteViews makeContentView() { + if (mN.contentView != null) { + return mN.contentView; + } else if (mStyle != null) { + final RemoteViews styleView = mStyle.makeContentView(); + if (styleView != null) { + return styleView; + } } - return null; // tickers are not created by default anymore + return applyStandardTemplate(getBaseLayoutResource()); } - private RemoteViews makeBigContentView() { - if (mActions.size() == 0) return null; + /** + * Construct a RemoteViews for the final big notification layout. + */ + public RemoteViews makeBigContentView() { + if (mStyle != null) { + final RemoteViews styleView = mStyle.makeBigContentView(); + if (styleView != null) { + return styleView; + } + } else if (mActions.size() == 0) return null; return applyStandardTemplateWithActions(getBigBaseLayoutResource()); } - private RemoteViews makeHeadsUpContentView() { - if (mActions.size() == 0) return null; + /** + * Construct a RemoteViews for the final heads-up notification layout. + */ + public RemoteViews makeHeadsUpContentView() { + if (mStyle != null) { + final RemoteViews styleView = mStyle.makeHeadsUpContentView(); + if (styleView != null) { + return styleView; + } + } else if (mActions.size() == 0) return null; return applyStandardTemplateWithActions(getBigBaseLayoutResource()); } @@ -3320,11 +3319,11 @@ public class Notification implements Parcelable * doesn't create material notifications by itself) app. */ private boolean isLegacy() { - return mColorUtil != null; + return getColorUtil() != null; } private void processLegacyAction(Action action, RemoteViews button) { - if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, action.getIcon())) { + if (!isLegacy() || getColorUtil().isGrayscaleIcon(mContext, action.getIcon())) { button.setTextViewCompoundDrawablesRelativeColorFilter(R.id.action0, 0, mContext.getColor(R.color.notification_action_color_filter), PorterDuff.Mode.MULTIPLY); @@ -3333,7 +3332,7 @@ public class Notification implements Parcelable private CharSequence processLegacyText(CharSequence charSequence) { if (isLegacy()) { - return mColorUtil.invertCharSequenceColors(charSequence); + return getColorUtil().invertCharSequenceColors(charSequence); } else { return charSequence; } @@ -3349,7 +3348,7 @@ public class Notification implements Parcelable PorterDuff.Mode.SRC_ATOP, -1); applyLargeIconBackground(contentView); } else { - if (mColorUtil.isGrayscaleIcon(mContext, largeIcon)) { + if (getColorUtil().isGrayscaleIcon(mContext, largeIcon)) { applyLargeIconBackground(contentView); } } @@ -3362,7 +3361,7 @@ public class Notification implements Parcelable // TODO: also check bounds, transparency, that sort of thing. private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView) { if (largeIcon != null && isLegacy() - && mColorUtil.isGrayscaleIcon(mContext, largeIcon)) { + && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) { applyLargeIconBackground(contentView); } else { removeLargeIconBackground(contentView); @@ -3404,7 +3403,7 @@ public class Notification implements Parcelable } final boolean gray = isLegacy() && smallIcon.getType() == Icon.TYPE_RESOURCE - && mColorUtil.isGrayscaleIcon(mContext, smallIcon.getResId()); + && getColorUtil().isGrayscaleIcon(mContext, smallIcon.getResId()); if (!isLegacy() || gray) { contentView.setInt(R.id.right_icon, "setBackgroundResource", @@ -3421,17 +3420,17 @@ public class Notification implements Parcelable } private int sanitizeColor() { - if (mColor != COLOR_DEFAULT) { - mColor |= 0xFF000000; // no alpha for custom colors + if (mN.color != COLOR_DEFAULT) { + mN.color |= 0xFF000000; // no alpha for custom colors } - return mColor; + return mN.color; } private int resolveColor() { - if (mColor == COLOR_DEFAULT) { + if (mN.color == COLOR_DEFAULT) { return mContext.getColor(R.color.notification_icon_bg_color); } - return mColor; + return mN.color; } /** @@ -3439,165 +3438,25 @@ public class Notification implements Parcelable * @hide */ public Notification buildUnstyled() { - Notification n = new Notification(); - n.when = mWhen; - n.mSmallIcon = mSmallIcon; - if (mSmallIcon != null && mSmallIcon.getType() == Icon.TYPE_RESOURCE) { - n.icon = mSmallIcon.getResId(); - } - n.iconLevel = mSmallIconLevel; - n.number = mNumber; - - n.color = sanitizeColor(); - - setBuilderContentView(n, makeContentView()); - n.contentIntent = mContentIntent; - n.deleteIntent = mDeleteIntent; - n.fullScreenIntent = mFullScreenIntent; - n.tickerText = mTickerText; - n.tickerView = makeTickerView(); - n.mLargeIcon = mLargeIcon; - if (mLargeIcon != null && mLargeIcon.getType() == Icon.TYPE_BITMAP) { - n.largeIcon = mLargeIcon.getBitmap(); - } - n.sound = mSound; - n.audioStreamType = mAudioStreamType; - n.audioAttributes = mAudioAttributes; - n.vibrate = mVibrate; - n.ledARGB = mLedArgb; - n.ledOnMS = mLedOnMs; - n.ledOffMS = mLedOffMs; - n.defaults = mDefaults; - n.flags = mFlags; - setBuilderBigContentView(n, makeBigContentView()); - setBuilderHeadsUpContentView(n, makeHeadsUpContentView()); - if (mLedOnMs != 0 || mLedOffMs != 0) { - n.flags |= FLAG_SHOW_LIGHTS; - } - if ((mDefaults & DEFAULT_LIGHTS) != 0) { - n.flags |= FLAG_SHOW_LIGHTS; - } - n.category = mCategory; - n.mGroupKey = mGroupKey; - n.mSortKey = mSortKey; - n.priority = mPriority; if (mActions.size() > 0) { - n.actions = new Action[mActions.size()]; - mActions.toArray(n.actions); + mN.actions = new Action[mActions.size()]; + mActions.toArray(mN.actions); } - n.visibility = mVisibility; - - if (mPublicVersion != null) { - n.publicVersion = new Notification(); - mPublicVersion.cloneInto(n.publicVersion, true); + if (!mPersonList.isEmpty()) { + mN.extras.putStringArray(EXTRA_PEOPLE, + mPersonList.toArray(new String[mPersonList.size()])); } if (mTopics.size() > 0) { - n.topics = new Topic[mTopics.size()]; - mTopics.toArray(n.topics); - } - // Note: If you're adding new fields, also update restoreFromNotitification(). - return n; - } - - /** - * Capture, in the provided bundle, semantic information used in the construction of - * this Notification object. - * @hide - */ - public void populateExtras(Bundle extras) { - // Store original information used in the construction of this object - extras.putInt(EXTRA_ORIGINATING_USERID, mOriginatingUserId); - extras.putParcelable(EXTRA_REBUILD_CONTEXT_APPLICATION_INFO, - mContext.getApplicationInfo()); - extras.putCharSequence(EXTRA_TITLE, mContentTitle); - extras.putCharSequence(EXTRA_TEXT, mContentText); - extras.putCharSequence(EXTRA_SUB_TEXT, mSubText); - extras.putCharSequence(EXTRA_INFO_TEXT, mContentInfo); - extras.putParcelable(EXTRA_SMALL_ICON, mSmallIcon); - extras.putInt(EXTRA_PROGRESS, mProgress); - extras.putInt(EXTRA_PROGRESS_MAX, mProgressMax); - extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mProgressIndeterminate); - extras.putBoolean(EXTRA_SHOW_CHRONOMETER, mUseChronometer); - extras.putBoolean(EXTRA_SHOW_WHEN, mShowWhen); - if (mLargeIcon != null) { - extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); - } - if (!mPeople.isEmpty()) { - extras.putStringArray(EXTRA_PEOPLE, mPeople.toArray(new String[mPeople.size()])); + mN.topics = new Topic[mTopics.size()]; + mTopics.toArray(mN.topics); } - // NOTE: If you're adding new extras also update restoreFromNotification(). + return mN; } - - /** - * @hide - */ - public static void stripForDelivery(Notification n) { - if (!STRIP_AND_REBUILD) { - return; - } - - String templateClass = n.extras.getString(EXTRA_TEMPLATE); - // Only strip views for known Styles because we won't know how to - // re-create them otherwise. - boolean stripViews = TextUtils.isEmpty(templateClass) || - getNotificationStyleClass(templateClass) != null; - - boolean isStripped = false; - - if (n.largeIcon != null && n.extras.containsKey(EXTRA_LARGE_ICON)) { - // TODO: Would like to check for equality here, but if the notification - // has been cloned, we can't. - n.largeIcon = null; - n.extras.putBoolean(EXTRA_REBUILD_LARGE_ICON, true); - isStripped = true; - } - // Get rid of unmodified BuilderRemoteViews. - - if (stripViews && - n.contentView instanceof BuilderRemoteViews && - n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) == - n.contentView.getSequenceNumber()) { - n.contentView = null; - n.extras.putBoolean(EXTRA_REBUILD_CONTENT_VIEW, true); - n.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT); - isStripped = true; - } - if (stripViews && - n.bigContentView instanceof BuilderRemoteViews && - n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) == - n.bigContentView.getSequenceNumber()) { - n.bigContentView = null; - n.extras.putBoolean(EXTRA_REBUILD_BIG_CONTENT_VIEW, true); - n.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT); - isStripped = true; - } - if (stripViews && - n.headsUpContentView instanceof BuilderRemoteViews && - n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) == - n.headsUpContentView.getSequenceNumber()) { - n.headsUpContentView = null; - n.extras.putBoolean(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW, true); - n.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT); - isStripped = true; - } - - if (isStripped) { - n.extras.putBoolean(EXTRA_NEEDS_REBUILD, true); - } - } - - /** - * @hide - */ - public static Notification rebuild(Context context, Notification n) { - Bundle extras = n.extras; - if (!extras.getBoolean(EXTRA_NEEDS_REBUILD)) return n; - extras.remove(EXTRA_NEEDS_REBUILD); - + public static Notification.Builder recoverBuilder(Context context, Notification n) { // Re-create notification context so we can access app resources. - ApplicationInfo applicationInfo = extras.getParcelable( - EXTRA_REBUILD_CONTEXT_APPLICATION_INFO); + ApplicationInfo applicationInfo = n.extras.getParcelable( + EXTRA_BUILDER_APPLICATION_INFO); Context builderContext; try { builderContext = context.createApplicationContext(applicationInfo, @@ -3607,58 +3466,7 @@ public class Notification implements Parcelable builderContext = context; // try with our context } - Builder b = new Builder(builderContext, n); - return b.rebuild(); - } - - /** - * Rebuilds the notification passed in to the rebuild-constructor - * {@link #Builder(Context, Notification)}. - * - * <p> - * Throws IllegalStateException when invoked on a Builder that isn't in rebuild mode. - * - * @hide - */ - private Notification rebuild() { - if (mRebuildNotification == null) { - throw new IllegalStateException("rebuild() only valid when in 'rebuild' mode."); - } - mHasThreeLines = hasThreeLines(); - - Bundle extras = mRebuildNotification.extras; - - if (extras.getBoolean(EXTRA_REBUILD_LARGE_ICON)) { - mRebuildNotification.largeIcon = extras.getParcelable(EXTRA_LARGE_ICON); - } - extras.remove(EXTRA_REBUILD_LARGE_ICON); - - if (extras.getBoolean(EXTRA_REBUILD_CONTENT_VIEW)) { - setBuilderContentView(mRebuildNotification, makeContentView()); - if (mStyle != null) { - mStyle.populateContentView(mRebuildNotification); - } - } - extras.remove(EXTRA_REBUILD_CONTENT_VIEW); - - if (extras.getBoolean(EXTRA_REBUILD_BIG_CONTENT_VIEW)) { - setBuilderBigContentView(mRebuildNotification, makeBigContentView()); - if (mStyle != null) { - mStyle.populateBigContentView(mRebuildNotification); - } - } - extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW); - - if (extras.getBoolean(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW)) { - setBuilderHeadsUpContentView(mRebuildNotification, makeHeadsUpContentView()); - if (mStyle != null) { - mStyle.populateHeadsUpContentView(mRebuildNotification); - } - } - extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW); - - mHasThreeLines = false; - return mRebuildNotification; + return new Builder(builderContext, n); } private static Class<? extends Style> getNotificationStyleClass(String templateClass) { @@ -3674,91 +3482,15 @@ public class Notification implements Parcelable private void setBuilderContentView(Notification n, RemoteViews contentView) { n.contentView = contentView; - if (contentView instanceof BuilderRemoteViews) { - mRebuildBundle.putInt(Builder.EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, - contentView.getSequenceNumber()); - } } private void setBuilderBigContentView(Notification n, RemoteViews bigContentView) { n.bigContentView = bigContentView; - if (bigContentView instanceof BuilderRemoteViews) { - mRebuildBundle.putInt(Builder.EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, - bigContentView.getSequenceNumber()); - } } private void setBuilderHeadsUpContentView(Notification n, RemoteViews headsUpContentView) { n.headsUpContentView = headsUpContentView; - if (headsUpContentView instanceof BuilderRemoteViews) { - mRebuildBundle.putInt(Builder.EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, - headsUpContentView.getSequenceNumber()); - } - } - - private void restoreFromNotification(Notification n) { - - // Notification fields. - mWhen = n.when; - mSmallIcon = n.mSmallIcon; - mSmallIconLevel = n.iconLevel; - mNumber = n.number; - - mColor = n.color; - - mContentView = n.contentView; - mDeleteIntent = n.deleteIntent; - mFullScreenIntent = n.fullScreenIntent; - mTickerText = n.tickerText; - mTickerView = n.tickerView; - mLargeIcon = n.mLargeIcon; - mSound = n.sound; - mAudioStreamType = n.audioStreamType; - mAudioAttributes = n.audioAttributes; - - mVibrate = n.vibrate; - mLedArgb = n.ledARGB; - mLedOnMs = n.ledOnMS; - mLedOffMs = n.ledOffMS; - mDefaults = n.defaults; - mFlags = n.flags; - - mCategory = n.category; - mGroupKey = n.mGroupKey; - mSortKey = n.mSortKey; - mPriority = n.priority; - mActions.clear(); - if (n.actions != null) { - Collections.addAll(mActions, n.actions); - } - mVisibility = n.visibility; - - mPublicVersion = n.publicVersion; - - if (n.topics != null) { - Collections.addAll(mTopics, n.topics); - } - - // Extras. - Bundle extras = n.extras; - mOriginatingUserId = extras.getInt(EXTRA_ORIGINATING_USERID); - mContentTitle = extras.getCharSequence(EXTRA_TITLE); - mContentText = extras.getCharSequence(EXTRA_TEXT); - mSubText = extras.getCharSequence(EXTRA_SUB_TEXT); - mContentInfo = extras.getCharSequence(EXTRA_INFO_TEXT); - mProgress = extras.getInt(EXTRA_PROGRESS); - mProgressMax = extras.getInt(EXTRA_PROGRESS_MAX); - mProgressIndeterminate = extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE); - mUseChronometer = extras.getBoolean(EXTRA_SHOW_CHRONOMETER); - mShowWhen = extras.getBoolean(EXTRA_SHOW_WHEN); - if (extras.containsKey(EXTRA_LARGE_ICON)) { - mLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON); - } - if (extras.containsKey(EXTRA_PEOPLE)) { - mPeople.clear(); - Collections.addAll(mPeople, extras.getStringArray(EXTRA_PEOPLE)); - } } /** @@ -3774,38 +3506,23 @@ public class Notification implements Parcelable * object. */ public Notification build() { - if (mSmallIcon != null) { - mSmallIcon.convertToAshmem(); - } - if (mLargeIcon != null) { - mLargeIcon.convertToAshmem(); - } - mOriginatingUserId = mContext.getUserId(); - mHasThreeLines = hasThreeLines(); - - Notification n = buildUnstyled(); - - if (mStyle != null) { - mStyle.purgeResources(); - n = mStyle.buildStyled(n); + // first, add any extras from the calling code + if (mUserExtras != null) { + mN.extras = getAllExtras(); } - if (mExtras != null) { - n.extras.putAll(mExtras); - } + // lazy stuff from mContext; see comment in Builder(Context, Notification) + mN.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, mContext.getApplicationInfo()); + mOriginatingUserId = mContext.getUserId(); + mN.extras.putInt(EXTRA_ORIGINATING_USERID, mOriginatingUserId); - if (mRebuildBundle.size() > 0) { - n.extras.putAll(mRebuildBundle); - mRebuildBundle.clear(); - } + buildUnstyled(); - populateExtras(n.extras); if (mStyle != null) { - mStyle.addExtras(n.extras); + mStyle.buildStyled(mN); } - mHasThreeLines = false; - return n; + return mN; } /** @@ -3901,14 +3618,15 @@ public class Notification implements Parcelable checkBuilder(); // Nasty. - CharSequence oldBuilderContentTitle = mBuilder.mContentTitle; + CharSequence oldBuilderContentTitle = + mBuilder.getAllExtras().getCharSequence(EXTRA_TITLE); if (mBigContentTitle != null) { mBuilder.setContentTitle(mBigContentTitle); } RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId); - mBuilder.mContentTitle = oldBuilderContentTitle; + mBuilder.getAllExtras().putCharSequence(EXTRA_TITLE, oldBuilderContentTitle); if (mBigContentTitle != null && mBigContentTitle.equals("")) { contentView.setViewVisibility(R.id.line1, View.GONE); @@ -3919,7 +3637,7 @@ public class Notification implements Parcelable // The last line defaults to the subtext, but can be replaced by mSummaryText final CharSequence overflowText = mSummaryTextSet ? mSummaryText - : mBuilder.mSubText; + : mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT); if (overflowText != null) { contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(overflowText)); contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE); @@ -3935,6 +3653,31 @@ public class Notification implements Parcelable } /** + * Construct a Style-specific RemoteViews for the final 1U notification layout. + * The default implementation has nothing additional to add. + * @hide + */ + public RemoteViews makeContentView() { + return null; + } + + /** + * Construct a Style-specific RemoteViews for the final big notification layout. + * @hide + */ + public RemoteViews makeBigContentView() { + return null; + } + + /** + * Construct a Style-specific RemoteViews for the final HUN layout. + * @hide + */ + public RemoteViews makeHeadsUpContentView() { + return null; + } + + /** * Changes the padding of the first line such that the big and small content view have the * same top padding. * @@ -3942,12 +3685,13 @@ public class Notification implements Parcelable */ protected void applyTopPadding(RemoteViews contentView) { int topPadding = Builder.calculateTopPadding(mBuilder.mContext, - mBuilder.mHasThreeLines, + mBuilder.hasThreeLines(), mBuilder.mContext.getResources().getConfiguration().fontScale); contentView.setViewPadding(R.id.line1, 0, topPadding, 0, 0); } /** + * Apply any style-specific extras to this notification before shipping it out. * @hide */ public void addExtras(Bundle extras) { @@ -3961,6 +3705,7 @@ public class Notification implements Parcelable } /** + * Reconstruct the internal state of this Style object from extras. * @hide */ protected void restoreFromExtras(Bundle extras) { @@ -3978,10 +3723,7 @@ public class Notification implements Parcelable * @hide */ public Notification buildStyled(Notification wip) { - populateTickerView(wip); - populateContentView(wip); - populateBigContentView(wip); - populateHeadsUpContentView(wip); + addExtras(wip.extras); return wip; } @@ -3990,26 +3732,6 @@ public class Notification implements Parcelable */ public void purgeResources() {} - // The following methods are split out so we can re-create notification partially. - /** - * @hide - */ - protected void populateTickerView(Notification wip) {} - /** - * @hide - */ - protected void populateContentView(Notification wip) {} - - /** - * @hide - */ - protected void populateBigContentView(Notification wip) {} - - /** - * @hide - */ - protected void populateHeadsUpContentView(Notification wip) {} - /** * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is * attached to. @@ -4115,29 +3837,33 @@ public class Notification implements Parcelable } } - private RemoteViews makeBigContentView() { - // Replace mLargeIcon with mBigLargeIcon if mBigLargeIconSet + /** + * @hide + */ + public RemoteViews makeBigContentView() { + // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet // This covers the following cases: // 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides - // mLargeIcon - // 2. !mBigLargeIconSet -> mLargeIcon applies + // mN.mLargeIcon + // 2. !mBigLargeIconSet -> mN.mLargeIcon applies Icon oldLargeIcon = null; if (mBigLargeIconSet) { - oldLargeIcon = mBuilder.mLargeIcon; - mBuilder.mLargeIcon = mBigLargeIcon; + oldLargeIcon = mBuilder.mN.mLargeIcon; + mBuilder.mN.mLargeIcon = mBigLargeIcon; } RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource()); if (mBigLargeIconSet) { - mBuilder.mLargeIcon = oldLargeIcon; + mBuilder.mN.mLargeIcon = oldLargeIcon; } contentView.setImageViewBitmap(R.id.big_picture, mPicture); applyTopPadding(contentView); - boolean twoTextLines = mBuilder.mSubText != null && mBuilder.mContentText != null; + boolean twoTextLines = mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT) != null + && mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT) != null; mBuilder.addProfileBadge(contentView, twoTextLines ? R.id.profile_badge_line2 : R.id.profile_badge_line3); return contentView; @@ -4168,14 +3894,6 @@ public class Notification implements Parcelable } mPicture = extras.getParcelable(EXTRA_PICTURE); } - - /** - * @hide - */ - @Override - public void populateBigContentView(Notification wip) { - mBuilder.setBuilderBigContentView(wip, makeBigContentView()); - } } /** @@ -4255,15 +3973,19 @@ public class Notification implements Parcelable mBigText = extras.getCharSequence(EXTRA_BIG_TEXT); } - private RemoteViews makeBigContentView() { + /** + * @hide + */ + public RemoteViews makeBigContentView() { // Nasty - CharSequence oldBuilderContentText = mBuilder.mContentText; - mBuilder.mContentText = null; + CharSequence oldBuilderContentText = + mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT); + mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null); RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource()); - mBuilder.mContentText = oldBuilderContentText; + mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText); contentView.setTextViewText(R.id.big_text, mBuilder.processLegacyText(mBigText)); contentView.setViewVisibility(R.id.big_text, View.VISIBLE); @@ -4282,7 +4004,8 @@ public class Notification implements Parcelable private int calculateMaxLines() { int lineCount = MAX_LINES; boolean hasActions = mBuilder.mActions.size() > 0; - boolean hasSummary = (mSummaryTextSet ? mSummaryText : mBuilder.mSubText) != null; + boolean hasSummary = (mSummaryTextSet ? mSummaryText + : mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT)) != null; if (hasActions) { lineCount -= LINES_CONSUMED_BY_ACTIONS; } @@ -4291,19 +4014,11 @@ public class Notification implements Parcelable } // If we have less top padding at the top, we can fit less lines. - if (!mBuilder.mHasThreeLines) { + if (!mBuilder.hasThreeLines()) { lineCount--; } return lineCount; } - - /** - * @hide - */ - @Override - public void populateBigContentView(Notification wip) { - mBuilder.setBuilderBigContentView(wip, makeBigContentView()); - } } /** @@ -4384,16 +4099,18 @@ public class Notification implements Parcelable } } - private RemoteViews makeBigContentView() { + /** + * @hide + */ + public RemoteViews makeBigContentView() { // Remove the content text so line3 disappears unless you have a summary - // Nasty - CharSequence oldBuilderContentText = mBuilder.mContentText; - mBuilder.mContentText = null; + CharSequence oldBuilderContentText = mBuilder.mN.extras.getCharSequence(EXTRA_TEXT); + mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null); RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource()); - mBuilder.mContentText = oldBuilderContentText; + mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText); contentView.setViewVisibility(R.id.text2, View.GONE); @@ -4437,14 +4154,6 @@ public class Notification implements Parcelable return contentView; } - - /** - * @hide - */ - @Override - public void populateBigContentView(Notification wip) { - mBuilder.setBuilderBigContentView(wip, makeBigContentView()); - } } /** @@ -4536,16 +4245,16 @@ public class Notification implements Parcelable * @hide */ @Override - public void populateContentView(Notification wip) { - mBuilder.setBuilderContentView(wip, makeMediaContentView()); + public RemoteViews makeContentView() { + return makeMediaContentView(); } /** * @hide */ @Override - public void populateBigContentView(Notification wip) { - mBuilder.setBuilderBigContentView(wip, makeMediaBigContentView()); + public RemoteViews makeBigContentView() { + return makeMediaBigContentView(); } /** @hide */ @@ -4659,7 +4368,7 @@ public class Notification implements Parcelable R.color.notification_media_secondary_color); contentView.setTextColor(R.id.title, primaryColor); if (mBuilder.showsTimeOrChronometer()) { - if (mBuilder.mUseChronometer) { + if (mBuilder.getAllExtras().getBoolean(EXTRA_SHOW_CHRONOMETER)) { contentView.setTextColor(R.id.chronometer, secondaryColor); } else { contentView.setTextColor(R.id.time, secondaryColor); @@ -5503,7 +5212,7 @@ public class Notification implements Parcelable /** * Gets the accent color. * - * @see setColor + * @see #setColor */ @ColorInt public int getColor() { diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index cb0ff33ee9b3..f75b22af1139 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -207,33 +207,7 @@ public class NotificationManager */ public void notify(String tag, int id, Notification notification) { - int[] idOut = new int[1]; - INotificationManager service = getService(); - String pkg = mContext.getPackageName(); - if (notification.sound != null) { - notification.sound = notification.sound.getCanonicalUri(); - if (StrictMode.vmFileUriExposureEnabled()) { - notification.sound.checkFileUriExposed("Notification.sound"); - } - } - fixLegacySmallIcon(notification, pkg); - if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { - if (notification.getSmallIcon() == null) { - throw new IllegalArgumentException("Invalid notification (no valid small icon): " - + notification); - } - } - if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); - Notification stripped = notification.clone(); - Builder.stripForDelivery(stripped); - try { - service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, - stripped, idOut, UserHandle.myUserId()); - if (id != idOut[0]) { - Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]); - } - } catch (RemoteException e) { - } + notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId())); } /** @@ -251,12 +225,17 @@ public class NotificationManager } } fixLegacySmallIcon(notification, pkg); + if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { + if (notification.getSmallIcon() == null) { + throw new IllegalArgumentException("Invalid notification (no valid small icon): " + + notification); + } + } if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); - Notification stripped = notification.clone(); - Builder.stripForDelivery(stripped); + final Notification copy = notification.clone(); try { service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, - stripped, idOut, user.getIdentifier()); + copy, idOut, user.getIdentifier()); if (id != idOut[0]) { Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]); } @@ -287,13 +266,7 @@ public class NotificationManager */ public void cancel(String tag, int id) { - INotificationManager service = getService(); - String pkg = mContext.getPackageName(); - if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")"); - try { - service.cancelNotificationWithTag(pkg, tag, id, UserHandle.myUserId()); - } catch (RemoteException e) { - } + cancelAsUser(tag, id, new UserHandle(UserHandle.myUserId())); } /** diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index 0a0d77d2a465..4270e16b3295 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -16,6 +16,8 @@ package android.app.admin; +import android.os.Bundle; + import java.util.List; /** @@ -69,4 +71,13 @@ public abstract class DevicePolicyManagerInternal { * @return true if the uid is an active admin with the given policy. */ public abstract boolean isActiveAdminWithPolicy(int uid, int reqPolicy); + + /** + * Takes a {@link Bundle} containing "base" user restrictions stored in + * {@link com.android.server.pm.UserManagerService}, mixes restrictions set by the device owner + * and the profile owner and returns the merged restrictions. + * + * This method always returns a new {@link Bundle}. + */ + public abstract Bundle getComposedUserRestrictions(int userId, Bundle inBundle); } diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java index ff4ebee221e9..874026fb157d 100644 --- a/core/java/android/bluetooth/BluetoothHeadsetClient.java +++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java @@ -1059,6 +1059,41 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { } /** + * Sets whether audio routing is allowed. + * + * Note: This is an internal function and shouldn't be exposed + */ + public void setAudioRouteAllowed(boolean allowed) { + if (VDBG) log("setAudioRouteAllowed"); + if (mService != null && isEnabled()) { + try { + mService.setAudioRouteAllowed(allowed); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + } + + /** + * Returns whether audio routing is allowed. + * + * Note: This is an internal function and shouldn't be exposed + */ + public boolean getAudioRouteAllowed() { + if (VDBG) log("getAudioRouteAllowed"); + if (mService != null && isEnabled()) { + try { + return mService.getAudioRouteAllowed(); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** * Initiates a connection of audio channel. * * It setup SCO channel with remote connected Handsfree AG device. diff --git a/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl b/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl index e518b7d225f8..79ae4e48fa7d 100644 --- a/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl +++ b/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl @@ -62,6 +62,8 @@ interface IBluetoothHeadsetClient { int getAudioState(in BluetoothDevice device); boolean connectAudio(); boolean disconnectAudio(); + void setAudioRouteAllowed(boolean allowed); + boolean getAudioRouteAllowed(); Bundle getCurrentAgFeatures(in BluetoothDevice device); } diff --git a/core/java/android/content/pm/AppsQueryHelper.java b/core/java/android/content/pm/AppsQueryHelper.java index 56b317310365..a5a8e3f9c211 100644 --- a/core/java/android/content/pm/AppsQueryHelper.java +++ b/core/java/android/content/pm/AppsQueryHelper.java @@ -23,6 +23,8 @@ import android.content.Intent; import android.os.RemoteException; import android.os.UserHandle; import android.util.ArraySet; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.VisibleForTesting; @@ -46,6 +48,14 @@ public class AppsQueryHelper { */ public static int GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM = 1 << 1; + /** + * Return all input methods that are marked as default. + * <p>When this flag is set, {@code user} specified in + * {@link #queryApps(int, boolean, UserHandle)} must be + * {@link UserHandle#myUserId user of the current process}. + */ + public static int GET_DEFAULT_IMES = 1 << 2; + private final Context mContext; private List<ApplicationInfo> mAllApps; @@ -56,13 +66,14 @@ public class AppsQueryHelper { /** * Return a List of all packages that satisfy a specified criteria. * @param flags search flags. Use any combination of {@link #GET_NON_LAUNCHABLE_APPS}, - * {@link #GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM} + * {@link #GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM} or {@link #GET_DEFAULT_IMES}. * @param systemAppsOnly if true, only system apps will be returned * @param user user, whose apps are queried */ public List<String> queryApps(int flags, boolean systemAppsOnly, UserHandle user) { boolean nonLaunchableApps = (flags & GET_NON_LAUNCHABLE_APPS) > 0; boolean interactAcrossUsers = (flags & GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM) > 0; + boolean defaultImes = (flags & GET_DEFAULT_IMES) > 0; if (mAllApps == null) { mAllApps = getAllApps(user.getIdentifier()); } @@ -118,6 +129,33 @@ public class AppsQueryHelper { } } + if (defaultImes) { + if (UserHandle.myUserId() != user.getIdentifier()) { + throw new IllegalArgumentException("Specified user handle " + user + + " is not a user of the current process."); + } + List<InputMethodInfo> imis = getInputMethodList(); + int imisSize = imis.size(); + ArraySet<String> defaultImePackages = new ArraySet<>(); + for (int i = 0; i < imisSize; i++) { + InputMethodInfo imi = imis.get(i); + if (imi.isDefault(mContext)) { + defaultImePackages.add(imi.getPackageName()); + } + } + final int allAppsSize = mAllApps.size(); + for (int i = 0; i < allAppsSize; i++) { + final ApplicationInfo appInfo = mAllApps.get(i); + if (systemAppsOnly && !appInfo.isSystemApp()) { + continue; + } + final String packageName = appInfo.packageName; + if (defaultImePackages.contains(packageName)) { + result.add(packageName); + } + } + } + return result; } @@ -150,4 +188,12 @@ public class AppsQueryHelper { throw new IllegalStateException("Package manager has died", e); } } + + @VisibleForTesting + @SuppressWarnings("unchecked") + protected List<InputMethodInfo> getInputMethodList() { + InputMethodManager imm = (InputMethodManager) + mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + return imm.getInputMethodList(); + } } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index aed1a0b08ee1..27ecf9fc1309 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1708,6 +1708,15 @@ public abstract class PackageManager { public static final String FEATURE_BACKUP = "android.software.backup"; /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports freeform window management. + * Windows have title bars and can be moved and resized. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_FREEFORM_WINDOW_MANAGEMENT + = "android.software.freeform_window_management"; + + /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: * The device supports creating secondary users and managed profiles via * {@link DevicePolicyManager}. diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 1fcfacab34a3..51796ebe463a 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -513,7 +513,7 @@ public final class CameraManager { * {@link CameraManager#registerAvailabilityCallback} to be notified of such availability * changes.</p> * - * @see registerAvailabilityCallback + * @see #registerAvailabilityCallback */ public static abstract class AvailabilityCallback { diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 6bfa2a495b82..1bb0fbb74a53 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -119,8 +119,12 @@ public final class LinkProperties implements Parcelable { // // For one such example of this, see b/18867306. // - // TODO: Remove this special case altogether. - if (before.isIPv4Provisioned() && !after.isIPv4Provisioned()) { + // Additionally, losing IPv6 provisioning can result in TCP + // connections getting stuck until timeouts fire and other + // baffling failures. Therefore, loss of either IPv4 or IPv6 on a + // previously dualstack network is deemed a lost of provisioning. + if ((before.isIPv4Provisioned() && !after.isIPv4Provisioned()) || + (before.isIPv6Provisioned() && !after.isIPv6Provisioned())) { return ProvisioningChange.LOST_PROVISIONING; } return ProvisioningChange.STILL_PROVISIONED; diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 3ade170b9269..12cac85308c5 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -55,14 +55,12 @@ interface IUserManager { int getUserHandle(int userSerialNumber); Bundle getUserRestrictions(int userHandle); boolean hasUserRestriction(in String restrictionKey, int userHandle); - void setUserRestrictions(in Bundle restrictions, int userHandle); void setUserRestriction(String key, boolean value, int userId); void setSystemControlledUserRestriction(String key, boolean value, int userId); void setApplicationRestrictions(in String packageName, in Bundle restrictions, int userHandle); Bundle getApplicationRestrictions(in String packageName); Bundle getApplicationRestrictionsForUser(in String packageName, int userHandle); - void removeRestrictions(); void setDefaultGuestRestrictions(in Bundle restrictions); Bundle getDefaultGuestRestrictions(); boolean markGuestForDeletion(int userHandle); diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java index d64273a36eac..73c2c804bdf1 100644 --- a/core/java/android/os/ShellCommand.java +++ b/core/java/android/os/ShellCommand.java @@ -44,7 +44,7 @@ public abstract class ShellCommand { private FastPrintWriter mOutPrintWriter; private FastPrintWriter mErrPrintWriter; - public void exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, + public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ResultReceiver resultReceiver) { mTarget = target; mIn = in; @@ -89,6 +89,7 @@ public abstract class ShellCommand { mResultReceiver.send(res, null); } if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget); + return res; } public PrintWriter getOutPrintWriter() { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 1c1575e06f7e..17e1a28bca31 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -21,6 +21,7 @@ import android.annotation.SystemApi; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; import android.content.Context; import android.content.pm.UserInfo; import android.content.res.Resources; @@ -55,7 +56,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts"; @@ -67,7 +69,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_CONFIG_WIFI = "no_config_wifi"; @@ -78,7 +81,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_INSTALL_APPS = "no_install_apps"; @@ -89,7 +93,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps"; @@ -102,7 +107,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_SHARE_LOCATION = "no_share_location"; @@ -114,7 +120,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_INSTALL_UNKNOWN_SOURCES = "no_install_unknown_sources"; @@ -127,7 +134,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth"; @@ -139,7 +147,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer"; @@ -150,7 +159,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials"; @@ -163,7 +173,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_REMOVE_USER = "no_remove_user"; @@ -174,7 +185,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_DEBUGGING_FEATURES = "no_debugging_features"; @@ -187,7 +199,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_CONFIG_VPN = "no_config_vpn"; @@ -199,7 +212,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_CONFIG_TETHERING = "no_config_tethering"; @@ -213,7 +227,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_NETWORK_RESET = "no_network_reset"; @@ -227,7 +242,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_FACTORY_RESET = "no_factory_reset"; @@ -241,7 +257,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_ADD_USER = "no_add_user"; @@ -252,7 +269,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String ENSURE_VERIFY_APPS = "ensure_verify_apps"; @@ -266,7 +284,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts"; @@ -280,7 +299,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks"; @@ -300,7 +320,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_APPS_CONTROL = "no_control_apps"; @@ -312,7 +333,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media"; @@ -324,7 +346,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone"; @@ -336,7 +359,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume"; @@ -350,7 +374,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls"; @@ -361,7 +386,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_SMS = "no_sms"; @@ -373,7 +399,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_FUN = "no_fun"; @@ -393,7 +420,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_CREATE_WINDOWS = "no_create_windows"; @@ -406,7 +434,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_CROSS_PROFILE_COPY_PASTE = "no_cross_profile_copy_paste"; @@ -417,7 +446,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam"; @@ -426,7 +456,8 @@ public class UserManager { * Hidden user restriction to disallow access to wallpaper manager APIs. This user restriction * is always set for managed profiles. * @hide - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_WALLPAPER = "no_wallpaper"; @@ -438,7 +469,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String DISALLOW_SAFE_BOOT = "no_safe_boot"; @@ -447,7 +479,8 @@ public class UserManager { * Specifies if a user is not allowed to record audio. This restriction is always enabled for * background users. The default value is <code>false</code>. * - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() * @hide */ @@ -466,7 +499,8 @@ public class UserManager { * * <p/>Key for user restrictions. * <p/>Type: Boolean - * @see #setUserRestrictions(Bundle) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() */ public static final String ALLOW_PARENT_PROFILE_APP_LINKING @@ -740,36 +774,23 @@ public class UserManager { } /** - * Sets all the user-wide restrictions for this user. - * Requires the MANAGE_USERS permission. - * @param restrictions the Bundle containing all the restrictions. - * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction( - * android.content.ComponentName, String)} or - * {@link android.app.admin.DevicePolicyManager#clearUserRestriction( - * android.content.ComponentName, String)} instead. + * This will no longer work. Device owners and profile owners should use + * {@link DevicePolicyManager#addUserRestriction(ComponentName, String)} instead. */ + // System apps should use UserManager.setUserRestriction() instead. @Deprecated public void setUserRestrictions(Bundle restrictions) { - setUserRestrictions(restrictions, Process.myUserHandle()); + throw new UnsupportedOperationException("This method is no longer supported"); } /** - * Sets all the user-wide restrictions for the specified user. - * Requires the MANAGE_USERS permission. - * @param restrictions the Bundle containing all the restrictions. - * @param userHandle the UserHandle of the user for whom to set the restrictions. - * @deprecated use {@link android.app.admin.DevicePolicyManager#addUserRestriction( - * android.content.ComponentName, String)} or - * {@link android.app.admin.DevicePolicyManager#clearUserRestriction( - * android.content.ComponentName, String)} instead. + * This will no longer work. Device owners and profile owners should use + * {@link DevicePolicyManager#addUserRestriction(ComponentName, String)} instead. */ + // System apps should use UserManager.setUserRestriction() instead. @Deprecated public void setUserRestrictions(Bundle restrictions, UserHandle userHandle) { - try { - mService.setUserRestrictions(restrictions, userHandle.getIdentifier()); - } catch (RemoteException re) { - Log.w(TAG, "Could not set user restrictions", re); - } + throw new UnsupportedOperationException("This method is no longer supported"); } /** @@ -784,9 +805,7 @@ public class UserManager { */ @Deprecated public void setUserRestriction(String key, boolean value) { - Bundle bundle = getUserRestrictions(); - bundle.putBoolean(key, value); - setUserRestrictions(bundle); + setUserRestriction(key, value, Process.myUserHandle()); } /** @@ -882,9 +901,8 @@ public class UserManager { try { user = mService.createUser(name, flags); if (user != null && !user.isAdmin()) { - Bundle userRestrictions = mService.getUserRestrictions(user.id); - addDefaultUserRestrictions(userRestrictions); - mService.setUserRestrictions(userRestrictions, user.id); + mService.setUserRestriction(DISALLOW_SMS, true, user.id); + mService.setUserRestriction(DISALLOW_OUTGOING_CALLS, true, user.id); } } catch (RemoteException re) { Log.w(TAG, "Could not create a user", re); @@ -899,27 +917,22 @@ public class UserManager { * @hide */ public UserInfo createGuest(Context context, String name) { - UserInfo guest = createUser(name, UserInfo.FLAG_GUEST); - if (guest != null) { - Settings.Secure.putStringForUser(context.getContentResolver(), - Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id); - try { - Bundle guestRestrictions = mService.getDefaultGuestRestrictions(); - guestRestrictions.putBoolean(DISALLOW_SMS, true); - guestRestrictions.putBoolean(DISALLOW_INSTALL_UNKNOWN_SOURCES, true); - mService.setUserRestrictions(guestRestrictions, guest.id); - } catch (RemoteException re) { - Log.w(TAG, "Could not update guest restrictions"); + UserInfo guest = null; + try { + guest = mService.createUser(name, UserInfo.FLAG_GUEST); + if (guest != null) { + Settings.Secure.putStringForUser(context.getContentResolver(), + Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id); + + mService.setUserRestriction(DISALLOW_SMS, true, guest.id); + mService.setUserRestriction(DISALLOW_INSTALL_UNKNOWN_SOURCES, true, guest.id); } + } catch (RemoteException re) { + Log.w(TAG, "Could not create a user", re); } return guest; } - private static void addDefaultUserRestrictions(Bundle restrictions) { - restrictions.putBoolean(DISALLOW_OUTGOING_CALLS, true); - restrictions.putBoolean(DISALLOW_SMS, true); - } - /** * Creates a user with the specified name and options as a profile of another user. * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. @@ -1465,15 +1478,6 @@ public class UserManager { return false; } - /** @hide */ - public void removeRestrictions() { - try { - mService.removeRestrictions(); - } catch (RemoteException re) { - Log.w(TAG, "Could not change restrictions pin"); - } - } - /** * @hide * Set restrictions that should apply to any future guest user that's created. diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java new file mode 100644 index 000000000000..d7be6d8c6b29 --- /dev/null +++ b/core/java/android/os/UserManagerInternal.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 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 android.os; + +/** + * @hide Only for use within the system server. + */ +public abstract class UserManagerInternal { + /** + * Lock that must be held when calling certain methods in this class. + * + * This is used to avoid dead lock between + * {@link com.android.server.pm.UserManagerService} and + * {@link com.android.server.devicepolicy.DevicePolicyManagerService}. This lock should not + * be newly taken while holding the DPMS lock, which would cause a dead lock. Take this + * lock first before taking the DPMS lock to avoid that. + */ + public abstract Object getUserRestrictionsLock(); + + /** + * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get + * {@link com.android.server.pm.UserManagerService} to update effective user restrictions. + * + * Must be called while taking the {@link #getUserRestrictionsLock()} lock. + */ + public abstract void updateEffectiveUserRestrictionsRL(int userId); + + /** + * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get + * {@link com.android.server.pm.UserManagerService} to update effective user restrictions. + * + * Must be called while taking the {@link #getUserRestrictionsLock()} lock. + */ + public abstract void updateEffectiveUserRestrictionsForAllUsersRL(); + + /** + * Returns the "base" user restrictions. + * + * Used by {@link com.android.server.devicepolicy.DevicePolicyManagerService} for upgrading + * from MNC. + */ + public abstract Bundle getBaseUserRestrictions(int userId); + + /** + * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} for upgrading + * from MNC. + */ + public abstract void setBaseUserRestrictionsByDpmsForMigration(int userId, + Bundle baseRestrictions); +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 11effd040ab6..a1f9743d4d4e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6277,6 +6277,15 @@ public final class Settings { */ public static final String FORCE_ALLOW_ON_EXTERNAL = "force_allow_on_external"; + /** + * Whether any activity can be resized. When this is true, any + * activity, regardless of manifest values, can be resized for multi-window. + * (0 = false, 1 = true) + * @hide + */ + public static final String DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES + = "force_resizable_activities"; + /** * Whether user has enabled development settings. */ diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index d424546a9233..7e7b5fcb9975 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -31,6 +31,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.graphics.Bitmap; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; @@ -472,9 +473,10 @@ public abstract class NotificationListenerService extends Service { StatusBarNotification sbn = list.get(i); Notification notification = sbn.getNotification(); try { - Builder.rebuild(getContext(), notification); // convert icon metadata to legacy format for older clients createLegacyIconExtras(notification); + // populate remote views for older clients. + maybePopulateRemoteViews(notification); } catch (IllegalArgumentException e) { if (corruptNotifications == null) { corruptNotifications = new ArrayList<>(N); @@ -676,6 +678,18 @@ public abstract class NotificationListenerService extends Service { } } + /** + * Populates remote views for pre-N targeting apps. + */ + private void maybePopulateRemoteViews(Notification notification) { + if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { + Builder builder = Builder.recoverBuilder(getContext(), notification); + notification.contentView = builder.makeContentView(); + notification.bigContentView = builder.makeBigContentView(); + notification.headsUpContentView = builder.makeHeadsUpContentView(); + } + } + private class INotificationListenerWrapper extends INotificationListener.Stub { @Override public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder, @@ -689,9 +703,10 @@ public abstract class NotificationListenerService extends Service { } try { - Notification.Builder.rebuild(getContext(), sbn.getNotification()); + Notification notification = sbn.getNotification(); // convert icon metadata to legacy format for older clients createLegacyIconExtras(sbn.getNotification()); + maybePopulateRemoteViews(sbn.getNotification()); } catch (IllegalArgumentException e) { // drop corrupt notification sbn = null; diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index de509b2d7b9a..2459cfa84636 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -238,6 +238,7 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme initialScrollY = Touch.getInitialScrollY(widget, buffer); } + boolean wasTouchSelecting = isTouchSelecting(isMouse, buffer); boolean handled = Touch.onTouchEvent(widget, buffer, event); if (widget.didTouchFocusSelect() && !isMouse) { @@ -267,9 +268,9 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme // Cursor can be active at any location in the text while mouse pointer can start // selection from a totally different location. Use LAST_TAP_DOWN span to ensure // text selection will start from mouse pointer location. + final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN); if (isMouse && Touch.isSelectionStarted(buffer)) { - int offset = buffer.getSpanStart(LAST_TAP_DOWN); - Selection.setSelection(buffer, offset); + Selection.setSelection(buffer, startOffset); } if (isTouchSelecting(isMouse, buffer) && handled) { @@ -284,9 +285,9 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme // Update selection as we're moving the selection area. // Get the current touch position - int offset = widget.getOffsetForPosition(event.getX(), event.getY()); - - Selection.extendSelection(buffer, offset); + final int offset = widget.getOffsetForPosition(event.getX(), event.getY()); + Selection.setSelection(buffer, Math.min(startOffset, offset), + Math.max(startOffset, offset)); return true; } } else if (action == MotionEvent.ACTION_UP) { @@ -300,10 +301,12 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme return true; } - int offset = widget.getOffsetForPosition(event.getX(), event.getY()); - if (isTouchSelecting(isMouse, buffer)) { + if (wasTouchSelecting) { + final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN); + final int endOffset = widget.getOffsetForPosition(event.getX(), event.getY()); + Selection.setSelection(buffer, Math.min(startOffset, endOffset), + Math.max(startOffset, endOffset)); buffer.removeSpan(LAST_TAP_DOWN); - Selection.extendSelection(buffer, offset); } MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index c1eb80d28690..37d675707a84 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -343,15 +343,15 @@ public class TimeUtils { int seconds = (int) Math.floor(duration / 1000); int days = 0, hours = 0, minutes = 0; - if (seconds > SECONDS_PER_DAY) { + if (seconds >= SECONDS_PER_DAY) { days = seconds / SECONDS_PER_DAY; seconds -= days * SECONDS_PER_DAY; } - if (seconds > SECONDS_PER_HOUR) { + if (seconds >= SECONDS_PER_HOUR) { hours = seconds / SECONDS_PER_HOUR; seconds -= hours * SECONDS_PER_HOUR; } - if (seconds > SECONDS_PER_MINUTE) { + if (seconds >= SECONDS_PER_MINUTE) { minutes = seconds / SECONDS_PER_MINUTE; seconds -= minutes * SECONDS_PER_MINUTE; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index fa86c74139ad..8b804e8ddc8a 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2415,6 +2415,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG3_SCROLL_INDICATOR_START * 1 PFLAG3_SCROLL_INDICATOR_END * 1 PFLAG3_ASSIST_BLOCKED + * 1111111 PFLAG3_POINTER_ICON_MASK * |-------|-------|-------|-------| */ @@ -2611,6 +2612,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback, static final int PFLAG3_ASSIST_BLOCKED = 0x4000; /** + * The mask for use with private flags indicating bits used for pointer icon shapes. + */ + static final int PFLAG3_POINTER_ICON_MASK = 0x7f8000; + + /** + * Left-shift used for pointer icon shape values in private flags. + */ + static final int PFLAG3_POINTER_ICON_LSHIFT = 15; + + /** + * Value indicating {@link PointerIcon.STYLE_NOT_SPECIFIED}. + */ + private static final int PFLAG3_POINTER_ICON_NOT_SPECIFIED = 0 << PFLAG3_POINTER_ICON_LSHIFT; + + /** + * Value indicating {@link PointerIcon.STYLE_NULL}. + */ + private static final int PFLAG3_POINTER_ICON_NULL = 1 << PFLAG3_POINTER_ICON_LSHIFT; + + /** + * Value incicating {@link PointerIcon.STYLE_CUSTOM}. + */ + private static final int PFLAG3_POINTER_ICON_CUSTOM = 2 << PFLAG3_POINTER_ICON_LSHIFT; + + /** + * The base value for other pointer icon shapes. + */ + private static final int PFLAG3_POINTER_ICON_VALUE_START = 3 << PFLAG3_POINTER_ICON_LSHIFT; + + /** * Always allow a user to over-scroll this view, provided it is a * view that can scroll. * @@ -5341,21 +5372,53 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the - * OnLongClickListener did not consume the event. + * Calls this view's OnLongClickListener, if it is defined. Invokes the + * context menu if the OnLongClickListener did not consume the event. * - * @return True if one of the above receivers consumed the event, false otherwise. + * @return {@code true} if one of the above receivers consumed the event, + * {@code false} otherwise */ public boolean performLongClick() { + return performLongClickInternal(false, 0, 0); + } + + /** + * Calls this view's OnLongClickListener, if it is defined. Invokes the + * context menu if the OnLongClickListener did not consume the event, + * anchoring it to an (x,y) coordinate. + * + * @param x x coordinate of the anchoring touch event + * @param y y coordinate of the anchoring touch event + * @return {@code true} if one of the above receivers consumed the event, + * {@code false} otherwise + */ + public boolean performLongClick(float x, float y) { + return performLongClickInternal(true, x, y); + } + + /** + * Calls this view's OnLongClickListener, if it is defined. Invokes the + * context menu if the OnLongClickListener did not consume the event, + * optionally anchoring it to an (x,y) coordinate. + * + * @param isAnchored whether this long click is anchored to a touch event + * @param x x coordinate of the anchoring touch event, ignored if + * {@code isAnchored} is set to {@code false} + * @param y y coordinate of the anchoring touch event, ignored if + * {@code isAnchored} is set to {@code false} + * @return {@code true} if one of the above receivers consumed the event, + * {@code false} otherwise + */ + private boolean performLongClickInternal(boolean isAnchored, float x, float y) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); boolean handled = false; - ListenerInfo li = mListenerInfo; + final ListenerInfo li = mListenerInfo; if (li != null && li.mOnLongClickListener != null) { handled = li.mOnLongClickListener.onLongClick(View.this); } if (!handled) { - handled = showContextMenu(); + handled = isAnchored ? showContextMenu(x, y) : showContextMenu(); } if (handled) { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); @@ -10002,32 +10065,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * KeyEvent.Callback.onKeyDown()}: perform press of the view * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER} * is released, if the view is enabled and clickable. + * <p> + * Key presses in software keyboards will generally NOT trigger this + * listener, although some may elect to do so in some situations. Do not + * rely on this to catch software key presses. * - * <p>Key presses in software keyboards will generally NOT trigger this listener, - * although some may elect to do so in some situations. Do not rely on this to - * catch software key presses. - * - * @param keyCode A key code that represents the button pressed, from - * {@link android.view.KeyEvent}. - * @param event The KeyEvent object that defines the button action. + * @param keyCode a key code that represents the button pressed, from + * {@link android.view.KeyEvent} + * @param event the KeyEvent object that defines the button action */ public boolean onKeyDown(int keyCode, KeyEvent event) { - boolean result = false; - if (KeyEvent.isConfirmKey(keyCode)) { if ((mViewFlags & ENABLED_MASK) == DISABLED) { return true; } - // Long clickable items don't necessarily have to be clickable - if (((mViewFlags & CLICKABLE) == CLICKABLE || - (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) && - (event.getRepeatCount() == 0)) { - setPressed(true); - checkForLongClick(0); + + // Long clickable items don't necessarily have to be clickable. + if (((mViewFlags & CLICKABLE) == CLICKABLE + || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) + && (event.getRepeatCount() == 0)) { + // For the purposes of menu anchoring and drawable hotspots, + // key events are considered to be at the center of the view. + final float x = getWidth() / 2f; + final float y = getHeight() / 2f; + setPressed(true, x, y); + checkForLongClick(0, x, y); return true; } } - return result; + + return false; } /** @@ -10555,7 +10622,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); - checkForLongClick(0); + checkForLongClick(0, x, y); } break; @@ -12889,6 +12956,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags |= PFLAG_DIRTY; + // Release any resources in-case we don't end up drawing again + // as anything cached is no longer valid + resetDisplayList(); + if (invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; @@ -19992,13 +20063,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - private void checkForLongClick(int delayOffset) { + private void checkForLongClick(int delayOffset, float x, float y) { if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } + mPendingCheckForLongPress.setAnchor(x, y); mPendingCheckForLongPress.rememberWindowAttachCount(); postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset); @@ -20997,7 +21069,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** @hide */ public int getPointerShape(MotionEvent event, float x, float y) { - return PointerIcon.STYLE_NOT_SPECIFIED; + final int value = (mPrivateFlags3 & PFLAG3_POINTER_ICON_MASK); + switch (value) { + case PFLAG3_POINTER_ICON_NOT_SPECIFIED: + return PointerIcon.STYLE_NOT_SPECIFIED; + case PFLAG3_POINTER_ICON_NULL: + return PointerIcon.STYLE_NULL; + case PFLAG3_POINTER_ICON_CUSTOM: + return PointerIcon.STYLE_CUSTOM; + default: + return ((value - PFLAG3_POINTER_ICON_VALUE_START) >> PFLAG3_POINTER_ICON_LSHIFT) + + PointerIcon.STYLE_ARROW; + } + } + + /** @hide */ + public void setPointerShape(int pointerShape) { + int newValue; + if (pointerShape == PointerIcon.STYLE_NOT_SPECIFIED) { + newValue = PFLAG3_POINTER_ICON_NOT_SPECIFIED; + } else if (pointerShape == PointerIcon.STYLE_NULL) { + newValue = PFLAG3_POINTER_ICON_NULL; + } else if (pointerShape == PointerIcon.STYLE_CUSTOM) { + newValue = PFLAG3_POINTER_ICON_CUSTOM; + } else if (pointerShape >= PointerIcon.STYLE_ARROW + && pointerShape <= PointerIcon.STYLE_GRABBING) { + newValue = ((pointerShape - PointerIcon.STYLE_ARROW) << PFLAG3_POINTER_ICON_LSHIFT) + + PFLAG3_POINTER_ICON_VALUE_START; + } else { + Log.w(VIEW_LOG_TAG, "Invalid pointer shape " + pointerShape + " is specified."); + return; + } + if (newValue != (mPrivateFlags3 & PFLAG3_POINTER_ICON_MASK)) { + mPrivateFlags3 = (mPrivateFlags3 & ~PFLAG3_POINTER_ICON_MASK) | newValue; + } } // @@ -21354,17 +21459,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private final class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; + private float mX; + private float mY; @Override public void run() { if (isPressed() && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { - if (performLongClick()) { + if (performLongClick(mX, mY)) { mHasPerformedLongPress = true; } } } + public void setAnchor(float x, float y) { + mX = x; + mY = y; + } + public void rememberWindowAttachCount() { mOriginalWindowAttachCount = mWindowAttachCount; } @@ -21378,7 +21490,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void run() { mPrivateFlags &= ~PFLAG_PREPRESSED; setPressed(true, x, y); - checkForLongClick(ViewConfiguration.getTapTimeout()); + checkForLongClick(ViewConfiguration.getTapTimeout(), x, y); } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 7c7ad9102695..db978a6748f7 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -4435,6 +4435,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } } + + if (mCurrentDragStartEvent != null && child.getVisibility() == VISIBLE) { + notifyChildOfDragStart(child); + } } private void addInArray(View child, int index) { @@ -4686,6 +4690,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mTransientIndices.set(i, oldIndex - 1); } } + + if (mCurrentDragStartEvent != null) { + mChildrenInterestedInDrag.remove(view); + } } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 7d39a0cc20d9..e17bdd7d9a3e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4191,7 +4191,10 @@ public final class ViewRootImpl implements ViewParent, if (mPointerIconShape != pointerShape) { mPointerIconShape = pointerShape; - event.getDevice().setPointerShape(pointerShape); + final InputDevice inputDevice = event.getDevice(); + if (inputDevice != null) { + inputDevice.setPointerShape(pointerShape); + } } } else if (event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) { mPointerIconShape = PointerIcon.STYLE_NOT_SPECIFIED; diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 1b1773669ce2..1735e1b9e829 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -3083,8 +3083,32 @@ public class AccessibilityNodeInfo implements Parcelable { return "ACTION_PASTE"; case ACTION_SET_SELECTION: return "ACTION_SET_SELECTION"; + case ACTION_EXPAND: + return "ACTION_EXPAND"; + case ACTION_COLLAPSE: + return "ACTION_COLLAPSE"; + case ACTION_DISMISS: + return "ACTION_DISMISS"; + case ACTION_SET_TEXT: + return "ACTION_SET_TEXT"; + case R.id.accessibilityActionShowOnScreen: + return "ACTION_SHOW_ON_SCREEN"; + case R.id.accessibilityActionScrollToPosition: + return "ACTION_SCROLL_TO_POSITION"; + case R.id.accessibilityActionScrollUp: + return "ACTION_SCROLL_UP"; + case R.id.accessibilityActionScrollLeft: + return "ACTION_SCROLL_LEFT"; + case R.id.accessibilityActionScrollDown: + return "ACTION_SCROLL_DOWN"; + case R.id.accessibilityActionScrollRight: + return "ACTION_SCROLL_RIGHT"; + case R.id.accessibilityActionSetProgress: + return "ACTION_SET_PROGRESS"; + case R.id.accessibilityActionContextClick: + return "ACTION_CONTEXT_CLICK"; default: - return"ACTION_UNKNOWN"; + return "ACTION_UNKNOWN"; } } diff --git a/core/java/android/webkit/WebResourceRequest.java b/core/java/android/webkit/WebResourceRequest.java index 0760d2b7bf9d..23e9a0df56fb 100644 --- a/core/java/android/webkit/WebResourceRequest.java +++ b/core/java/android/webkit/WebResourceRequest.java @@ -40,6 +40,13 @@ public interface WebResourceRequest { boolean isForMainFrame(); /** + * Gets whether the request was a result of a redirect. + * + * @return whether the request was a result of a redirect. + */ + boolean isRedirect(); + + /** * Gets whether a gesture (such as a click) was associated with the request. * For security reasons in certain situations this method may return false even though the * sequence of events which caused the request to be created was initiated by a user gesture. diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index f2bb55a7db13..0e5034de7f4f 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -38,12 +38,42 @@ public class WebViewClient { * @param url The url to be loaded. * @return True if the host application wants to leave the current WebView * and handle the url itself, otherwise return false. + * @deprecated Use {@link #shouldOverrideUrlLoading(WebView, WebResourceRequest) + * shouldOverrideUrlLoading(WebView, WebResourceRequest)} instead. */ + @Deprecated public boolean shouldOverrideUrlLoading(WebView view, String url) { return false; } /** + * Give the host application a chance to take over the control when a new + * url is about to be loaded in the current WebView. If WebViewClient is not + * provided, by default WebView will ask Activity Manager to choose the + * proper handler for the url. If WebViewClient is provided, return true + * means the host application handles the url, while return false means the + * current WebView handles the url. + * + * <p>Notes: + * <ul> + * <li>This method is not called for requests using the POST "method".</li> + * <li>This method is also called for subframes with non-http schemes, thus it is + * strongly disadvised to unconditionally call {@link WebView#loadUrl(String)} + * with the request's url from inside the method and then return true, + * as this will make WebView to attempt loading a non-http url, and thus fail.</li> + * </ul> + * </p> + * + * @param view The WebView that is initiating the callback. + * @param request Object containing the details of the request. + * @return True if the host application wants to leave the current WebView + * and handle the url itself, otherwise return false. + */ + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + return shouldOverrideUrlLoading(view, request.getUrl().toString()); + } + + /** * Notify the host application that a page has started loading. This method * is called once for each main frame load so a page with iframes or * framesets will call onPageStarted one time for the main frame. This also diff --git a/core/java/com/android/internal/alsa/AlsaCardsParser.java b/core/java/com/android/internal/alsa/AlsaCardsParser.java index 67ee08596553..17e8c9c80592 100644 --- a/core/java/com/android/internal/alsa/AlsaCardsParser.java +++ b/core/java/com/android/internal/alsa/AlsaCardsParser.java @@ -94,6 +94,12 @@ public class AlsaCardsParser { public String textFormat() { return mCardName + " : " + mCardDescription; } + + public void log(int listIndex) { + Slog.d(TAG, "" + listIndex + + " [" + mCardNum + " " + mCardName + " : " + mCardDescription + + " usb:" + mIsUsb); + } } public AlsaCardsParser() {} @@ -169,9 +175,41 @@ public class AlsaCardsParser { // return -1 if none found public int getDefaultUsbCard() { + // save the current list of devices + ArrayList<AlsaCardsParser.AlsaCardRecord> prevRecs = mCardRecords; + if (DEBUG) { + LogDevices("Previous Devices:", prevRecs); + } + + // get the new list of devices + scan(); + if (DEBUG) { + LogDevices("Current Devices:", mCardRecords); + } + + // Calculate the difference between the old and new device list + ArrayList<AlsaCardRecord> newRecs = getNewCardRecords(prevRecs); + if (DEBUG) { + LogDevices("New Devices:", newRecs); + } + // Choose the most-recently added EXTERNAL card + // Check recently added devices + for (AlsaCardRecord rec : newRecs) { + if (DEBUG) { + Slog.d(TAG, rec.mCardName + " card:" + rec.mCardNum + " usb:" + rec.mIsUsb); + } + if (rec.mIsUsb) { + // Found it + return rec.mCardNum; + } + } + // or return the first added EXTERNAL card? - for (AlsaCardRecord rec : mCardRecords) { + for (AlsaCardRecord rec : prevRecs) { + if (DEBUG) { + Slog.d(TAG, rec.mCardName + " card:" + rec.mCardNum + " usb:" + rec.mIsUsb); + } if (rec.mIsUsb) { return rec.mCardNum; } @@ -183,11 +221,17 @@ public class AlsaCardsParser { public int getDefaultCard() { // return an external card if possible int card = getDefaultUsbCard(); + if (DEBUG) { + Slog.d(TAG, "getDefaultCard() default usb card:" + card); + } if (card < 0 && getNumCardRecords() > 0) { // otherwise return the (internal) card with the highest number card = getCardRecordAt(getNumCardRecords() - 1).mCardNum; } + if (DEBUG) { + Slog.d(TAG, " returns card:" + card); + } return card; } @@ -222,4 +266,13 @@ public class AlsaCardsParser { } } } + + static public void LogDevices(String caption, ArrayList<AlsaCardRecord> deviceList) { + Slog.d(TAG, caption + " ----------------"); + int listIndex = 0; + for (AlsaCardRecord device : deviceList) { + device.log(listIndex++); + } + Slog.d(TAG, "----------------"); + } } diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index afef7633bc69..7fab31f6f9ea 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -5409,7 +5409,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { * @Return Returns true if the window should show a non client decor. **/ private static boolean hasNonClientDecor(int workspaceId) { - return workspaceId == FREEFORM_WORKSPACE_STACK_ID || workspaceId == PINNED_STACK_ID; + return workspaceId == FREEFORM_WORKSPACE_STACK_ID; } /** diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 20d00b03092a..7b69c9e5b694 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -189,6 +189,7 @@ LOCAL_C_INCLUDES += \ external/pdfium/core/include/fpdfdoc \ external/pdfium/fpdfsdk/include \ external/pdfium/public \ + external/skia/include/private \ external/skia/src/core \ external/skia/src/effects \ external/skia/src/images \ diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 28bc7fe47dd2..ecaf9519d8bc 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -165,7 +165,7 @@ public: virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { const SkImageInfo& info = bitmap->info(); - if (info.fColorType == kUnknown_SkColorType) { + if (info.colorType() == kUnknown_SkColorType) { ALOGW("unable to reuse a bitmap as the target has an unknown bitmap configuration"); return false; } diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 93259e70abbd..068517a12288 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -486,7 +486,7 @@ static bool computeAllocationSize(const SkBitmap& bitmap, size_t* size) { android::Bitmap* GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, SkColorTable* ctable) { const SkImageInfo& info = bitmap->info(); - if (info.fColorType == kUnknown_SkColorType) { + if (info.colorType() == kUnknown_SkColorType) { doThrowIAE(env, "unknown bitmap configuration"); return NULL; } @@ -538,7 +538,7 @@ static void allocatePixelsReleaseProc(void* ptr, void* ctx) { bool GraphicsJNI::allocatePixels(JNIEnv* env, SkBitmap* bitmap, SkColorTable* ctable) { const SkImageInfo& info = bitmap->info(); - if (info.fColorType == kUnknown_SkColorType) { + if (info.colorType() == kUnknown_SkColorType) { doThrowIAE(env, "unknown bitmap configuration"); return NULL; } @@ -581,7 +581,7 @@ android::Bitmap* GraphicsJNI::allocateAshmemPixelRef(JNIEnv* env, SkBitmap* bitm int fd; const SkImageInfo& info = bitmap->info(); - if (info.fColorType == kUnknown_SkColorType) { + if (info.colorType() == kUnknown_SkColorType) { doThrowIAE(env, "unknown bitmap configuration"); return nullptr; } @@ -625,7 +625,7 @@ android::Bitmap* GraphicsJNI::allocateAshmemPixelRef(JNIEnv* env, SkBitmap* bitm android::Bitmap* GraphicsJNI::mapAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap, SkColorTable* ctable, int fd, void* addr, bool readOnly) { const SkImageInfo& info = bitmap->info(); - if (info.fColorType == kUnknown_SkColorType) { + if (info.colorType() == kUnknown_SkColorType) { doThrowIAE(env, "unknown bitmap configuration"); return nullptr; } diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 24055e76234e..da96b935883e 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -313,12 +313,11 @@ static jlong nativeLockCanvas(JNIEnv* env, jclass clazz, return 0; } + SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height, convertPixelFormat(outBuffer.format), - kPremul_SkAlphaType); - if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) { - info.fAlphaType = kOpaque_SkAlphaType; - } + outBuffer.format == PIXEL_FORMAT_RGBX_8888 ? + kOpaque_SkAlphaType : kPremul_SkAlphaType); SkBitmap bitmap; ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 65ebb6633a8f..931ad54fb5e8 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -138,35 +138,36 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, return NULL; } - SkImageInfo screenshotInfo; - screenshotInfo.fWidth = screenshot->getWidth(); - screenshotInfo.fHeight = screenshot->getHeight(); - + SkColorType colorType; + SkAlphaType alphaType; switch (screenshot->getFormat()) { case PIXEL_FORMAT_RGBX_8888: { - screenshotInfo.fColorType = kRGBA_8888_SkColorType; - screenshotInfo.fAlphaType = kOpaque_SkAlphaType; + colorType = kRGBA_8888_SkColorType; + alphaType = kOpaque_SkAlphaType; break; } case PIXEL_FORMAT_RGBA_8888: { - screenshotInfo.fColorType = kRGBA_8888_SkColorType; - screenshotInfo.fAlphaType = kPremul_SkAlphaType; + colorType = kRGBA_8888_SkColorType; + alphaType = kPremul_SkAlphaType; break; } case PIXEL_FORMAT_RGB_565: { - screenshotInfo.fColorType = kRGB_565_SkColorType; - screenshotInfo.fAlphaType = kOpaque_SkAlphaType; + colorType = kRGB_565_SkColorType; + alphaType = kOpaque_SkAlphaType; break; } default: { return NULL; } } + SkImageInfo screenshotInfo = SkImageInfo::Make(screenshot->getWidth(), + screenshot->getHeight(), + colorType, alphaType); const size_t rowBytes = screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat()); - if (!screenshotInfo.fWidth || !screenshotInfo.fHeight) { + if (!screenshotInfo.width() || !screenshotInfo.height()) { return NULL; } diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp index b736a17c1199..e185281dad8a 100644 --- a/core/jni/android_view_TextureView.cpp +++ b/core/jni/android_view_TextureView.cpp @@ -72,29 +72,25 @@ static struct { // FIXME: consider exporting this to share (e.g. android_view_Surface.cpp) static inline SkImageInfo convertPixelFormat(const ANativeWindow_Buffer& buffer) { - SkImageInfo info; - info.fWidth = buffer.width; - info.fHeight = buffer.height; + SkColorType colorType = kUnknown_SkColorType; + SkAlphaType alphaType = kOpaque_SkAlphaType; switch (buffer.format) { case WINDOW_FORMAT_RGBA_8888: - info.fColorType = kN32_SkColorType; - info.fAlphaType = kPremul_SkAlphaType; + colorType = kN32_SkColorType; + alphaType = kPremul_SkAlphaType; break; case WINDOW_FORMAT_RGBX_8888: - info.fColorType = kN32_SkColorType; - info.fAlphaType = kOpaque_SkAlphaType; + colorType = kN32_SkColorType; + alphaType = kOpaque_SkAlphaType; break; case WINDOW_FORMAT_RGB_565: - info.fColorType = kRGB_565_SkColorType; - info.fAlphaType = kOpaque_SkAlphaType; + colorType = kRGB_565_SkColorType; + alphaType = kOpaque_SkAlphaType; break; default: - info.fColorType = kUnknown_SkColorType; - // switch to kUnknown_SkAlphaType when its in skia - info.fAlphaType = kOpaque_SkAlphaType; break; } - return info; + return SkImageInfo::Make(buffer.width, buffer.height, colorType, alphaType); } /** diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 39eda584e70c..5828829511c1 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1476,7 +1476,7 @@ <permission android:name="android.permission.SYSTEM_ALERT_WINDOW" android:label="@string/permlab_systemAlertWindow" android:description="@string/permdesc_systemAlertWindow" - android:protectionLevel="signature|preinstalled|appop|pre23" /> + android:protectionLevel="signature|preinstalled|appop|pre23|development" /> <!-- ================================== --> <!-- Permissions affecting the system wallpaper --> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 45e82ace0360..ff0fa9ce4fd4 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1029,7 +1029,7 @@ <string name="adb_active_notification_message" msgid="1016654627626476142">"タップしてUSBデバッグを無効化"</string> <string name="select_input_method" msgid="8547250819326693584">"キーボードの変更"</string> <string name="configure_input_methods" msgid="4769971288371946846">"キーボードの選択"</string> - <string name="show_ime" msgid="9157568568695230830">"入力方法を表示する"</string> + <string name="show_ime" msgid="9157568568695230830">"スクリーンキーボードを表示する"</string> <string name="hardware" msgid="7517821086888990278">"ハードウェア"</string> <string name="select_keyboard_layout_notification_title" msgid="1407367017263030773">"キーボードレイアウトの選択"</string> <string name="select_keyboard_layout_notification_message" msgid="4465907700449257063">"タップしてキーボードレイアウトを選択してください。"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 6c576add300c..228f4aed49c3 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -677,7 +677,7 @@ <string name="lockscreen_transport_stop_description" msgid="5907083260651210034">"Opriți"</string> <string name="lockscreen_transport_rew_description" msgid="6944412838651990410">"Derulaţi"</string> <string name="lockscreen_transport_ffw_description" msgid="42987149870928985">"Derulaţi rapid înainte"</string> - <string name="emergency_calls_only" msgid="6733978304386365407">"Numai apeluri de urgenţă"</string> + <string name="emergency_calls_only" msgid="6733978304386365407">"Numai apeluri de urgență"</string> <string name="lockscreen_network_locked_message" msgid="143389224986028501">"Rețea blocată"</string> <string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"Cardul SIM este blocat cu codul PUK."</string> <string name="lockscreen_sim_puk_locked_instructions" msgid="8127916255245181063">"Consultaţi Ghidul de utilizare sau contactaţi Serviciul de relaţii cu clienţii."</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 960a31418a7d..7a3aab8eabb0 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1006,14 +1006,14 @@ <string name="sms_short_code_confirm_never_allow" msgid="446992765774269673">"Asla İzin Verme"</string> <string name="sim_removed_title" msgid="6227712319223226185">"SIM kart çıkarıldı"</string> <string name="sim_removed_message" msgid="5450336489923274918">"Hücresel ağ, geçerli bir SIM kart takıp cihazınızı yeniden başlatıncaya kadar kullanılamayacak."</string> - <string name="sim_done_button" msgid="827949989369963775">"Tamamlandı"</string> + <string name="sim_done_button" msgid="827949989369963775">"Bitti"</string> <string name="sim_added_title" msgid="3719670512889674693">"SIM kart eklendi"</string> <string name="sim_added_message" msgid="7797975656153714319">"Hücresel ağa erişmek için cihazınızı yeniden başlatın."</string> <string name="sim_restart_button" msgid="4722407842815232347">"Yeniden başlat"</string> <string name="time_picker_dialog_title" msgid="8349362623068819295">"Saati ayarlayın"</string> <string name="date_picker_dialog_title" msgid="5879450659453782278">"Tarihi ayarlayın"</string> <string name="date_time_set" msgid="5777075614321087758">"Ayarla"</string> - <string name="date_time_done" msgid="2507683751759308828">"Tamamlandı"</string> + <string name="date_time_done" msgid="2507683751759308828">"Bitti"</string> <string name="perms_new_perm_prefix" msgid="8257740710754301407"><font size="12" fgcolor="#ff33b5e5">"YENİ: "</font></string> <string name="perms_description_app" msgid="5139836143293299417">"Sağlayan: <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="no_permissions" msgid="7283357728219338112">"İzin gerektirmez"</string> diff --git a/core/res/res/values-watch/strings.xml b/core/res/res/values-watch/strings.xml index e5991fc111ea..dde8b2e3ec50 100644 --- a/core/res/res/values-watch/strings.xml +++ b/core/res/res/values-watch/strings.xml @@ -26,47 +26,4 @@ <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. Override from base which says "Body Sensors". [CHAR_LIMIT=25] --> <string name="permgrouplab_sensors">Sensors</string> - - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permgrouplab_contactswear">access your contacts</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permgrouplab_locationwear">access this watch\'s location</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permgrouplab_calendarwear">access your calendar</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permgrouplab_smswear">send and view SMS messages</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permgrouplab_storagewear">access photos, media, and files on your watch</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permgrouplab_microphonewear">record audio</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permgrouplab_camerawear">take pictures and record video</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permgrouplab_phonewear">make and manage phone calls</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permgrouplab_sensorswear">access sensor data about your vital signs</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permlab_statusBarServicewear">be the status bar</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permlab_bodySensorswear">access body sensors (like heart rate monitors)</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permlab_accessFineLocationwear">access precise location (GPS and network-based)</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permlab_accessCoarseLocationwear">access approximate location (network-based)</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permlab_sim_communicationwear">send commands to the SIM</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permlab_createNetworkSocketswear">have full network access</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permlab_manageProfileAndDeviceOwnerswear">manage profile and device owners</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permlab_changeWimaxStatewear">change WiMAX state</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permlab_handoverStatuswear">receive Android Beam transfer status</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permlab_route_media_outputwear">route media output</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permlab_readInstallSessionswear">read install sessions</string> - <!-- Description of a permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=100] --> - <string name="permlab_requestInstallPackageswear">request install packages</string> </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 093ea80950b7..d7dd3ec92a56 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5018,6 +5018,12 @@ i <attr name="autoMirrored" format="boolean" /> </declare-styleable> + <!-- Drawable class used to wrap other drawables. --> + <declare-styleable name="DrawableWrapper"> + <!-- The wrapped drawable. --> + <attr name="drawable" /> + </declare-styleable> + <!-- Drawable used to render several states. Each state is represented by a child drawable. --> <declare-styleable name="StateListDrawable"> @@ -5385,6 +5391,7 @@ i <attr name="color" /> </declare-styleable> + <!-- Drawable used to wrap and inset another drawable. --> <declare-styleable name="InsetDrawable"> <attr name="visible" /> <attr name="drawable" /> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 2621bc9cf219..dd42c2278ba2 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -367,7 +367,8 @@ <dimen name="resolver_max_width">480dp</dimen> - <!-- Size of the offset applied to the position of the circular mask. This + <!-- @deprecated Use config_windowOutsetBottom instead. + Size of the offset applied to the position of the circular mask. This is only used on circular displays. In the case where there is no "chin", this will default to 0 --> <dimen name="circular_display_mask_offset">0px</dimen> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index c8d53d712119..7584d4de753c 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -621,7 +621,7 @@ <string name="permdesc_statusBar">Allows the app to disable the status bar or add and remove system icons.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_statusBarService">status bar</string> + <string name="permlab_statusBarService">be the status bar</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_statusBarService">Allows the app to be the status bar.</string> @@ -718,7 +718,7 @@ discover information about which applications are used on the device.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_manageProfileAndDeviceOwners">Manage profile and device owners</string> + <string name="permlab_manageProfileAndDeviceOwners">manage profile and device owners</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to set the profile/device owners. [CHAR LIMIT=NONE] --> <string name="permdesc_manageProfileAndDeviceOwners">Allows apps to set the profile owners and the device owner.</string> @@ -882,7 +882,7 @@ Malicious apps may use this to erase or modify your call log.</string> <!-- Title of the body sensors permission, listed so the user can decide whether to allow the application to access body sensor data. [CHAR LIMIT=30] --> - <string name="permlab_bodySensors">body sensors (like heart rate monitors) + <string name="permlab_bodySensors">access body sensors (like heart rate monitors) </string> <!-- Description of the body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] --> <string name="permdesc_bodySensors" product="default">Allows the app to access data from sensors @@ -936,7 +936,7 @@ with the operation of the GPS or other location sources.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_accessFineLocation">precise location (GPS and + <string name="permlab_accessFineLocation">access precise location (GPS and network-based)</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_accessFineLocation">Allows the app to get your @@ -947,7 +947,7 @@ battery power.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_accessCoarseLocation">approximate location + <string name="permlab_accessCoarseLocation">access approximate location (network-based)</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_accessCoarseLocation">Allows the app to get your @@ -970,7 +970,7 @@ without your confirmation.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_sim_communication">sim communication</string> + <string name="permlab_sim_communication">send commands to the SIM</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_sim_communication">Allows the app to send commands to the SIM. This is very dangerous.</string> @@ -1077,7 +1077,7 @@ connected.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_createNetworkSockets">full network access</string> + <string name="permlab_createNetworkSockets">have full network access</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_createNetworkSockets">Allows the app to create network sockets and use custom network protocols. The browser and other @@ -1141,7 +1141,7 @@ WiMAX is enabled and information about any WiMAX networks that are connected. </string> - <string name="permlab_changeWimaxState">Change WiMAX state</string> + <string name="permlab_changeWimaxState">change WiMAX state</string> <string name="permdesc_changeWimaxState" product="tablet">Allows the app to connect the tablet to and disconnect the tablet from WiMAX networks.</string> <string name="permdesc_changeWimaxState" product="tv">Allows the app to @@ -1346,7 +1346,7 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_accessDrmCertificates">Allows an application to provision and use DRM certficates. Should never be needed for normal apps.</string> - <string name="permlab_handoverStatus">Receive Android Beam transfer status</string> + <string name="permlab_handoverStatus">receive Android Beam transfer status</string> <string name="permdesc_handoverStatus">Allows this application to receive information about current Android Beam transfers</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> @@ -2975,17 +2975,17 @@ <string name="activity_list_empty">No matching activities found.</string> <!-- Title of an application permission that lets an application route media output. --> - <string name="permlab_route_media_output">Route media output</string> + <string name="permlab_route_media_output">route media output</string> <!-- Description of an application permission that lets an application route media output. --> <string name="permdesc_route_media_output">Allows an application to route media output to other external devices.</string> <!-- Title of an application permission that lets it read install sessions. --> - <string name="permlab_readInstallSessions">Read install sessions</string> + <string name="permlab_readInstallSessions">read install sessions</string> <!-- Description of an application permission that lets it read install sessions. --> <string name="permdesc_readInstallSessions">Allows an application to read install sessions. This allows it to see details about active package installations.</string> <!-- Title of an application permission that lets it read install sessions. --> - <string name="permlab_requestInstallPackages">Request install packages</string> + <string name="permlab_requestInstallPackages">request install packages</string> <!-- Description of an application permission that lets it read install sessions. --> <string name="permdesc_requestInstallPackages">Allows an application to request installation of packages.</string> diff --git a/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java b/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java index 80181cf10659..4c9b00aa9a6e 100644 --- a/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java +++ b/core/tests/coretests/src/android/content/pm/AppsQueryHelperTests.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.Intent; import android.os.UserHandle; import android.test.AndroidTestCase; +import android.view.inputmethod.InputMethodInfo; import java.util.Arrays; import java.util.HashSet; @@ -77,6 +78,27 @@ public class AppsQueryHelperTests extends AndroidTestCase { assertEqualsIgnoreOrder(Arrays.asList("sys_app1", "sys_app3"), apps); } + public void testQueryAppsDefaultIme() { + // Test query default system IMEs + List<String> apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES, + true, UserHandle.of(UserHandle.myUserId())); + assertEqualsIgnoreOrder(Arrays.asList("sys_app1"), apps); + + // Test query default IMEs + apps = mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES, false, + UserHandle.of(UserHandle.myUserId())); + assertEqualsIgnoreOrder(Arrays.asList("sys_app1", "app4"), apps); + + // Test that GET_DEFAULT_IMES cannot be used with a user id different from current process + try { + mAppsQueryHelper.queryApps(AppsQueryHelper.GET_DEFAULT_IMES, false, + UserHandle.of(UserHandle.USER_NULL)); + fail("queryApps must fail if wrong user was passed"); + } catch (IllegalArgumentException e) { + // OK + } + } + private class AppsQueryHelperTestable extends AppsQueryHelper { public AppsQueryHelperTestable(Context context) { @@ -121,6 +143,21 @@ public class AppsQueryHelperTests extends AndroidTestCase { p1.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; return Arrays.asList(p1); } + + @Override + protected List<InputMethodInfo> getInputMethodList() { + final ResolveInfo sysApp1 = new ResolveInfo(); + sysApp1.serviceInfo = new ServiceInfo(); + sysApp1.serviceInfo.packageName = "sys_app1"; + sysApp1.serviceInfo.name = "name"; + InputMethodInfo imi1 = new InputMethodInfo(sysApp1, false, null, null, 0, true); + final ResolveInfo app4 = new ResolveInfo(); + app4.serviceInfo = new ServiceInfo(); + app4.serviceInfo.packageName = "app4"; + app4.serviceInfo.name = "name"; + InputMethodInfo imi2 = new InputMethodInfo(app4, false, null, null, 0, true); + return Arrays.asList(imi1, imi2); + } } private static void assertEqualsIgnoreOrder(List<String> expected, List<String> actual) { diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java index b6b4f4fa96f9..d5f632190d06 100644 --- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java +++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java @@ -561,9 +561,13 @@ public class LinkPropertiesTest extends TestCase { assertTrue(v46lp.isProvisioned()); assertEquals(ProvisioningChange.STILL_PROVISIONED, + LinkProperties.compareProvisioning(v4lp, v46lp)); + assertEquals(ProvisioningChange.STILL_PROVISIONED, LinkProperties.compareProvisioning(v6lp, v46lp)); assertEquals(ProvisioningChange.LOST_PROVISIONING, LinkProperties.compareProvisioning(v46lp, v6lp)); + assertEquals(ProvisioningChange.LOST_PROVISIONING, + LinkProperties.compareProvisioning(v46lp, v4lp)); // Check that losing and gaining a secondary router does not change // the provisioning status. diff --git a/core/tests/coretests/src/android/util/TimeUtilsTest.java b/core/tests/coretests/src/android/util/TimeUtilsTest.java index 74c8e04b8dc1..237062748f9f 100644 --- a/core/tests/coretests/src/android/util/TimeUtilsTest.java +++ b/core/tests/coretests/src/android/util/TimeUtilsTest.java @@ -436,15 +436,17 @@ public class TimeUtilsTest extends TestCase { assertFormatDuration("+100ms", 100); assertFormatDuration("+101ms", 101); assertFormatDuration("+330ms", 330); + assertFormatDuration("+1s0ms", 1000); assertFormatDuration("+1s330ms", 1330); assertFormatDuration("+10s24ms", 10024); + assertFormatDuration("+1m0s30ms", 60030); + assertFormatDuration("+1h0m0s30ms", 3600030); + assertFormatDuration("+1d0h0m0s30ms", 86400030); } public void testFormatHugeDuration() { - //assertFormatDuration("+15542d1h11m11s555ms", 1342833071555L); - // TODO: improve formatDuration() API - assertFormatDuration("+999d23h59m59s999ms", 1342833071555L); - assertFormatDuration("-999d23h59m59s999ms", -1342833071555L); + assertFormatDuration("+15542d1h11m11s555ms", 1342833071555L); + assertFormatDuration("-15542d1h11m11s555ms", -1342833071555L); } private void assertFormatDuration(String expected, long duration) { diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java new file mode 100644 index 000000000000..c5e2ae67affc --- /dev/null +++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 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 android.widget; + +import static android.widget.espresso.TextViewActions.mouseDragOnText; +import static android.widget.espresso.TextViewAssertions.hasSelection; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.typeTextIntoFocusedView; +import static android.support.test.espresso.matcher.ViewMatchers.withId; + +import com.android.frameworks.coretests.R; + +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.SmallTest; + +/** + * Tests mouse interaction of the TextView widget from an Activity + */ +public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2<TextViewActivity>{ + + public TextViewActivityMouseTest() { + super(TextViewActivity.class); + } + + @SmallTest + public void testSelectTextByDrag() throws Exception { + getActivity(); + + final String helloWorld = "Hello world!"; + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld)); + onView(withId(R.id.textview)).perform( + mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!"))); + + onView(withId(R.id.textview)).check(hasSelection("llo wor")); + } + + @SmallTest + public void testSelectTextByDrag_reverse() throws Exception { + getActivity(); + + final String helloWorld = "Hello world!"; + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld)); + onView(withId(R.id.textview)).perform( + mouseDragOnText( helloWorld.indexOf("ld!"), helloWorld.indexOf("llo"))); + + onView(withId(R.id.textview)).check(hasSelection("llo wor")); + } +} diff --git a/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java b/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java index a0cd848a53f5..9ff8e8219f16 100644 --- a/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java +++ b/core/tests/coretests/src/android/widget/espresso/DragOnTextViewActions.java @@ -20,25 +20,22 @@ import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFro import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; import static com.android.internal.util.Preconditions.checkNotNull; import static org.hamcrest.Matchers.allOf; - import android.annotation.Nullable; import android.os.SystemClock; import android.support.test.espresso.UiController; import android.support.test.espresso.PerformException; import android.support.test.espresso.ViewAction; import android.support.test.espresso.action.CoordinatesProvider; -import android.support.test.espresso.action.GeneralClickAction; import android.support.test.espresso.action.MotionEvents; import android.support.test.espresso.action.PrecisionDescriber; -import android.support.test.espresso.action.Press; import android.support.test.espresso.action.Swiper; -import android.support.test.espresso.action.Tap; import android.support.test.espresso.util.HumanReadables; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.widget.TextView; + import org.hamcrest.Matcher; @@ -51,11 +48,48 @@ import org.hamcrest.Matcher; * <ul> */ public final class DragOnTextViewActions implements ViewAction { + public interface Dragger extends Swiper { + UiController wrapUiController(UiController uiController); + } /** * Executes different "drag on text" types to given positions. */ - public enum Drag implements Swiper { + public enum Drag implements Dragger { + + /** + * Starts a drag with a mouse down. + */ + MOUSE_DOWN { + private DownMotionPerformer downMotion = new DownMotionPerformer() { + @Override + public MotionEvent perform( + UiController uiController, float[] coordinates, float[] precision) { + MotionEvent downEvent = MotionEvents.sendDown( + uiController, coordinates, precision) + .down; + return downEvent; + } + }; + + @Override + public Status sendSwipe( + UiController uiController, + float[] startCoordinates, float[] endCoordinates, float[] precision) { + return sendLinearDrag( + uiController, downMotion, startCoordinates, endCoordinates, precision); + } + + @Override + public String toString() { + return "mouse down and drag to select"; + } + + @Override + public UiController wrapUiController(UiController uiController) { + return new MouseUiController(uiController); + } + }, /** * Starts a drag with a long-press. @@ -197,6 +231,11 @@ public final class DragOnTextViewActions implements ViewAction { return res; } + + @Override + public UiController wrapUiController(UiController uiController) { + return uiController; + } } /** @@ -215,13 +254,13 @@ public final class DragOnTextViewActions implements ViewAction { MotionEvent perform(UiController uiController, float[] coordinates, float[] precision); } - private final Swiper mDragger; + private final Dragger mDragger; private final CoordinatesProvider mStartCoordinatesProvider; private final CoordinatesProvider mEndCoordinatesProvider; private final PrecisionDescriber mPrecisionDescriber; public DragOnTextViewActions( - Swiper dragger, + Dragger dragger, CoordinatesProvider startCoordinatesProvider, CoordinatesProvider endCoordinatesProvider, PrecisionDescriber precisionDescriber) { @@ -242,6 +281,8 @@ public final class DragOnTextViewActions implements ViewAction { checkNotNull(uiController); checkNotNull(view); + uiController = mDragger.wrapUiController(uiController); + float[] startCoordinates = mStartCoordinatesProvider.calculateCoordinates(view); float[] endCoordinates = mEndCoordinatesProvider.calculateCoordinates(view); float[] precision = mPrecisionDescriber.describePrecision(); diff --git a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java new file mode 100644 index 000000000000..f1387f801b01 --- /dev/null +++ b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 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 android.widget.espresso; + +import android.support.test.espresso.InjectEventSecurityException; +import android.support.test.espresso.UiController; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; + +/** + * Class to wrap an UiController to overwrite source of motion events to SOURCE_MOUSE. + * Note that this doesn't change the tool type. + */ +public class MouseUiController implements UiController { + private final UiController mUiController; + + public MouseUiController(UiController uiController) { + mUiController = uiController; + } + + @Override + public boolean injectKeyEvent(KeyEvent event) throws InjectEventSecurityException { + return mUiController.injectKeyEvent(event); + } + + @Override + public boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException { + // Modify the event to mimic mouse primary button event. + event.setSource(InputDevice.SOURCE_MOUSE); + event.setButtonState(MotionEvent.BUTTON_PRIMARY); + return mUiController.injectMotionEvent(event); + } + + @Override + public boolean injectString(String str) throws InjectEventSecurityException { + return mUiController.injectString(str); + } + + @Override + public void loopMainThreadForAtLeast(long millisDelay) { + mUiController.loopMainThreadForAtLeast(millisDelay); + } + + @Override + public void loopMainThreadUntilIdle() { + mUiController.loopMainThreadUntilIdle(); + } +} diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java index 835b1b958860..4f5a72b14b2a 100644 --- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java +++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java @@ -124,6 +124,27 @@ public final class TextViewActions { } /** + * Returns an action that click then drags by mouse on text from startIndex to endIndex on the + * TextView.<br> + * <br> + * View constraints: + * <ul> + * <li>must be a TextView displayed on screen + * <ul> + * + * @param startIndex The index of the TextView's text to start a drag from + * @param endIndex The index of the TextView's text to end the drag at + */ + public static ViewAction mouseDragOnText(int startIndex, int endIndex) { + return actionWithAssertions( + new DragOnTextViewActions( + DragOnTextViewActions.Drag.MOUSE_DOWN, + new TextCoordinates(startIndex), + new TextCoordinates(endIndex), + Press.PINPOINT)); + } + + /** * A provider of the x, y coordinates of the text at the specified index in a text view. */ private static final class TextCoordinates implements CoordinatesProvider { diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java index 3bd17fa6b8f3..abcccbdbc9fb 100644 --- a/graphics/java/android/graphics/Point.java +++ b/graphics/java/android/graphics/Point.java @@ -99,7 +99,7 @@ public class Point implements Parcelable { /** @hide */ public void printShortString(PrintWriter pw) { - pw.println("["); pw.print(x); pw.print(","); pw.print(y); pw.print("]"); + pw.print("["); pw.print(x); pw.print(","); pw.print(y); pw.print("]"); } /** diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java index 4fc5edee94c6..971a3a295ca2 100644 --- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java @@ -50,7 +50,7 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl * Creates a new animated rotating drawable with no wrapped drawable. */ public AnimatedRotateDrawable() { - this(new AnimatedRotateState(null), null); + this(new AnimatedRotateState(null, null), null); } @Override @@ -126,17 +126,43 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedRotateDrawable); - super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible); + + // Inflation will advance the XmlPullParser and AttributeSet. + super.inflate(r, parser, attrs, theme); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); updateLocalState(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + @Override + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); + + final AnimatedRotateState state = mState; + if (state == null) { + return; + } + + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes( + state.mThemeAttrs, R.styleable.AnimatedRotateDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); + } + } + + updateLocalState(); + } + + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { // If we're not waiting on a theme, verify required attributes. if (getDrawable() == null && (mState.mThemeAttrs == null || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) { @@ -146,11 +172,17 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl } } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); - + private void updateStateFromTypedArray(@NonNull TypedArray a) { final AnimatedRotateState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) { final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX); @@ -168,38 +200,6 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl R.styleable.AnimatedRotateDrawable_framesCount, state.mFramesCount)); setFramesDuration(a.getInt( R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration)); - - final Drawable dr = a.getDrawable(R.styleable.AnimatedRotateDrawable_drawable); - if (dr != null) { - setDrawable(dr); - } - } - - @Override - public void applyTheme(@Nullable Theme t) { - final AnimatedRotateState state = mState; - if (state == null) { - return; - } - - if (state.mThemeAttrs != null) { - final TypedArray a = t.resolveAttributes( - state.mThemeAttrs, R.styleable.AnimatedRotateDrawable); - try { - updateStateFromTypedArray(a); - verifyRequiredAttributes(a); - } catch (XmlPullParserException e) { - throw new RuntimeException(e); - } finally { - a.recycle(); - } - } - - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); - - updateLocalState(); } public void setFramesCount(int framesCount) { @@ -211,7 +211,15 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl mState.mFrameDuration = framesDuration; } + @Override + DrawableWrapperState mutateConstantState() { + mState = new AnimatedRotateState(mState, null); + return mState; + } + static final class AnimatedRotateState extends DrawableWrapper.DrawableWrapperState { + private int[] mThemeAttrs; + boolean mPivotXRel = false; float mPivotX = 0; boolean mPivotYRel = false; @@ -219,8 +227,8 @@ public class AnimatedRotateDrawable extends DrawableWrapper implements Animatabl int mFrameDuration = 150; int mFramesCount = 12; - public AnimatedRotateState(AnimatedRotateState orig) { - super(orig); + public AnimatedRotateState(AnimatedRotateState orig, Resources res) { + super(orig, res); if (orig != null) { mPivotXRel = orig.mPivotXRel; diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java index 31fccd0880c9..cdd336dded0a 100644 --- a/graphics/java/android/graphics/drawable/ClipDrawable.java +++ b/graphics/java/android/graphics/drawable/ClipDrawable.java @@ -21,6 +21,8 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; @@ -59,7 +61,7 @@ public class ClipDrawable extends DrawableWrapper { private ClipState mState; ClipDrawable() { - this(new ClipState(null), null); + this(new ClipState(null, null), null); } /** @@ -72,7 +74,7 @@ public class ClipDrawable extends DrawableWrapper { * {@link #VERTICAL} */ public ClipDrawable(Drawable drawable, int gravity, int orientation) { - this(new ClipState(null), null); + this(new ClipState(null, null), null); mState.mGravity = gravity; mState.mOrientation = orientation; @@ -81,45 +83,23 @@ public class ClipDrawable extends DrawableWrapper { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable); + + // Inflation will advance the XmlPullParser and AttributeSet. super.inflate(r, parser, attrs, theme); - final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { - // If we're not waiting on a theme, verify required attributes. - if (getDrawable() == null && (mState.mThemeAttrs == null - || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) { - throw new XmlPullParserException(a.getPositionDescription() - + ": <clip> tag requires a 'drawable' attribute or " - + "child tag defining a drawable"); - } - } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); - - final ClipState state = mState; - state.mOrientation = a.getInt( - R.styleable.ClipDrawable_clipOrientation, state.mOrientation); - state.mGravity = a.getInt( - R.styleable.ClipDrawable_gravity, state.mGravity); - - final Drawable dr = a.getDrawable(R.styleable.ClipDrawable_drawable); - if (dr != null) { - setDrawable(dr); - } - } + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); - @Override - public void applyTheme(Theme t) { final ClipState state = mState; if (state == null) { return; @@ -136,10 +116,34 @@ public class ClipDrawable extends DrawableWrapper { a.recycle(); } } + } - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (getDrawable() == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <clip> tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + } + + private void updateStateFromTypedArray(@NonNull TypedArray a) { + final ClipState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + state.mOrientation = a.getInt( + R.styleable.ClipDrawable_clipOrientation, state.mOrientation); + state.mGravity = a.getInt( + R.styleable.ClipDrawable_gravity, state.mGravity); } @Override @@ -200,12 +204,20 @@ public class ClipDrawable extends DrawableWrapper { } } + @Override + DrawableWrapperState mutateConstantState() { + mState = new ClipState(mState, null); + return mState; + } + static final class ClipState extends DrawableWrapper.DrawableWrapperState { + private int[] mThemeAttrs; + int mOrientation = HORIZONTAL; int mGravity = Gravity.LEFT; - ClipState(ClipState orig) { - super(orig); + ClipState(ClipState orig, Resources res) { + super(orig, res); if (orig != null) { mOrientation = orig.mOrientation; diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index b95c183642ca..ff287776e1a3 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -55,6 +55,8 @@ import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Collection; +import com.android.internal.R; + /** * A Drawable is a general abstraction for "something that can be drawn." Most * often you will deal with Drawable as the type of resource retrieved for @@ -791,8 +793,10 @@ public abstract class Drawable { /** * Applies the specified theme to this Drawable and its children. + * + * @param t the theme to apply */ - public void applyTheme(@SuppressWarnings("unused") Theme t) { + public void applyTheme(@NonNull @SuppressWarnings("unused") Theme t) { } public boolean canApplyTheme() { @@ -1177,8 +1181,8 @@ public abstract class Drawable { * * @see #inflate(Resources, XmlPullParser, AttributeSet, Theme) */ - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) - throws XmlPullParserException, IOException { + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs) throws XmlPullParserException, IOException { inflate(r, parser, attrs, null); } @@ -1192,17 +1196,11 @@ public abstract class Drawable { * @throws XmlPullParserException * @throws IOException */ - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { - final TypedArray a; - if (theme != null) { - a = theme.obtainStyledAttributes( - attrs, com.android.internal.R.styleable.Drawable, 0, 0); - } else { - a = r.obtainAttributes(attrs, com.android.internal.R.styleable.Drawable); - } - - inflateWithAttributes(r, parser, a, com.android.internal.R.styleable.Drawable_visible); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.Drawable); + mVisible = a.getBoolean(R.styleable.Drawable_visible, mVisible); a.recycle(); } @@ -1212,8 +1210,8 @@ public abstract class Drawable { * @throws XmlPullParserException * @throws IOException */ - void inflateWithAttributes(Resources r, XmlPullParser parser, TypedArray attrs, int visibleAttr) - throws XmlPullParserException, IOException { + void inflateWithAttributes(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull TypedArray attrs, int visibleAttr) throws XmlPullParserException, IOException { mVisible = attrs.getBoolean(visibleAttr, mVisible); } diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java index 9185e1a06466..c42787000c49 100644 --- a/graphics/java/android/graphics/drawable/DrawableWrapper.java +++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java @@ -16,6 +16,8 @@ package android.graphics.drawable; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -23,6 +25,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.ColorStateList; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -33,6 +36,7 @@ import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.Rect; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.view.View; import java.io.IOException; @@ -112,32 +116,79 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb return mDrawable; } - void updateStateFromTypedArray(TypedArray a) { + @Override + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + super.inflate(r, parser, attrs, theme); + final DrawableWrapperState state = mState; if (state == null) { return; } - // Account for any configuration changes. - state.mChangingConfigurations |= a.getChangingConfigurations(); + // The density may have changed since the last update. This will + // apply scaling to any existing constant state properties. + final int densityDpi = r.getDisplayMetrics().densityDpi; + final int targetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + state.setDensity(targetDensity); - // Extract the theme attributes, if any. - state.mThemeAttrs = a.extractThemeAttrs(); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.DrawableWrapper); + updateStateFromTypedArray(a); + a.recycle(); - // TODO: Consider using R.styleable.DrawableWrapper_drawable + inflateChildDrawable(r, parser, attrs, theme); } @Override - public void applyTheme(Resources.Theme t) { + public void applyTheme(@NonNull Theme t) { super.applyTheme(t); + // If we load the drawable later as part of updating from the typed + // array, it will already be themed correctly. So, we can theme the + // local drawable first. + if (mDrawable != null && mDrawable.canApplyTheme()) { + mDrawable.applyTheme(t); + } + final DrawableWrapperState state = mState; if (state == null) { return; } - if (mDrawable != null && mDrawable.canApplyTheme()) { - mDrawable.applyTheme(t); + final int densityDpi = t.getResources().getDisplayMetrics().densityDpi; + final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + state.setDensity(density); + + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes( + state.mThemeAttrs, R.styleable.DrawableWrapper); + updateStateFromTypedArray(a); + a.recycle(); + } + } + + /** + * Updates constant state properties from the provided typed array. + * <p> + * Implementing subclasses should call through to the super method first. + * + * @param a the typed array rom which properties should be read + */ + private void updateStateFromTypedArray(@NonNull TypedArray a) { + final DrawableWrapperState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + if (a.hasValueOrEmpty(R.styleable.DrawableWrapper_drawable)) { + setDrawable(a.getDrawable(R.styleable.DrawableWrapper_drawable)); } } @@ -371,8 +422,9 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb * child element will take precedence over any other child elements or * explicit drawable attribute. */ - void inflateChildDrawable(Resources r, XmlPullParser parser, AttributeSet attrs, - Resources.Theme theme) throws XmlPullParserException, IOException { + private void inflateChildDrawable(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { // Seek to the first child element. Drawable dr = null; int type; @@ -390,17 +442,61 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb } abstract static class DrawableWrapperState extends Drawable.ConstantState { - int[] mThemeAttrs; + private int[] mThemeAttrs; + int mChangingConfigurations; + int mDensity = DisplayMetrics.DENSITY_DEFAULT; Drawable.ConstantState mDrawableState; - DrawableWrapperState(DrawableWrapperState orig) { + DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) { if (orig != null) { mThemeAttrs = orig.mThemeAttrs; mChangingConfigurations = orig.mChangingConfigurations; mDrawableState = orig.mDrawableState; } + + final int density; + if (res != null) { + density = res.getDisplayMetrics().densityDpi; + } else if (orig != null) { + density = orig.mDensity; + } else { + density = 0; + } + + mDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; + } + + /** + * Sets the constant state density. + * <p> + * If the density has been previously set, dispatches the change to + * subclasses so that density-dependent properties may be scaled as + * necessary. + * + * @param targetDensity the new constant state density + */ + public final void setDensity(int targetDensity) { + if (mDensity != targetDensity) { + final int sourceDensity = mDensity; + mDensity = targetDensity; + + onDensityChanged(sourceDensity, targetDensity); + } + } + + /** + * Called when the constant state density changes. + * <p> + * Subclasses with density-dependent constant state properties should + * override this method and scale their properties as necessary. + * + * @param sourceDensity the previous constant state density + * @param targetDensity the new constant state density + */ + void onDensityChanged(int sourceDensity, int targetDensity) { + // Stub method. } @Override @@ -425,7 +521,7 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb } @Override - public abstract Drawable newDrawable(Resources res); + public abstract Drawable newDrawable(@Nullable Resources res); @Override public int getChangingConfigurations() { diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java index e1ebdbb65a26..927b9c9d9f6c 100644 --- a/graphics/java/android/graphics/drawable/InsetDrawable.java +++ b/graphics/java/android/graphics/drawable/InsetDrawable.java @@ -22,14 +22,17 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Outline; import android.graphics.PixelFormat; import android.graphics.Rect; import android.util.AttributeSet; +import android.util.DisplayMetrics; import java.io.IOException; @@ -58,7 +61,7 @@ public class InsetDrawable extends DrawableWrapper { * No-arg constructor used by drawable inflation. */ InsetDrawable() { - this(new InsetState(null), null); + this(new InsetState(null, null), null); } /** @@ -67,7 +70,7 @@ public class InsetDrawable extends DrawableWrapper { * @param drawable The drawable to inset. * @param inset Inset in pixels around the drawable. */ - public InsetDrawable(Drawable drawable, int inset) { + public InsetDrawable(@Nullable Drawable drawable, int inset) { this(drawable, inset, inset, inset, inset); } @@ -80,9 +83,9 @@ public class InsetDrawable extends DrawableWrapper { * @param insetRight Right inset in pixels. * @param insetBottom Bottom inset in pixels. */ - public InsetDrawable(Drawable drawable, int insetLeft, int insetTop,int insetRight, - int insetBottom) { - this(new InsetState(null), null); + public InsetDrawable(@Nullable Drawable drawable, int insetLeft, int insetTop, + int insetRight, int insetBottom) { + this(new InsetState(null, null), null); mState.mInsetLeft = insetLeft; mState.mInsetTop = insetTop; @@ -93,69 +96,23 @@ public class InsetDrawable extends DrawableWrapper { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable); + + // Inflation will advance the XmlPullParser and AttributeSet. super.inflate(r, parser, attrs, theme); - final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { - // If we're not waiting on a theme, verify required attributes. - if (getDrawable() == null && (mState.mThemeAttrs == null - || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) { - throw new XmlPullParserException(a.getPositionDescription() - + ": <inset> tag requires a 'drawable' attribute or " - + "child tag defining a drawable"); - } - } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); - - final InsetState state = mState; - final int N = a.getIndexCount(); - for (int i = 0; i < N; i++) { - final int attr = a.getIndex(i); - switch (attr) { - case R.styleable.InsetDrawable_drawable: - final Drawable dr = a.getDrawable(attr); - if (dr != null) { - setDrawable(dr); - } - break; - case R.styleable.InsetDrawable_inset: - final int inset = a.getDimensionPixelOffset(attr, Integer.MIN_VALUE); - if (inset != Integer.MIN_VALUE) { - state.mInsetLeft = inset; - state.mInsetTop = inset; - state.mInsetRight = inset; - state.mInsetBottom = inset; - } - break; - case R.styleable.InsetDrawable_insetLeft: - state.mInsetLeft = a.getDimensionPixelOffset(attr, state.mInsetLeft); - break; - case R.styleable.InsetDrawable_insetTop: - state.mInsetTop = a.getDimensionPixelOffset(attr, state.mInsetTop); - break; - case R.styleable.InsetDrawable_insetRight: - state.mInsetRight = a.getDimensionPixelOffset(attr, state.mInsetRight); - break; - case R.styleable.InsetDrawable_insetBottom: - state.mInsetBottom = a.getDimensionPixelOffset(attr, state.mInsetBottom); - break; - } - } - } + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); - @Override - public void applyTheme(Theme t) { final InsetState state = mState; if (state == null) { return; @@ -172,10 +129,47 @@ public class InsetDrawable extends DrawableWrapper { a.recycle(); } } + } - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { + // If we're not waiting on a theme, verify required attributes. + if (getDrawable() == null && (mState.mThemeAttrs == null + || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <inset> tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + } + + private void updateStateFromTypedArray(@NonNull TypedArray a) { + final InsetState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + // Inset attribute may be overridden by more specific attributes. + if (a.hasValue(R.styleable.InsetDrawable_inset)) { + final int inset = a.getDimensionPixelOffset(R.styleable.InsetDrawable_inset, 0); + state.mInsetLeft = inset; + state.mInsetTop = inset; + state.mInsetRight = inset; + state.mInsetBottom = inset; + } + + state.mInsetLeft = a.getDimensionPixelOffset( + R.styleable.InsetDrawable_insetLeft, state.mInsetLeft); + state.mInsetRight = a.getDimensionPixelOffset( + R.styleable.InsetDrawable_insetRight, state.mInsetRight); + state.mInsetTop = a.getDimensionPixelOffset( + R.styleable.InsetDrawable_insetTop, state.mInsetTop); + state.mInsetBottom = a.getDimensionPixelOffset( + R.styleable.InsetDrawable_insetBottom, state.mInsetBottom); } @Override @@ -243,30 +237,72 @@ public class InsetDrawable extends DrawableWrapper { @Override DrawableWrapperState mutateConstantState() { - mState = new InsetState(mState); + mState = new InsetState(mState, null); return mState; } static final class InsetState extends DrawableWrapper.DrawableWrapperState { + private int[] mThemeAttrs; + int mInsetLeft = 0; int mInsetTop = 0; int mInsetRight = 0; int mInsetBottom = 0; - InsetState(InsetState orig) { - super(orig); + InsetState(@Nullable InsetState orig, @Nullable Resources res) { + super(orig, res); if (orig != null) { mInsetLeft = orig.mInsetLeft; mInsetTop = orig.mInsetTop; mInsetRight = orig.mInsetRight; mInsetBottom = orig.mInsetBottom; + + if (orig.mDensity != mDensity) { + applyDensityScaling(orig.mDensity, mDensity); + } } } @Override - public Drawable newDrawable(Resources res) { - return new InsetDrawable(this, res); + void onDensityChanged(int sourceDensity, int targetDensity) { + super.onDensityChanged(sourceDensity, targetDensity); + + applyDensityScaling(sourceDensity, targetDensity); + } + + /** + * Called when the constant state density changes to scale + * density-dependent properties specific to insets. + * + * @param sourceDensity the previous constant state density + * @param targetDensity the new constant state density + */ + private void applyDensityScaling(int sourceDensity, int targetDensity) { + mInsetLeft = Bitmap.scaleFromDensity(mInsetLeft, sourceDensity, targetDensity); + mInsetTop = Bitmap.scaleFromDensity(mInsetTop, sourceDensity, targetDensity); + mInsetRight = Bitmap.scaleFromDensity(mInsetRight, sourceDensity, targetDensity); + mInsetBottom = Bitmap.scaleFromDensity(mInsetBottom, sourceDensity, targetDensity); + } + + @Override + public Drawable newDrawable(@Nullable Resources res) { + // If this drawable is being created for a different density, + // just create a new constant state and call it a day. + final InsetState state; + if (res != null) { + final int densityDpi = res.getDisplayMetrics().densityDpi; + final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + if (density != mDensity) { + state = new InsetState(this, res); + } else { + state = this; + } + } else { + state = this; + } + + return new InsetDrawable(state, res); } } @@ -274,7 +310,7 @@ public class InsetDrawable extends DrawableWrapper { * The one constructor to rule them all. This is called by all public * constructors to set the state and initialize local properties. */ - private InsetDrawable(InsetState state, Resources res) { + private InsetDrawable(@NonNull InsetState state, @Nullable Resources res) { super(state, res); mState = state; diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 1a0ba6f2eabc..4368fe99030a 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -30,6 +30,7 @@ import android.graphics.PixelFormat; import android.graphics.PorterDuff.Mode; import android.graphics.Rect; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.util.LayoutDirection; import android.view.Gravity; import android.view.View; @@ -124,7 +125,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { final int length = layers.length; final ChildDrawable[] r = new ChildDrawable[length]; for (int i = 0; i < length; i++) { - r[i] = new ChildDrawable(); + r[i] = new ChildDrawable(mLayerState.mDensity); r[i].mDrawable = layers[i]; layers[i].setCallback(this); mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations(); @@ -140,6 +141,10 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { this((LayerState) null, null); } + /** + * The one constructor to rule them all. This is called by all public + * constructors to set the state and initialize local properties. + */ LayerDrawable(@Nullable LayerState state, @Nullable Resources res) { mLayerState = createConstantState(state, res); if (mLayerState.mNum > 0) { @@ -153,63 +158,78 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { super.inflate(r, parser, attrs, theme); + final LayerState state = mLayerState; + if (state == null) { + return; + } + + // The density may have changed since the last update. This will + // apply scaling to any existing constant state properties. + final int densityDpi = r.getDisplayMetrics().densityDpi; + final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + state.setDensity(density); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable); updateStateFromTypedArray(a); a.recycle(); + final ChildDrawable[] array = state.mChildren; + final int N = state.mNum; + for (int i = 0; i < N; i++) { + final ChildDrawable layer = array[i]; + layer.setDensity(density); + } + inflateLayers(r, parser, attrs, theme); ensurePadding(); refreshPadding(); } - /** - * Initializes the constant state from the values in the typed array. - */ - private void updateStateFromTypedArray(TypedArray a) { + @Override + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); + final LayerState state = mLayerState; + if (state == null) { + return; + } - // Account for any configuration changes. - state.mChangingConfigurations |= a.getChangingConfigurations(); + final int densityDpi = t.getResources().getDisplayMetrics().densityDpi; + final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + state.setDensity(density); - // Extract the theme attributes, if any. - state.mThemeAttrs = a.extractThemeAttrs(); + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes( + state.mThemeAttrs, R.styleable.LayerDrawable); + updateStateFromTypedArray(a); + a.recycle(); + } - final int N = a.getIndexCount(); + final ChildDrawable[] array = state.mChildren; + final int N = state.mNum; for (int i = 0; i < N; i++) { - int attr = a.getIndex(i); - switch (attr) { - case R.styleable.LayerDrawable_opacity: - state.mOpacityOverride = a.getInt(attr, state.mOpacityOverride); - break; - case R.styleable.LayerDrawable_paddingTop: - state.mPaddingTop = a.getDimensionPixelOffset(attr, state.mPaddingTop); - break; - case R.styleable.LayerDrawable_paddingBottom: - state.mPaddingBottom = a.getDimensionPixelOffset(attr, state.mPaddingBottom); - break; - case R.styleable.LayerDrawable_paddingLeft: - state.mPaddingLeft = a.getDimensionPixelOffset(attr, state.mPaddingLeft); - break; - case R.styleable.LayerDrawable_paddingRight: - state.mPaddingRight = a.getDimensionPixelOffset(attr, state.mPaddingRight); - break; - case R.styleable.LayerDrawable_paddingStart: - state.mPaddingStart = a.getDimensionPixelOffset(attr, state.mPaddingStart); - break; - case R.styleable.LayerDrawable_paddingEnd: - state.mPaddingEnd = a.getDimensionPixelOffset(attr, state.mPaddingEnd); - break; - case R.styleable.LayerDrawable_autoMirrored: - state.mAutoMirrored = a.getBoolean(attr, state.mAutoMirrored); - break; - case R.styleable.LayerDrawable_paddingMode: - state.mPaddingMode = a.getInteger(attr, state.mPaddingMode); - break; + final ChildDrawable layer = array[i]; + layer.setDensity(density); + + if (layer.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes( + layer.mThemeAttrs, R.styleable.LayerDrawableItem); + updateLayerFromTypedArray(layer, a); + a.recycle(); + } + + final Drawable d = layer.mDrawable; + if (d != null && d.canApplyTheme()) { + d.applyTheme(t); + + // Update cached mask of child changing configurations. + state.mChildrenChangingConfigurations |= d.getChangingConfigurations(); } } } @@ -217,7 +237,8 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { /** * Inflates child layers using the specified parser. */ - private void inflateLayers(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + private void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { final LayerState state = mLayerState; @@ -234,7 +255,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { continue; } - final ChildDrawable layer = new ChildDrawable(); + final ChildDrawable layer = new ChildDrawable(state.mDensity); final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem); updateLayerFromTypedArray(layer, a); a.recycle(); @@ -264,77 +285,103 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } } - private void updateLayerFromTypedArray(ChildDrawable layer, TypedArray a) { + /** + * Initializes the constant state from the values in the typed array. + */ + private void updateStateFromTypedArray(@NonNull TypedArray a) { final LayerState state = mLayerState; // Account for any configuration changes. - state.mChildrenChangingConfigurations |= a.getChangingConfigurations(); + state.mChangingConfigurations |= a.getChangingConfigurations(); // Extract the theme attributes, if any. - layer.mThemeAttrs = a.extractThemeAttrs(); - - layer.mInsetL = a.getDimensionPixelOffset( - R.styleable.LayerDrawableItem_left, layer.mInsetL); - layer.mInsetT = a.getDimensionPixelOffset( - R.styleable.LayerDrawableItem_top, layer.mInsetT); - layer.mInsetR = a.getDimensionPixelOffset( - R.styleable.LayerDrawableItem_right, layer.mInsetR); - layer.mInsetB = a.getDimensionPixelOffset( - R.styleable.LayerDrawableItem_bottom, layer.mInsetB); - layer.mInsetS = a.getDimensionPixelOffset( - R.styleable.LayerDrawableItem_start, layer.mInsetS); - layer.mInsetE = a.getDimensionPixelOffset( - R.styleable.LayerDrawableItem_end, layer.mInsetE); - layer.mWidth = a.getDimensionPixelSize( - R.styleable.LayerDrawableItem_width, layer.mWidth); - layer.mHeight = a.getDimensionPixelSize( - R.styleable.LayerDrawableItem_height, layer.mHeight); - layer.mGravity = a.getInteger( - R.styleable.LayerDrawableItem_gravity, layer.mGravity); - layer.mId = a.getResourceId(R.styleable.LayerDrawableItem_id, layer.mId); + state.mThemeAttrs = a.extractThemeAttrs(); - final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable); - if (dr != null) { - layer.mDrawable = dr; + final int N = a.getIndexCount(); + for (int i = 0; i < N; i++) { + final int attr = a.getIndex(i); + switch (attr) { + case R.styleable.LayerDrawable_opacity: + state.mOpacityOverride = a.getInt(attr, state.mOpacityOverride); + break; + case R.styleable.LayerDrawable_paddingTop: + state.mPaddingTop = a.getDimensionPixelOffset(attr, state.mPaddingTop); + break; + case R.styleable.LayerDrawable_paddingBottom: + state.mPaddingBottom = a.getDimensionPixelOffset(attr, state.mPaddingBottom); + break; + case R.styleable.LayerDrawable_paddingLeft: + state.mPaddingLeft = a.getDimensionPixelOffset(attr, state.mPaddingLeft); + break; + case R.styleable.LayerDrawable_paddingRight: + state.mPaddingRight = a.getDimensionPixelOffset(attr, state.mPaddingRight); + break; + case R.styleable.LayerDrawable_paddingStart: + state.mPaddingStart = a.getDimensionPixelOffset(attr, state.mPaddingStart); + break; + case R.styleable.LayerDrawable_paddingEnd: + state.mPaddingEnd = a.getDimensionPixelOffset(attr, state.mPaddingEnd); + break; + case R.styleable.LayerDrawable_autoMirrored: + state.mAutoMirrored = a.getBoolean(attr, state.mAutoMirrored); + break; + case R.styleable.LayerDrawable_paddingMode: + state.mPaddingMode = a.getInteger(attr, state.mPaddingMode); + break; + } } } - @Override - public void applyTheme(Theme t) { - super.applyTheme(t); - + private void updateLayerFromTypedArray(@NonNull ChildDrawable layer, @NonNull TypedArray a) { final LayerState state = mLayerState; - if (state == null) { - return; - } - - if (state.mThemeAttrs != null) { - final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.LayerDrawable); - updateStateFromTypedArray(a); - a.recycle(); - } - final ChildDrawable[] array = state.mChildren; - final int N = state.mNum; - for (int i = 0; i < N; i++) { - final ChildDrawable layer = array[i]; - if (layer.mThemeAttrs != null) { - final TypedArray a = t.resolveAttributes(layer.mThemeAttrs, - R.styleable.LayerDrawableItem); - updateLayerFromTypedArray(layer, a); - a.recycle(); - } + // Account for any configuration changes. + state.mChildrenChangingConfigurations |= a.getChangingConfigurations(); - final Drawable d = layer.mDrawable; - if (d != null && d.canApplyTheme()) { - d.applyTheme(t); + // Extract the theme attributes, if any. + layer.mThemeAttrs = a.extractThemeAttrs(); - // Update cached mask of child changing configurations. - state.mChildrenChangingConfigurations |= d.getChangingConfigurations(); + final int N = a.getIndexCount(); + for (int i = 0; i < N; i++) { + final int attr = a.getIndex(i); + switch (attr) { + case R.styleable.LayerDrawableItem_left: + layer.mInsetL = a.getDimensionPixelOffset(attr, layer.mInsetL); + break; + case R.styleable.LayerDrawableItem_top: + layer.mInsetT = a.getDimensionPixelOffset(attr, layer.mInsetT); + break; + case R.styleable.LayerDrawableItem_right: + layer.mInsetR = a.getDimensionPixelOffset(attr, layer.mInsetR); + break; + case R.styleable.LayerDrawableItem_bottom: + layer.mInsetB = a.getDimensionPixelOffset(attr, layer.mInsetB); + break; + case R.styleable.LayerDrawableItem_start: + layer.mInsetS = a.getDimensionPixelOffset(attr, layer.mInsetS); + break; + case R.styleable.LayerDrawableItem_end: + layer.mInsetE = a.getDimensionPixelOffset(attr, layer.mInsetE); + break; + case R.styleable.LayerDrawableItem_width: + layer.mWidth = a.getDimensionPixelSize(attr, layer.mWidth); + break; + case R.styleable.LayerDrawableItem_height: + layer.mHeight = a.getDimensionPixelSize(attr, layer.mHeight); + break; + case R.styleable.LayerDrawableItem_gravity: + layer.mGravity = a.getInteger(attr, layer.mGravity); + break; + case R.styleable.LayerDrawableItem_id: + layer.mId = a.getResourceId(attr, layer.mId); + break; } } - ensurePadding(); + final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable); + if (dr != null) { + layer.mDrawable = dr; + } } @Override @@ -368,7 +415,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { * @param layer The layer to add. * @return The index of the layer. */ - int addLayer(ChildDrawable layer) { + int addLayer(@NonNull ChildDrawable layer) { final LayerState st = mLayerState; final int N = st.mChildren != null ? st.mChildren.length : 0; final int i = st.mNum; @@ -418,7 +465,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } private ChildDrawable createLayer(Drawable dr) { - final ChildDrawable layer = new ChildDrawable(); + final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity); layer.mDrawable = dr; return layer; } @@ -1708,6 +1755,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { static class ChildDrawable { public Drawable mDrawable; public int[] mThemeAttrs; + public int mDensity = DisplayMetrics.DENSITY_DEFAULT; public int mInsetL, mInsetT, mInsetR, mInsetB; public int mInsetS = UNDEFINED_INSET; public int mInsetE = UNDEFINED_INSET; @@ -1716,11 +1764,12 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { public int mGravity = Gravity.NO_GRAVITY; public int mId = View.NO_ID; - ChildDrawable() { - // Default empty constructor. + ChildDrawable(int density) { + mDensity = density; } - ChildDrawable(ChildDrawable orig, LayerDrawable owner, Resources res) { + ChildDrawable(@NonNull ChildDrawable orig, @NonNull LayerDrawable owner, + @Nullable Resources res) { final Drawable dr = orig.mDrawable; final Drawable clone; if (dr != null) { @@ -1750,19 +1799,58 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { mHeight = orig.mHeight; mGravity = orig.mGravity; mId = orig.mId; + + final int densityDpi = res == null ? orig.mDensity : res.getDisplayMetrics().densityDpi; + mDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + + if (orig.mDensity != mDensity) { + applyDensityScaling(orig.mDensity, mDensity); + } } public boolean canApplyTheme() { return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme()); } + + public final void setDensity(int targetDensity) { + if (mDensity != targetDensity) { + final int sourceDensity = mDensity; + mDensity = targetDensity; + + applyDensityScaling(sourceDensity, targetDensity); + } + } + + private void applyDensityScaling(int sourceDensity, int targetDensity) { + mInsetL = Bitmap.scaleFromDensity(mInsetL, sourceDensity, targetDensity); + mInsetT = Bitmap.scaleFromDensity(mInsetT, sourceDensity, targetDensity); + mInsetR = Bitmap.scaleFromDensity(mInsetR, sourceDensity, targetDensity); + mInsetB = Bitmap.scaleFromDensity(mInsetB, sourceDensity, targetDensity); + if (mInsetS != UNDEFINED_INSET) { + mInsetS = Bitmap.scaleFromDensity(mInsetS, sourceDensity, targetDensity); + } + if (mInsetE != UNDEFINED_INSET) { + mInsetE = Bitmap.scaleFromDensity(mInsetE, sourceDensity, targetDensity); + } + if (mWidth > 0) { + mWidth = Bitmap.scaleFromDensity(mWidth, sourceDensity, targetDensity); + } + if (mHeight > 0) { + mHeight = Bitmap.scaleFromDensity(mHeight, sourceDensity, targetDensity); + } + } } static class LayerState extends ConstantState { + private int[] mThemeAttrs; + int mNum; ChildDrawable[] mChildren; - int[] mThemeAttrs; + int mDensity; + + // These values all correspond to mDensity. int mPaddingTop = -1; int mPaddingBottom = -1; int mPaddingLeft = -1; @@ -1784,7 +1872,19 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { private int mPaddingMode = PADDING_MODE_NEST; - LayerState(LayerState orig, LayerDrawable owner, Resources res) { + LayerState(@Nullable LayerState orig, @NonNull LayerDrawable owner, + @Nullable Resources res) { + final int densityDpi; + if (res != null) { + densityDpi = res.getDisplayMetrics().densityDpi; + } else if (orig != null) { + densityDpi = orig.mDensity; + } else { + densityDpi = 0; + } + + mDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + if (orig != null) { final ChildDrawable[] origChildDrawable = orig.mChildren; final int N = orig.mNum; @@ -1814,12 +1914,56 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { mPaddingStart = orig.mPaddingStart; mPaddingEnd = orig.mPaddingEnd; mOpacityOverride = orig.mOpacityOverride; + + if (orig.mDensity != mDensity) { + applyDensityScaling(orig.mDensity, mDensity); + } } else { mNum = 0; mChildren = null; } } + public final void setDensity(int targetDensity) { + if (mDensity != targetDensity) { + final int sourceDensity = mDensity; + mDensity = targetDensity; + + onDensityChanged(sourceDensity, targetDensity); + } + } + + protected void onDensityChanged(int sourceDensity, int targetDensity) { + applyDensityScaling(sourceDensity, targetDensity); + } + + private void applyDensityScaling(int sourceDensity, int targetDensity) { + if (mPaddingLeft > 0) { + mPaddingLeft = Bitmap.scaleFromDensity( + mPaddingLeft, sourceDensity, targetDensity); + } + if (mPaddingTop > 0) { + mPaddingTop = Bitmap.scaleFromDensity( + mPaddingTop, sourceDensity, targetDensity); + } + if (mPaddingRight > 0) { + mPaddingRight = Bitmap.scaleFromDensity( + mPaddingRight, sourceDensity, targetDensity); + } + if (mPaddingBottom > 0) { + mPaddingBottom = Bitmap.scaleFromDensity( + mPaddingBottom, sourceDensity, targetDensity); + } + if (mPaddingStart > 0) { + mPaddingStart = Bitmap.scaleFromDensity( + mPaddingStart, sourceDensity, targetDensity); + } + if (mPaddingEnd > 0) { + mPaddingEnd = Bitmap.scaleFromDensity( + mPaddingEnd, sourceDensity, targetDensity); + } + } + @Override public boolean canApplyTheme() { if (mThemeAttrs != null || super.canApplyTheme()) { @@ -1844,7 +1988,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } @Override - public Drawable newDrawable(Resources res) { + public Drawable newDrawable(@Nullable Resources res) { return new LayerDrawable(this, res); } diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 2690223e3e20..a196fad25b7e 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -409,18 +409,20 @@ public class RippleDrawable extends LayerDrawable { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RippleDrawable); - updateStateFromTypedArray(a); - a.recycle(); // Force padding default to STACK before inflating. setPaddingMode(PADDING_MODE_STACK); + // Inflation will advance the XmlPullParser and AttributeSet. super.inflate(r, parser, attrs, theme); - setTargetDensity(r.getDisplayMetrics()); + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + a.recycle(); updateLocalState(); } @@ -461,7 +463,7 @@ public class RippleDrawable extends LayerDrawable { /** * Initializes the constant state from the values in the typed array. */ - private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { + private void updateStateFromTypedArray(@NonNull TypedArray a) throws XmlPullParserException { final RippleState state = mState; // Account for any configuration changes. @@ -477,11 +479,9 @@ public class RippleDrawable extends LayerDrawable { mState.mMaxRadius = a.getDimensionPixelSize( R.styleable.RippleDrawable_radius, mState.mMaxRadius); - - verifyRequiredAttributes(a); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { if (mState.mColor == null && (mState.mTouchThemeAttrs == null || mState.mTouchThemeAttrs[R.styleable.RippleDrawable_color] == 0)) { throw new XmlPullParserException(a.getPositionDescription() + @@ -489,20 +489,8 @@ public class RippleDrawable extends LayerDrawable { } } - /** - * Set the density at which this drawable will be rendered. - * - * @param metrics The display metrics for this drawable. - */ - private void setTargetDensity(DisplayMetrics metrics) { - if (mDensity != metrics.density) { - mDensity = metrics.density; - invalidateSelf(false); - } - } - @Override - public void applyTheme(Theme t) { + public void applyTheme(@NonNull Theme t) { super.applyTheme(t); final RippleState state = mState; @@ -515,6 +503,7 @@ public class RippleDrawable extends LayerDrawable { R.styleable.RippleDrawable); try { updateStateFromTypedArray(a); + verifyRequiredAttributes(a); } catch (XmlPullParserException e) { throw new RuntimeException(e); } finally { @@ -555,7 +544,8 @@ public class RippleDrawable extends LayerDrawable { mBackground = new RippleBackground(this, mHotspotBounds, mForceSoftware); } - mBackground.setup(mState.mMaxRadius, mDensity); + final float densityScale = mState.mDensity * DisplayMetrics.DENSITY_DEFAULT_SCALE; + mBackground.setup(mState.mMaxRadius, densityScale); mBackground.enter(focused); } @@ -1002,6 +992,23 @@ public class RippleDrawable extends LayerDrawable { mTouchThemeAttrs = origs.mTouchThemeAttrs; mColor = origs.mColor; mMaxRadius = origs.mMaxRadius; + + if (origs.mDensity != mDensity) { + applyDensityScaling(orig.mDensity, mDensity); + } + } + } + + @Override + protected void onDensityChanged(int sourceDensity, int targetDensity) { + super.onDensityChanged(sourceDensity, targetDensity); + + applyDensityScaling(sourceDensity, targetDensity); + } + + private void applyDensityScaling(int sourceDensity, int targetDensity) { + if (mMaxRadius != RADIUS_AUTO) { + mMaxRadius = Bitmap.scaleFromDensity(mMaxRadius, sourceDensity, targetDensity); } } diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java index 036a078eb00d..1531ba2380fb 100644 --- a/graphics/java/android/graphics/drawable/RotateDrawable.java +++ b/graphics/java/android/graphics/drawable/RotateDrawable.java @@ -21,6 +21,8 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Canvas; import android.graphics.Rect; import android.content.res.Resources; @@ -58,22 +60,46 @@ public class RotateDrawable extends DrawableWrapper { * Creates a new rotating drawable with no wrapped drawable. */ public RotateDrawable() { - this(new RotateState(null), null); + this(new RotateState(null, null), null); } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RotateDrawable); - super.inflateWithAttributes(r, parser, a, R.styleable.RotateDrawable_visible); + + // Inflation will advance the XmlPullParser and AttributeSet. + super.inflate(r, parser, attrs, theme); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + @Override + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); + + final RotateState state = mState; + if (state == null) { + return; + } + + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); + } + } + } + + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { // If we're not waiting on a theme, verify required attributes. if (getDrawable() == null && (mState.mThemeAttrs == null || mState.mThemeAttrs[R.styleable.RotateDrawable_drawable] == 0)) { @@ -83,11 +109,14 @@ public class RotateDrawable extends DrawableWrapper { } } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); - + private void updateStateFromTypedArray(@NonNull TypedArray a) { final RotateState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); // Extract the theme attributes, if any. state.mThemeAttrs = a.extractThemeAttrs(); @@ -109,35 +138,6 @@ public class RotateDrawable extends DrawableWrapper { state.mToDegrees = a.getFloat( R.styleable.RotateDrawable_toDegrees, state.mToDegrees); state.mCurrentDegrees = state.mFromDegrees; - - final Drawable dr = a.getDrawable(R.styleable.RotateDrawable_drawable); - if (dr != null) { - setDrawable(dr); - } - } - - @Override - public void applyTheme(Theme t) { - final RotateState state = mState; - if (state == null) { - return; - } - - if (state.mThemeAttrs != null) { - final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable); - try { - updateStateFromTypedArray(a); - verifyRequiredAttributes(a); - } catch (XmlPullParserException e) { - throw new RuntimeException(e); - } finally { - a.recycle(); - } - } - - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); } @Override @@ -316,11 +316,13 @@ public class RotateDrawable extends DrawableWrapper { @Override DrawableWrapperState mutateConstantState() { - mState = new RotateState(mState); + mState = new RotateState(mState, null); return mState; } static final class RotateState extends DrawableWrapper.DrawableWrapperState { + private int[] mThemeAttrs; + boolean mPivotXRel = true; float mPivotX = 0.5f; boolean mPivotYRel = true; @@ -329,8 +331,8 @@ public class RotateDrawable extends DrawableWrapper { float mToDegrees = 360.0f; float mCurrentDegrees = 0.0f; - RotateState(RotateState orig) { - super(orig); + RotateState(RotateState orig, Resources res) { + super(orig, res); if (orig != null) { mPivotXRel = orig.mPivotXRel; diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java index f9206b7d3c26..f87c19a40538 100644 --- a/graphics/java/android/graphics/drawable/ScaleDrawable.java +++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java @@ -21,6 +21,8 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; @@ -67,7 +69,7 @@ public class ScaleDrawable extends DrawableWrapper { private ScaleState mState; ScaleDrawable() { - this(new ScaleState(null), null); + this(new ScaleState(null, null), null); } /** @@ -83,7 +85,7 @@ public class ScaleDrawable extends DrawableWrapper { * is at the maximum value, or -1 to not scale height */ public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) { - this(new ScaleState(null), null); + this(new ScaleState(null, null), null); mState.mGravity = gravity; mState.mScaleWidth = scaleWidth; @@ -93,20 +95,46 @@ public class ScaleDrawable extends DrawableWrapper { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable); + + // Inflation will advance the XmlPullParser and AttributeSet. super.inflate(r, parser, attrs, theme); - final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ScaleDrawable); updateStateFromTypedArray(a); - inflateChildDrawable(r, parser, attrs, theme); verifyRequiredAttributes(a); a.recycle(); updateLocalState(); } - private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { + @Override + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); + + final ScaleState state = mState; + if (state == null) { + return; + } + + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ScaleDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + throw new RuntimeException(e); + } finally { + a.recycle(); + } + } + + updateLocalState(); + } + + private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { // If we're not waiting on a theme, verify required attributes. if (getDrawable() == null && (mState.mThemeAttrs == null || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) { @@ -116,11 +144,18 @@ public class ScaleDrawable extends DrawableWrapper { } } - @Override - void updateStateFromTypedArray(TypedArray a) { - super.updateStateFromTypedArray(a); - + private void updateStateFromTypedArray(@NonNull TypedArray a) { final ScaleState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + state.mScaleWidth = getPercent(a, R.styleable.ScaleDrawable_scaleWidth, state.mScaleWidth); state.mScaleHeight = getPercent(a, @@ -131,11 +166,6 @@ public class ScaleDrawable extends DrawableWrapper { R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, state.mUseIntrinsicSizeAsMin); state.mInitialLevel = a.getInt( R.styleable.ScaleDrawable_level, state.mInitialLevel); - - final Drawable dr = a.getDrawable(R.styleable.ScaleDrawable_drawable); - if (dr != null) { - setDrawable(dr); - } } private static float getPercent(TypedArray a, int index, float defaultValue) { @@ -157,33 +187,6 @@ public class ScaleDrawable extends DrawableWrapper { } @Override - public void applyTheme(Theme t) { - final ScaleState state = mState; - if (state == null) { - return; - } - - if (state.mThemeAttrs != null) { - final TypedArray a = t.resolveAttributes( - state.mThemeAttrs, R.styleable.ScaleDrawable); - try { - updateStateFromTypedArray(a); - verifyRequiredAttributes(a); - } catch (XmlPullParserException e) { - throw new RuntimeException(e); - } finally { - a.recycle(); - } - } - - // The drawable may have changed as a result of applying the theme, so - // apply the theme to the wrapped drawable last. - super.applyTheme(t); - - updateLocalState(); - } - - @Override public void draw(Canvas canvas) { final Drawable d = getDrawable(); if (d != null && d.getLevel() != 0) { @@ -243,7 +246,7 @@ public class ScaleDrawable extends DrawableWrapper { @Override DrawableWrapperState mutateConstantState() { - mState = new ScaleState(mState); + mState = new ScaleState(mState, null); return mState; } @@ -251,14 +254,16 @@ public class ScaleDrawable extends DrawableWrapper { /** Constant used to disable scaling for a particular dimension. */ private static final float DO_NOT_SCALE = -1.0f; + private int[] mThemeAttrs; + float mScaleWidth = DO_NOT_SCALE; float mScaleHeight = DO_NOT_SCALE; int mGravity = Gravity.LEFT; boolean mUseIntrinsicSizeAsMin = false; int mInitialLevel = 0; - ScaleState(ScaleState orig) { - super(orig); + ScaleState(ScaleState orig, Resources res) { + super(orig, res); if (orig != null) { mScaleWidth = orig.mScaleWidth; diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 4385e7012f54..d94c91d64572 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -113,6 +113,7 @@ $(call local-generated-sources-dir)/proto/$(LOCAL_PATH) endef hwui_c_includes += \ + external/skia/include/private \ external/skia/src/core hwui_shared_libraries := \ @@ -202,6 +203,7 @@ LOCAL_SRC_FILES += \ unit_tests/CanvasStateTests.cpp \ unit_tests/ClipAreaTests.cpp \ unit_tests/DamageAccumulatorTests.cpp \ + unit_tests/FatVectorTests.cpp \ unit_tests/LinearAllocatorTests.cpp \ unit_tests/StringUtilsTests.cpp diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp index 94806cab2fc5..086885320cce 100644 --- a/libs/hwui/BakedOpRenderer.cpp +++ b/libs/hwui/BakedOpRenderer.cpp @@ -25,112 +25,210 @@ namespace android { namespace uirenderer { -Texture* BakedOpRenderer::Info::getTexture(const SkBitmap* bitmap) { - Texture* texture = renderState.assetAtlas().getEntryTexture(bitmap); - if (!texture) { - return caches.textureCache.get(bitmap); - } - return texture; +//////////////////////////////////////////////////////////////////////////////// +// OffscreenBuffer +//////////////////////////////////////////////////////////////////////////////// + +OffscreenBuffer::OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight, + uint32_t viewportWidth, uint32_t viewportHeight) + : texture(caches) + , texCoords(0, viewportHeight / float(textureHeight), viewportWidth / float(textureWidth), 0) { + texture.width = textureWidth; + texture.height = textureHeight; + + caches.textureState().activateTexture(0); + glGenTextures(1, &texture.id); + caches.textureState().bindTexture(GL_TEXTURE_2D, texture.id); + + texture.setWrap(GL_CLAMP_TO_EDGE, false, false, GL_TEXTURE_2D); + // not setting filter on texture, since it's set when drawing, based on transform + + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, nullptr); } -void BakedOpRenderer::Info::renderGlop(const BakedOpState& state, const Glop& glop) { - bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None; - renderState.scissor().setEnabled(useScissor); - if (useScissor) { - const Rect& clip = state.computedState.clipRect; - renderState.scissor().set(clip.left, viewportHeight - clip.bottom, - clip.getWidth(), clip.getHeight()); - } - renderState.render(glop, orthoMatrix); - didDraw = true; +//////////////////////////////////////////////////////////////////////////////// +// BakedOpRenderer +//////////////////////////////////////////////////////////////////////////////// + +OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) { + LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer..."); + + // TODO: really should be caching these! + OffscreenBuffer* buffer = new OffscreenBuffer(mCaches, width, height, width, height); + mRenderTarget.offscreenBuffer = buffer; + + // create and bind framebuffer + mRenderTarget.frameBufferId = mRenderState.genFramebuffer(); + mRenderState.bindFramebuffer(mRenderTarget.frameBufferId); + + // attach the texture to the FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + buffer->texture.id, 0); + LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED"); + LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE, + "framebuffer incomplete!"); + + // Clear the FBO + mRenderState.scissor().setEnabled(false); + glClear(GL_COLOR_BUFFER_BIT); + + // Change the viewport & ortho projection + setViewport(width, height); + return buffer; +} + +void BakedOpRenderer::endLayer() { + mRenderTarget.offscreenBuffer = nullptr; + + // Detach the texture from the FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "endLayer FAILED"); + mRenderState.deleteFramebuffer(mRenderTarget.frameBufferId); + mRenderTarget.frameBufferId = -1; } -void BakedOpRenderer::startFrame(Info& info) { - info.renderState.setViewport(info.viewportWidth, info.viewportHeight); - info.renderState.blend().syncEnabled(); - Caches::getInstance().clearGarbage(); +void BakedOpRenderer::startFrame(uint32_t width, uint32_t height) { + mRenderState.bindFramebuffer(0); + setViewport(width, height); + mCaches.clearGarbage(); - if (!info.opaque) { + if (!mOpaque) { // TODO: partial invalidate! - info.renderState.scissor().setEnabled(false); + mRenderState.scissor().setEnabled(false); glClear(GL_COLOR_BUFFER_BIT); - info.didDraw = true; + mHasDrawn = true; } } -void BakedOpRenderer::endFrame(Info& info) { - info.caches.pathCache.trim(); - info.caches.tessellationCache.trim(); + +void BakedOpRenderer::endFrame() { + mCaches.pathCache.trim(); + mCaches.tessellationCache.trim(); #if DEBUG_OPENGL GLUtils::dumpGLErrors(); #endif #if DEBUG_MEMORY_USAGE - info.caches.dumpMemoryUsage(); + mCaches.dumpMemoryUsage(); #else if (Properties::debugLevel & kDebugMemory) { - info.caches.dumpMemoryUsage(); + mCaches.dumpMemoryUsage(); } #endif } -void BakedOpRenderer::onRenderNodeOp(Info&, const RenderNodeOp&, const BakedOpState&) { +void BakedOpRenderer::setViewport(uint32_t width, uint32_t height) { + mRenderTarget.viewportWidth = width; + mRenderTarget.viewportHeight = height; + mRenderTarget.orthoMatrix.loadOrtho(width, height); + + mRenderState.setViewport(width, height); + mRenderState.blend().syncEnabled(); +} + +Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) { + Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap); + if (!texture) { + return mCaches.textureCache.get(bitmap); + } + return texture; +} + +void BakedOpRenderer::renderGlop(const BakedOpState& state, const Glop& glop) { + bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None; + mRenderState.scissor().setEnabled(useScissor); + if (useScissor) { + const Rect& clip = state.computedState.clipRect; + mRenderState.scissor().set(clip.left, mRenderTarget.viewportHeight - clip.bottom, + clip.getWidth(), clip.getHeight()); + } + mRenderState.render(glop, mRenderTarget.orthoMatrix); + mHasDrawn = true; +} + +//////////////////////////////////////////////////////////////////////////////// +// static BakedOpDispatcher methods +//////////////////////////////////////////////////////////////////////////////// + +void BakedOpDispatcher::onRenderNodeOp(BakedOpRenderer&, const RenderNodeOp&, const BakedOpState&) { LOG_ALWAYS_FATAL("unsupported operation"); } -void BakedOpRenderer::onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) { - info.caches.textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere? - Texture* texture = info.getTexture(op.bitmap); +void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) { + renderer.caches().textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere? + Texture* texture = renderer.getTexture(op.bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType) ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None; Glop glop; - GlopBuilder(info.renderState, info.caches, &glop) + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) .setRoundRectClipState(state.roundRectClipState) .setMeshTexturedUnitQuad(texture->uvMapper) .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha) .setTransform(state.computedState.transform, TransformFlags::None) .setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height)) .build(); - info.renderGlop(state, glop); + renderer.renderGlop(state, glop); } -void BakedOpRenderer::onRectOp(Info& info, const RectOp& op, const BakedOpState& state) { +void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) { Glop glop; - GlopBuilder(info.renderState, info.caches, &glop) + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) .setRoundRectClipState(state.roundRectClipState) .setMeshUnitQuad() .setFillPaint(*op.paint, state.alpha) .setTransform(state.computedState.transform, TransformFlags::None) .setModelViewMapUnitToRect(op.unmappedBounds) .build(); - info.renderGlop(state, glop); + renderer.renderGlop(state, glop); } -void BakedOpRenderer::onSimpleRectsOp(Info& info, const SimpleRectsOp& op, const BakedOpState& state) { +void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, const BakedOpState& state) { Glop glop; - GlopBuilder(info.renderState, info.caches, &glop) + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) .setRoundRectClipState(state.roundRectClipState) .setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4) .setFillPaint(*op.paint, state.alpha) .setTransform(state.computedState.transform, TransformFlags::None) .setModelViewOffsetRect(0, 0, op.unmappedBounds) .build(); - info.renderGlop(state, glop); + renderer.renderGlop(state, glop); } -void BakedOpRenderer::onBeginLayerOp(Info& info, const BeginLayerOp& op, const BakedOpState& state) { +void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) { LOG_ALWAYS_FATAL("unsupported operation"); } -void BakedOpRenderer::onEndLayerOp(Info& info, const EndLayerOp& op, const BakedOpState& state) { +void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) { LOG_ALWAYS_FATAL("unsupported operation"); } -void BakedOpRenderer::onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) { - LOG_ALWAYS_FATAL("unsupported operation"); +void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) { + OffscreenBuffer* buffer = *op.layerHandle; + + // TODO: extend this to handle HW layers & paint properties which + // reside in node.properties().layerProperties() + float layerAlpha = (op.paint->getAlpha() / 255.0f) * state.alpha; + const bool tryToSnap = state.computedState.transform.isPureTranslate(); + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshTexturedUvQuad(nullptr, buffer->texCoords) + .setFillLayer(buffer->texture, op.paint->getColorFilter(), layerAlpha, PaintUtils::getXfermodeDirect(op.paint), Blend::ModeOrderSwap::NoSwap) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds) + .build(); + renderer.renderGlop(state, glop); + + // destroy and delete, since each clipped saveLayer is only drawn once. + buffer->texture.deleteTexture(); + + // TODO: return texture/offscreenbuffer to cache! + delete buffer; } } // namespace uirenderer diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h index f45dbe41f308..16afad44657e 100644 --- a/libs/hwui/BakedOpRenderer.h +++ b/libs/hwui/BakedOpRenderer.h @@ -25,48 +25,85 @@ namespace uirenderer { class Caches; struct Glop; +class Layer; class RenderState; +/** + * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and + * encompasses enough information to draw it back on screen (minus paint properties, which are held + * by LayerOp). + */ +class OffscreenBuffer { +public: + OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight, + uint32_t viewportWidth, uint32_t viewportHeight); + + Texture texture; + Rect texCoords; + Region region; +}; + +/** + * Main rendering manager for a collection of work - one frame + any contained FBOs. + * + * Manages frame and FBO lifecycle, binding the GL framebuffer as appropriate. This is the only + * place where FBOs are bound, created, and destroyed. + * + * All rendering operations will be sent by the Dispatcher, a collection of static methods, + * which has intentionally limited access to the renderer functionality. + */ class BakedOpRenderer { public: - class Info { - public: - Info(Caches& caches, RenderState& renderState, int viewportWidth, int viewportHeight, bool opaque) - : renderState(renderState) - , caches(caches) - , opaque(opaque) - , viewportWidth(viewportWidth) - , viewportHeight(viewportHeight) { - orthoMatrix.loadOrtho(viewportWidth, viewportHeight); - } - - Texture* getTexture(const SkBitmap* bitmap); - - void renderGlop(const BakedOpState& state, const Glop& glop); - RenderState& renderState; - Caches& caches; - - bool didDraw = false; - bool opaque; - - - // where should these live? layer state object? - int viewportWidth; - int viewportHeight; + BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque) + : mRenderState(renderState) + , mCaches(caches) + , mOpaque(opaque) { + } + + RenderState& renderState() { return mRenderState; } + Caches& caches() { return mCaches; } + + void startFrame(uint32_t width, uint32_t height); + void endFrame(); + OffscreenBuffer* startLayer(uint32_t width, uint32_t height); + void endLayer(); + + Texture* getTexture(const SkBitmap* bitmap); + + void renderGlop(const BakedOpState& state, const Glop& glop); + bool didDraw() { return mHasDrawn; } +private: + void setViewport(uint32_t width, uint32_t height); + + RenderState& mRenderState; + Caches& mCaches; + bool mOpaque; + bool mHasDrawn = false; + + // render target state - setup by start/end layer/frame + // only valid to use in between start/end pairs. + struct { + GLuint frameBufferId = 0; + OffscreenBuffer* offscreenBuffer = nullptr; + uint32_t viewportWidth = 0; + uint32_t viewportHeight = 0; Matrix4 orthoMatrix; - }; - - static void startFrame(Info& info); - static void endFrame(Info& info); - - /** - * Declare all "onBitmapOp(...)" style function for every op type. - * - * These functions will perform the actual rendering of the individual operations in OpenGL, - * given the transform/clip and other state built into the BakedOpState object passed in. - */ - #define BAKED_OP_RENDERER_METHOD(Type) static void on##Type(Info& info, const Type& op, const BakedOpState& state); - MAP_OPS(BAKED_OP_RENDERER_METHOD); + } mRenderTarget; +}; + +/** + * Provides all "onBitmapOp(...)" style static methods for every op type, which convert the + * RecordedOps and their state to Glops, and renders them with the provided BakedOpRenderer. + * + * This dispatcher is separate from the renderer so that the dispatcher / renderer interaction is + * minimal through public BakedOpRenderer APIs. + */ +class BakedOpDispatcher { +public: + // Declares all "onBitmapOp(...)" style methods for every op type +#define DISPATCH_METHOD(Type) \ + static void on##Type(BakedOpRenderer& renderer, const Type& op, const BakedOpState& state); + MAP_OPS(DISPATCH_METHOD); }; }; // namespace uirenderer diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp index bad397219398..f5e57355138c 100644 --- a/libs/hwui/DisplayListCanvas.cpp +++ b/libs/hwui/DisplayListCanvas.cpp @@ -584,7 +584,7 @@ void DisplayListCanvas::refBitmapsInShader(const SkShader* shader) { // it to the bitmap pile SkBitmap bitmap; SkShader::TileMode xy[2]; - if (shader->asABitmap(&bitmap, nullptr, xy) == SkShader::kDefault_BitmapType) { + if (shader->isABitmap(&bitmap, nullptr, xy)) { refBitmap(bitmap); return; } diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index f99d92b89420..489ebc116a2a 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -155,8 +155,7 @@ void Layer::removeFbo(bool flush) { if (fbo) { if (flush) LayerRenderer::flushLayer(renderState, this); - // If put fails the cache will delete the FBO - caches.fboCache.put(fbo); + renderState.deleteFramebuffer(fbo); fbo = 0; } } diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index 227271d83cf8..e9e5d81f1ce2 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -189,7 +189,7 @@ Layer* LayerRenderer::createRenderLayer(RenderState& renderState, uint32_t width LAYER_RENDERER_LOGD("Requesting new render layer %dx%d", width, height); Caches& caches = Caches::getInstance(); - GLuint fbo = caches.fboCache.get(); + GLuint fbo = renderState.genFramebuffer(); if (!fbo) { ALOGW("Could not obtain an FBO"); return nullptr; @@ -204,7 +204,7 @@ Layer* LayerRenderer::createRenderLayer(RenderState& renderState, uint32_t width // We first obtain a layer before comparing against the max texture size // because layers are not allocated at the exact desired size. They are - // always created slighly larger to improve recycling + // always created slightly larger to improve recycling const uint32_t maxTextureSize = caches.maxTextureSize; if (layer->getWidth() > maxTextureSize || layer->getHeight() > maxTextureSize) { ALOGW("Layer exceeds max. dimensions supported by the GPU (%dx%d, max=%dx%d)", @@ -357,7 +357,7 @@ bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* && bitmap->width() <= caches.maxTextureSize && bitmap->height() <= caches.maxTextureSize) { - GLuint fbo = caches.fboCache.get(); + GLuint fbo = renderState.getFramebuffer(); if (!fbo) { ALOGW("Could not obtain an FBO"); return false; @@ -465,7 +465,7 @@ error: layer->setAlpha(alpha, mode); layer->setFbo(previousLayerFbo); caches.textureState().deleteTexture(texture); - caches.fboCache.put(fbo); + renderState.deleteFramebuffer(fbo); renderState.setViewport(previousViewportWidth, previousViewportHeight); return status; diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp index c1417c451895..ddeb33624798 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/OpReorderer.cpp @@ -203,7 +203,7 @@ private: }; // iterate back toward target to see if anything drawn since should overlap the new op -// if no target, merging ops still interate to find similar batch to insert after +// if no target, merging ops still iterate to find similar batch to insert after void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds, BatchBase** targetBatch, size_t* insertBatchIndex) const { for (int i = mBatches.size() - 1; i >= 0; i--) { @@ -277,7 +277,8 @@ void OpReorderer::LayerReorderer::deferMergeableOp(LinearAllocator& allocator, } } -void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const { +void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers) const { + ATRACE_NAME("flush drawing commands"); for (const BatchBase* batch : mBatches) { // TODO: different behavior based on batch->isMerging() for (const BakedOpState* op : batch->getOps()) { @@ -292,18 +293,14 @@ void OpReorderer::LayerReorderer::dump() const { } } -OpReorderer::OpReorderer() +OpReorderer::OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight, + const std::vector< sp<RenderNode> >& nodes) : mCanvasState(*this) { - mLayerReorderers.emplace_back(); - mLayerStack.push_back(0); -} - -void OpReorderer::onViewportInitialized() {} + ATRACE_NAME("prepare drawing commands"); -void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} + mLayerReorderers.emplace_back(viewportWidth, viewportHeight); + mLayerStack.push_back(0); -void OpReorderer::defer(const SkRect& clip, int viewportWidth, int viewportHeight, - const std::vector< sp<RenderNode> >& nodes) { mCanvasState.initializeSaveStack(viewportWidth, viewportHeight, clip.fLeft, clip.fTop, clip.fRight, clip.fBottom, Vector3()); @@ -321,13 +318,22 @@ void OpReorderer::defer(const SkRect& clip, int viewportWidth, int viewportHeigh } } -void OpReorderer::defer(int viewportWidth, int viewportHeight, const DisplayList& displayList) { +OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList) + : mCanvasState(*this) { ATRACE_NAME("prepare drawing commands"); + + mLayerReorderers.emplace_back(viewportWidth, viewportHeight); + mLayerStack.push_back(0); + mCanvasState.initializeSaveStack(viewportWidth, viewportHeight, 0, 0, viewportWidth, viewportHeight, Vector3()); deferImpl(displayList); } +void OpReorderer::onViewportInitialized() {} + +void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} + /** * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods. * @@ -348,15 +354,6 @@ void OpReorderer::deferImpl(const DisplayList& displayList) { } } -void OpReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) { - ATRACE_NAME("flush drawing commands"); - // Relay through layers in reverse order, since layers - // later in the list will be drawn by earlier ones - for (int i = mLayerReorderers.size() - 1; i >= 0; i--) { - mLayerReorderers[i].replayBakedOpsImpl(arg, receivers); - } -} - void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) { if (op.renderNode->nothingToDraw()) { return; @@ -405,15 +402,17 @@ void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) { // TODO: test rejection at defer time, where the bounds become empty void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) { + const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth(); + const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight(); + mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); mCanvasState.writableSnapshot()->transform->loadIdentity(); - mCanvasState.writableSnapshot()->initializeViewport( - (int) op.unmappedBounds.getWidth(), (int) op.unmappedBounds.getHeight()); + mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight); mCanvasState.writableSnapshot()->roundRectClipState = nullptr; // create a new layer, and push its index on the stack mLayerStack.push_back(mLayerReorderers.size()); - mLayerReorderers.emplace_back(); + mLayerReorderers.emplace_back(layerWidth, layerHeight); mLayerReorderers.back().beginLayerOp = &op; } @@ -432,7 +431,8 @@ void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) { beginLayerOp.unmappedBounds, beginLayerOp.localMatrix, beginLayerOp.localClipRect, - beginLayerOp.paint); + beginLayerOp.paint, + &mLayerReorderers[finishedLayerIndex].offscreenBuffer); BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp); if (bakedOpState) { diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h index 73dc9af01f71..927ecfae3b9f 100644 --- a/libs/hwui/OpReorderer.h +++ b/libs/hwui/OpReorderer.h @@ -33,6 +33,7 @@ namespace uirenderer { class BakedOpState; class BatchBase; class MergingOpBatch; +class OffscreenBuffer; class OpBatch; class Rect; @@ -55,7 +56,7 @@ namespace OpBatchType { } class OpReorderer : public CanvasStateClient { - typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpReceiver; + typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpDispatcher; /** * Stores the deferred render operations and state used to compute ordering @@ -63,6 +64,10 @@ class OpReorderer : public CanvasStateClient { */ class LayerReorderer { public: + LayerReorderer(uint32_t width, uint32_t height) + : width(width) + , height(height) {} + // iterate back toward target to see if anything drawn since should overlap the new op // if no target, merging ops still iterate to find similar batch to insert after void locateInsertIndex(int batchId, const Rect& clippedBounds, @@ -75,7 +80,11 @@ class OpReorderer : public CanvasStateClient { void deferMergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId, mergeid_t mergeId); - void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const; + void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers) const; + + bool empty() const { + return mBatches.empty(); + } void clear() { mBatches.clear(); @@ -83,9 +92,12 @@ class OpReorderer : public CanvasStateClient { void dump() const; + OffscreenBuffer* offscreenBuffer = nullptr; const BeginLayerOp* beginLayerOp = nullptr; - + const uint32_t width; + const uint32_t height; private: + std::vector<BatchBase*> mBatches; /** @@ -100,14 +112,13 @@ class OpReorderer : public CanvasStateClient { }; public: - OpReorderer(); - virtual ~OpReorderer() {} - // TODO: not final, just presented this way for simplicity. Layers too? - void defer(const SkRect& clip, int viewportWidth, int viewportHeight, + OpReorderer(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight, const std::vector< sp<RenderNode> >& nodes); - void defer(int viewportWidth, int viewportHeight, const DisplayList& displayList); + OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList); + + virtual ~OpReorderer() {} /** * replayBakedOps() is templated based on what class will receive ops being replayed. @@ -117,20 +128,33 @@ public: * * For example a BitmapOp would resolve, via the lambda lookup, to calling: * - * StaticReceiver::onBitmapOp(Arg* arg, const BitmapOp& op, const BakedOpState& state); + * StaticDispatcher::onBitmapOp(Renderer& renderer, const BitmapOp& op, const BakedOpState& state); */ #define BAKED_OP_RECEIVER(Type) \ - [](void* internalArg, const RecordedOp& op, const BakedOpState& state) { \ - StaticReceiver::on##Type(*(static_cast<Arg*>(internalArg)), static_cast<const Type&>(op), state); \ + [](void* internalRenderer, const RecordedOp& op, const BakedOpState& state) { \ + StaticDispatcher::on##Type(*(static_cast<Renderer*>(internalRenderer)), static_cast<const Type&>(op), state); \ }, - template <typename StaticReceiver, typename Arg> - void replayBakedOps(Arg& arg) { - static BakedOpReceiver receivers[] = { + template <typename StaticDispatcher, typename Renderer> + void replayBakedOps(Renderer& renderer) { + static BakedOpDispatcher receivers[] = { MAP_OPS(BAKED_OP_RECEIVER) }; - StaticReceiver::startFrame(arg); - replayBakedOpsImpl((void*)&arg, receivers); - StaticReceiver::endFrame(arg); + + // Relay through layers in reverse order, since layers + // later in the list will be drawn by earlier ones + for (int i = mLayerReorderers.size() - 1; i >= 1; i--) { + LayerReorderer& layer = mLayerReorderers[i]; + if (!layer.empty()) { + layer.offscreenBuffer = renderer.startLayer(layer.width, layer.height); + layer.replayBakedOpsImpl((void*)&renderer, receivers); + renderer.endLayer(); + } + } + + const LayerReorderer& fbo0 = mLayerReorderers[0]; + renderer.startFrame(fbo0.width, fbo0.height); + fbo0.replayBakedOpsImpl((void*)&renderer, receivers); + renderer.endFrame(); } void dump() const { @@ -155,7 +179,7 @@ private: void deferImpl(const DisplayList& displayList); - void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers); + void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers); /** * Declares all OpReorderer::onXXXXOp() methods for every RecordedOp type. diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index d4f65b635d4c..8c3603b153b9 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -673,7 +673,7 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) { layer->clipRect.set(clip); - layer->setFbo(mCaches.fboCache.get()); + layer->setFbo(mRenderState.genFramebuffer()); writableSnapshot()->region = &writableSnapshot()->layer->region; writableSnapshot()->flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer; diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h index dd016372584a..7874d85f249c 100644 --- a/libs/hwui/RecordedOp.h +++ b/libs/hwui/RecordedOp.h @@ -29,6 +29,7 @@ class SkPaint; namespace android { namespace uirenderer { +class OffscreenBuffer; class RenderNode; struct Vertex; @@ -136,8 +137,12 @@ struct EndLayerOp : RecordedOp { }; struct LayerOp : RecordedOp { - LayerOp(BASE_PARAMS) - : SUPER(LayerOp) {} + LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle) + : SUPER(LayerOp) + , layerHandle(layerHandle) {} + // Records a handle to the Layer object, since the Layer itself won't be + // constructed until after this operation is constructed. + OffscreenBuffer** layerHandle; }; }; // namespace uirenderer diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 1f113bc19ebb..273af3ac5997 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -458,7 +458,7 @@ void RecordingCanvas::refBitmapsInShader(const SkShader* shader) { // it to the bitmap pile SkBitmap bitmap; SkShader::TileMode xy[2]; - if (shader->asABitmap(&bitmap, nullptr, xy) == SkShader::kDefault_BitmapType) { + if (shader->isABitmap(&bitmap, nullptr, xy)) { refBitmap(bitmap); return; } diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 9c32b1a769dc..454ee24cfc64 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -27,6 +27,8 @@ #include "Snapshot.h" #include "SkDrawFilter.h" +#include "SkPaint.h" +#include "SkTLazy.h" #include <vector> namespace android { diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 351fbaa86a2a..39cb8e9229b1 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -16,13 +16,6 @@ #include "RenderNode.h" -#include <algorithm> -#include <string> - -#include <SkCanvas.h> -#include <algorithm> - - #include "DamageAccumulator.h" #include "Debug.h" #if HWUI_NEW_OPS @@ -39,6 +32,12 @@ #include "protos/hwui.pb.h" #include "protos/ProtoHelpers.h" +#include <SkCanvas.h> + +#include <algorithm> +#include <sstream> +#include <string> + namespace android { namespace uirenderer { @@ -79,6 +78,11 @@ void RenderNode::setStagingDisplayList(DisplayList* displayList) { mNeedsDisplayListSync = true; delete mStagingDisplayList; mStagingDisplayList = displayList; + // If mParentCount == 0 we are the sole reference to this RenderNode, + // so immediately free the old display list + if (!mParentCount && !mStagingDisplayList) { + deleteDisplayList(); + } } /** @@ -250,7 +254,8 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { bool transformUpdateNeeded = false; if (!mLayer) { - mLayer = LayerRenderer::createRenderLayer(info.renderState, getWidth(), getHeight()); + mLayer = LayerRenderer::createRenderLayer( + info.canvasContext.getRenderState(), getWidth(), getHeight()); applyLayerPropertiesToLayer(info); damageSelf(info); transformUpdateNeeded = true; @@ -269,9 +274,16 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { if (!mLayer) { Caches::getInstance().dumpMemoryUsage(); if (info.errorHandler) { - std::string msg = "Unable to create layer for "; - msg += getName(); - info.errorHandler->onError(msg); + std::ostringstream err; + err << "Unable to create layer for " << getName(); + const int maxTextureSize = Caches::getInstance().maxTextureSize; + if (getWidth() > maxTextureSize || getHeight() > maxTextureSize) { + err << ", size " << getWidth() << "x" << getHeight() + << " exceeds max size " << maxTextureSize; + } else { + err << ", see logcat for more info"; + } + info.errorHandler->onError(err.str()); } return; } @@ -293,12 +305,10 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { info.renderer->pushLayerUpdate(mLayer); } - if (info.canvasContext) { - // There might be prefetched layers that need to be accounted for. - // That might be us, so tell CanvasContext that this layer is in the - // tree and should not be destroyed. - info.canvasContext->markLayerInUse(this); - } + // There might be prefetched layers that need to be accounted for. + // That might be us, so tell CanvasContext that this layer is in the + // tree and should not be destroyed. + info.canvasContext.markLayerInUse(this); } /** @@ -419,7 +429,8 @@ void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayL TextureCache& cache = Caches::getInstance().textureCache; info.out.hasFunctors |= subtree->getFunctors().size(); for (auto&& bitmapResource : subtree->getBitmapResources()) { - info.prepareTextures = cache.prefetchAndMarkInUse(info.canvasContext, bitmapResource); + void* ownerToken = &info.canvasContext; + info.prepareTextures = cache.prefetchAndMarkInUse(ownerToken, bitmapResource); } for (auto&& op : subtree->getChildren()) { RenderNode* childNode = op->renderNode; diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 36633b5205a1..a8f8134170d3 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -194,15 +194,13 @@ private: void SkiaCanvas::setBitmap(const SkBitmap& bitmap) { SkCanvas* newCanvas = new SkCanvas(bitmap); - SkASSERT(newCanvas); if (!bitmap.isNull()) { // Copy the canvas matrix & clip state. newCanvas->setMatrix(mCanvas->getTotalMatrix()); - if (NULL != mCanvas->getDevice() && NULL != newCanvas->getDevice()) { - ClipCopier copier(newCanvas); - mCanvas->replayClips(&copier); - } + + ClipCopier copier(newCanvas); + mCanvas->replayClips(&copier); } // unrefs the existing canvas @@ -217,15 +215,15 @@ void SkiaCanvas::setBitmap(const SkBitmap& bitmap) { // ---------------------------------------------------------------------------- bool SkiaCanvas::isOpaque() { - return mCanvas->getDevice()->accessBitmap(false).isOpaque(); + return mCanvas->imageInfo().isOpaque(); } int SkiaCanvas::width() { - return mCanvas->getBaseLayerSize().width(); + return mCanvas->imageInfo().width(); } int SkiaCanvas::height() { - return mCanvas->getBaseLayerSize().height(); + return mCanvas->imageInfo().height(); } // ---------------------------------------------------------------------------- @@ -581,7 +579,7 @@ void SkiaCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop, float dstRight, float dstBottom, const SkPaint* paint) { SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom); SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); - mCanvas->drawBitmapRectToRect(bitmap, &srcRect, dstRect, paint); + mCanvas->drawBitmapRect(bitmap, srcRect, dstRect, paint); } void SkiaCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight, diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp index c3f5eb27e236..2d5f70f6a70c 100644 --- a/libs/hwui/SkiaCanvasProxy.cpp +++ b/libs/hwui/SkiaCanvasProxy.cpp @@ -125,7 +125,7 @@ void SkiaCanvasProxy::onDrawBitmap(const SkBitmap& bitmap, SkScalar left, SkScal } void SkiaCanvasProxy::onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* srcPtr, - const SkRect& dst, const SkPaint* paint, DrawBitmapRectFlags) { + const SkRect& dst, const SkPaint* paint, SrcRectConstraint) { SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(bitmap.width(), bitmap.height()); // TODO: if bitmap is a subset, do we need to add pixelRefOrigin to src? mCanvas->drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h index 0de965094e64..2fe4327b9539 100644 --- a/libs/hwui/SkiaCanvasProxy.h +++ b/libs/hwui/SkiaCanvasProxy.h @@ -63,7 +63,7 @@ protected: virtual void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top, const SkPaint*) override; virtual void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst, - const SkPaint* paint, DrawBitmapRectFlags flags) override; + const SkPaint* paint, SrcRectConstraint) override; virtual void onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, const SkRect& dst, const SkPaint*) override; virtual void onDrawSprite(const SkBitmap&, int left, int top, diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index 6c105cfc7b00..83652c6de21f 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -204,7 +204,7 @@ bool tryStoreBitmap(Caches& caches, const SkShader& shader, const Matrix4& model SkiaShaderData::BitmapShaderData* outData) { SkBitmap bitmap; SkShader::TileMode xy[2]; - if (shader.asABitmap(&bitmap, nullptr, xy) != SkShader::kDefault_BitmapType) { + if (!shader.isABitmap(&bitmap, nullptr, xy)) { return false; } @@ -272,7 +272,7 @@ SkiaShaderType getComposeSubType(const SkShader& shader) { } // The shader is not a gradient. Check for a bitmap shader. - if (shader.asABitmap(nullptr, nullptr, nullptr) == SkShader::kDefault_BitmapType) { + if (shader.isABitmap()) { return kBitmap_SkiaShaderType; } return kNone_SkiaShaderType; diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h index 98e61468ea70..1c3148726b63 100644 --- a/libs/hwui/TreeInfo.h +++ b/libs/hwui/TreeInfo.h @@ -55,70 +55,46 @@ public: MODE_RT_ONLY, }; - explicit TreeInfo(TraversalMode mode, RenderState& renderState) - : mode(mode) - , prepareTextures(mode == MODE_FULL) - , runAnimations(true) - , damageAccumulator(nullptr) - , renderState(renderState) - , renderer(nullptr) - , errorHandler(nullptr) - , canvasContext(nullptr) - {} - - explicit TreeInfo(TraversalMode mode, const TreeInfo& clone) - : mode(mode) - , prepareTextures(mode == MODE_FULL) - , runAnimations(clone.runAnimations) - , damageAccumulator(clone.damageAccumulator) - , renderState(clone.renderState) - , renderer(clone.renderer) - , errorHandler(clone.errorHandler) - , canvasContext(clone.canvasContext) + TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext) + : mode(mode) + , prepareTextures(mode == MODE_FULL) + , canvasContext(canvasContext) {} TraversalMode mode; // TODO: Remove this? Currently this is used to signal to stop preparing // textures if we run out of cache space. bool prepareTextures; + renderthread::CanvasContext& canvasContext; // TODO: buildLayer uses this to suppress running any animations, but this // should probably be refactored somehow. The reason this is done is // because buildLayer is not setup for injecting the animationHook, as well // as this being otherwise wasted work as all the animators will be // re-evaluated when the frame is actually drawn - bool runAnimations; + bool runAnimations = true; // Must not be null during actual usage - DamageAccumulator* damageAccumulator; - RenderState& renderState; + DamageAccumulator* damageAccumulator = nullptr; // The renderer that will be drawing the next frame. Use this to push any // layer updates or similar. May be NULL. - OpenGLRenderer* renderer; - ErrorHandler* errorHandler; - // May be NULL (TODO: can it really?) - renderthread::CanvasContext* canvasContext; + OpenGLRenderer* renderer = nullptr; + ErrorHandler* errorHandler = nullptr; struct Out { - Out() - : hasFunctors(false) - , hasAnimations(false) - , requiresUiRedraw(false) - , canDrawThisFrame(true) - {} - bool hasFunctors; + bool hasFunctors = false; // This is only updated if evaluateAnimations is true - bool hasAnimations; + bool hasAnimations = false; // This is set to true if there is an animation that RenderThread cannot // animate itself, such as if hasFunctors is true // This is only set if hasAnimations is true - bool requiresUiRedraw; + bool requiresUiRedraw = false; // This is set to true if draw() can be called this frame // false means that we must delay until the next vsync pulse as frame // production is outrunning consumption // NOTE that if this is false CanvasContext will set either requiresUiRedraw // *OR* will post itself for the next vsync automatically, use this // only to avoid calling draw() - bool canDrawThisFrame; + bool canDrawThisFrame = true; } out; // TODO: Damage calculations diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp index cf96d44c286f..43f170f54d41 100644 --- a/libs/hwui/microbench/OpReordererBench.cpp +++ b/libs/hwui/microbench/OpReordererBench.cpp @@ -48,8 +48,7 @@ BENCHMARK_NO_ARG(BM_OpReorderer_defer); void BM_OpReorderer_defer::Run(int iters) { StartBenchmarkTiming(); for (int i = 0; i < iters; i++) { - OpReorderer reorderer; - reorderer.defer(200, 200, *sReorderingDisplayList); + OpReorderer reorderer(200, 200, *sReorderingDisplayList); MicroBench::DoNotOptimize(&reorderer); } StopBenchmarkTiming(); @@ -60,12 +59,11 @@ void BM_OpReorderer_deferAndRender::Run(int iters) { TestUtils::runOnRenderThread([this, iters](RenderState& renderState, Caches& caches) { StartBenchmarkTiming(); for (int i = 0; i < iters; i++) { - OpReorderer reorderer; - reorderer.defer(200, 200, *sReorderingDisplayList); - MicroBench::DoNotOptimize(&reorderer); + OpReorderer reorderer(200, 200, *sReorderingDisplayList); - BakedOpRenderer::Info info(caches, renderState, 200, 200, true); - reorderer.replayBakedOps<BakedOpRenderer>(info); + BakedOpRenderer renderer(caches, renderState, true); + reorderer.replayBakedOps<BakedOpDispatcher>(renderer); + MicroBench::DoNotOptimize(&renderer); } StopBenchmarkTiming(); }); diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp index dfa70ace2f44..9637117bc11a 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -125,6 +125,21 @@ void RenderState::bindFramebuffer(GLuint fbo) { } } +GLuint RenderState::genFramebuffer() { + GLuint ret; + glGenFramebuffers(1, &ret); + return ret; +} + +void RenderState::deleteFramebuffer(GLuint fbo) { + if (mFramebuffer == fbo) { + // GL defines that deleting the currently bound FBO rebinds FBO 0. + // Reflect this in our cached value. + mFramebuffer = 0; + } + glDeleteFramebuffers(1, &fbo); +} + void RenderState::invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info) { if (mode == DrawGlInfo::kModeProcessNoContext) { // If there's no context we don't need to interrupt as there's diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index 9ae084506f1d..3cda1708da75 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -16,24 +16,25 @@ #ifndef RENDERSTATE_H #define RENDERSTATE_H -#include <set> -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> -#include <utils/Mutex.h> -#include <utils/Functor.h> -#include <utils/RefBase.h> -#include <private/hwui/DrawGlInfo.h> -#include <renderstate/Blend.h> - #include "AssetAtlas.h" #include "Caches.h" #include "Glop.h" +#include "renderstate/Blend.h" #include "renderstate/MeshState.h" #include "renderstate/PixelBufferState.h" #include "renderstate/Scissor.h" #include "renderstate/Stencil.h" #include "utils/Macros.h" +#include <set> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <ui/Region.h> +#include <utils/Mutex.h> +#include <utils/Functor.h> +#include <utils/RefBase.h> +#include <private/hwui/DrawGlInfo.h> + namespace android { namespace uirenderer { @@ -49,6 +50,8 @@ class RenderThread; // wrapper of Caches for users to migrate to. class RenderState { PREVENT_COPY_AND_ASSIGN(RenderState); + friend class renderthread::RenderThread; + friend class Caches; public: void onGLContextCreated(); void onGLContextDestroyed(); @@ -57,7 +60,9 @@ public: void getViewport(GLsizei* outWidth, GLsizei* outHeight); void bindFramebuffer(GLuint fbo); - GLint getFramebuffer() { return mFramebuffer; } + GLuint getFramebuffer() { return mFramebuffer; } + GLuint genFramebuffer(); + void deleteFramebuffer(GLuint fbo); void invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info); @@ -93,10 +98,8 @@ public: Stencil& stencil() { return *mStencil; } void dump(); -private: - friend class renderthread::RenderThread; - friend class Caches; +private: void interruptForFunctorInvoke(); void resumeFromFunctorInvoke(); void assertOnGLThread(); diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index f5714265032a..fac26dc2bfa2 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -27,6 +27,7 @@ #include "renderstate/RenderState.h" #include "renderstate/Stencil.h" #include "protos/hwui.pb.h" +#include "utils/TimeUtils.h" #if HWUI_NEW_OPS #include "BakedOpRenderer.h" @@ -108,6 +109,7 @@ void CanvasContext::setSurface(ANativeWindow* window) { const bool preserveBuffer = (mSwapBehavior != kSwap_discardBuffer); mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); mHaveNewSurface = true; + mSwapHistory.clear(); makeCurrent(); } else { mRenderThread.removeFrameCallback(this); @@ -197,7 +199,6 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, info.damageAccumulator = &mDamageAccumulator; info.renderer = mCanvas; - info.canvasContext = this; mAnimationContext->startFrame(info.mode); for (const sp<RenderNode>& node : mRenderNodes) { @@ -217,13 +218,30 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, return; } - int runningBehind = 0; - // TODO: This query is moderately expensive, investigate adding some sort - // of fast-path based off when we last called eglSwapBuffers() as well as - // last vsync time. Or something. - mNativeWindow->query(mNativeWindow.get(), - NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind); - info.out.canDrawThisFrame = !runningBehind; + if (CC_LIKELY(mSwapHistory.size())) { + nsecs_t latestVsync = mRenderThread.timeLord().latestVsync(); + const SwapHistory& lastSwap = mSwapHistory.back(); + int vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync); + // The slight fudge-factor is to deal with cases where + // the vsync was estimated due to being slow handling the signal. + // See the logic in TimeLord#computeFrameTimeNanos or in + // Choreographer.java for details on when this happens + if (vsyncDelta < 2_ms) { + // Already drew for this vsync pulse, UI draw request missed + // the deadline for RT animations + info.out.canDrawThisFrame = false; + } else if (lastSwap.swapTime < latestVsync) { + info.out.canDrawThisFrame = true; + } else { + // We're maybe behind? Find out for sure + int runningBehind = 0; + mNativeWindow->query(mNativeWindow.get(), + NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind); + info.out.canDrawThisFrame = !runningBehind; + } + } else { + info.out.canDrawThisFrame = true; + } if (!info.out.canDrawThisFrame) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); @@ -266,11 +284,11 @@ void CanvasContext::draw() { Frame frame = mEglManager.beginFrame(mEglSurface); - if (frame.width() != lastFrameWidth || frame.height() != lastFrameHeight) { + if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) { // can't rely on prior content of window if viewport size changes dirty.setEmpty(); - lastFrameWidth = frame.width(); - lastFrameHeight = frame.height(); + mLastFrameWidth = frame.width(); + mLastFrameHeight = frame.height(); } else if (mHaveNewSurface || frame.bufferAge() == 0) { // New surface needs a full draw dirty.setEmpty(); @@ -297,7 +315,7 @@ void CanvasContext::draw() { // last frame so there's nothing to union() against // Therefore we only care about the > 1 case. if (frame.bufferAge() > 1) { - if (frame.bufferAge() > (int) mDamageHistory.size()) { + if (frame.bufferAge() > (int) mSwapHistory.size()) { // We don't have enough history to handle this old of a buffer // Just do a full-draw dirty.set(0, 0, frame.width(), frame.height()); @@ -305,27 +323,22 @@ void CanvasContext::draw() { // At this point we haven't yet added the latest frame // to the damage history (happens below) // So we need to damage - for (int i = mDamageHistory.size() - 1; - i > ((int) mDamageHistory.size()) - frame.bufferAge(); i--) { - dirty.join(mDamageHistory[i]); + for (int i = mSwapHistory.size() - 1; + i > ((int) mSwapHistory.size()) - frame.bufferAge(); i--) { + dirty.join(mSwapHistory[i].damage); } } } - // Add the screen damage to the ring buffer. - mDamageHistory.next() = screenDirty; - mEglManager.damageFrame(frame, dirty); #if HWUI_NEW_OPS - OpReorderer reorderer; - reorderer.defer(dirty, frame.width(), frame.height(), mRenderNodes); - BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(), - frame.width(), frame.height(), mOpaque); + OpReorderer reorderer(dirty, frame.width(), frame.height(), mRenderNodes); + BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), mOpaque); // TODO: profiler().draw(mCanvas); - reorderer.replayBakedOps<BakedOpRenderer>(info); + reorderer.replayBakedOps<BakedOpDispatcher>(renderer); - bool drew = info.didDraw; + bool drew = renderer.didDraw(); #else mCanvas->prepareDirty(frame.width(), frame.height(), @@ -447,6 +460,10 @@ void CanvasContext::draw() { if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) { setSurface(nullptr); } + SwapHistory& swap = mSwapHistory.next(); + swap.damage = screenDirty; + swap.swapTime = systemTime(CLOCK_MONOTONIC); + swap.vsyncTime = mRenderThread.timeLord().latestVsync(); mHaveNewSurface = false; } @@ -489,7 +506,7 @@ void CanvasContext::prepareAndDraw(RenderNode* node) { .setVsync(mRenderThread.timeLord().computeFrameTimeNanos(), mRenderThread.timeLord().latestVsync()); - TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState()); + TreeInfo info(TreeInfo::MODE_RT_ONLY, *this); prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC), node); if (info.out.canDrawThisFrame) { draw(); @@ -533,7 +550,7 @@ void CanvasContext::buildLayer(RenderNode* node) { // buildLayer() will leave the tree in an unknown state, so we must stop drawing stopDrawing(); - TreeInfo info(TreeInfo::MODE_FULL, mRenderThread.renderState()); + TreeInfo info(TreeInfo::MODE_FULL, *this); info.damageAccumulator = &mDamageAccumulator; info.renderer = mCanvas; info.runAnimations = false; diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 16956e6f1e47..30e6562526d5 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -130,6 +130,10 @@ public: mContentDrawBounds.set(left, top, right, bottom); } + RenderState& getRenderState() { + return mRenderThread.renderState(); + } + private: friend class RegisterFrameCallbackTask; // TODO: Replace with something better for layer & other GL object @@ -141,8 +145,8 @@ private: void freePrefetechedLayers(); - int lastFrameWidth = 0; - int lastFrameHeight = 0; + EGLint mLastFrameWidth = 0; + EGLint mLastFrameHeight = 0; RenderThread& mRenderThread; EglManager& mEglManager; @@ -150,7 +154,13 @@ private: EGLSurface mEglSurface = EGL_NO_SURFACE; bool mBufferPreserved = false; SwapBehavior mSwapBehavior = kSwap_default; - RingBuffer<SkRect, 3> mDamageHistory; + struct SwapHistory { + SkRect damage; + nsecs_t vsyncTime; + nsecs_t swapTime; + }; + + RingBuffer<SwapHistory, 3> mSwapHistory; bool mOpaque; OpenGLRenderer* mCanvas = nullptr; diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index a47c9ecf8b31..ab860c7d3b32 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -87,7 +87,7 @@ void DrawFrameTask::run() { bool canUnblockUiThread; bool canDrawThisFrame; { - TreeInfo info(TreeInfo::MODE_FULL, mRenderThread->renderState()); + TreeInfo info(TreeInfo::MODE_FULL, *mContext); canUnblockUiThread = syncFrameState(info); canDrawThisFrame = info.out.canDrawThisFrame; } diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index b353ae6ec1fc..485759bd1a50 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -333,7 +333,7 @@ bool EglManager::swapBuffers(const Frame& frame, const SkRect& screenDirty) { if (CC_LIKELY(err == EGL_SUCCESS)) { return true; } - if (err == EGL_BAD_SURFACE) { + if (err == EGL_BAD_SURFACE || err == EGL_BAD_NATIVE_WINDOW) { // For some reason our surface was destroyed out from under us // This really shouldn't happen, but if it does we can recover easily // by just not trying to use the surface anymore diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp index 891af9171518..2eefd37561a6 100644 --- a/libs/hwui/tests/TreeContentAnimation.cpp +++ b/libs/hwui/tests/TreeContentAnimation.cpp @@ -54,15 +54,10 @@ public: } }; -static TestCanvas* startRecording(RenderNode* node) { - TestCanvas* renderer = new TestCanvas( - node->stagingProperties().getWidth(), node->stagingProperties().getHeight()); - return renderer; -} - -static void endRecording(TestCanvas* renderer, RenderNode* node) { - node->setStagingDisplayList(renderer->finishRecording()); - delete renderer; +static void recordNode(RenderNode& node, std::function<void(TestCanvas&)> contentCallback) { + TestCanvas canvas(node.stagingProperties().getWidth(), node.stagingProperties().getHeight()); + contentCallback(canvas); + node.setStagingDisplayList(canvas.finishRecording()); } class TreeContentAnimation { @@ -75,7 +70,7 @@ public: frameCount = fc; } } - virtual void createContent(int width, int height, TestCanvas* renderer) = 0; + virtual void createContent(int width, int height, TestCanvas* canvas) = 0; virtual void doFrame(int frameNr) = 0; template <class T> @@ -108,11 +103,9 @@ public: proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15); proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)}); - android::uirenderer::Rect DUMMY; - - TestCanvas* renderer = startRecording(rootNode); - animation.createContent(width, height, renderer); - endRecording(renderer, rootNode); + recordNode(*rootNode, [&animation, width, height](TestCanvas& canvas) { + animation.createContent(width, height, &canvas); //TODO: no& + }); // Do a few cold runs then reset the stats so that the caches are all hot for (int i = 0; i < 3; i++) { @@ -140,19 +133,19 @@ public: class ShadowGridAnimation : public TreeContentAnimation { public: std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, TestCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); + void createContent(int width, int height, TestCanvas* canvas) override { + canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas->insertReorderBarrier(true); for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { sp<RenderNode> card = createCard(x, y, dp(100), dp(100)); - renderer->drawRenderNode(card.get()); + canvas->drawRenderNode(card.get()); cards.push_back(card); } } - renderer->insertReorderBarrier(false); + canvas->insertReorderBarrier(false); } void doFrame(int frameNr) override { int curFrame = frameNr % 150; @@ -171,9 +164,9 @@ private: node->mutateStagingProperties().mutableOutline().setShouldClip(true); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); - TestCanvas* renderer = startRecording(node.get()); - renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); - endRecording(renderer, node.get()); + recordNode(*node, [](TestCanvas& canvas) { + canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); + }); return node; } }; @@ -187,19 +180,19 @@ static Benchmark _ShadowGrid(BenchmarkInfo{ class ShadowGrid2Animation : public TreeContentAnimation { public: std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, TestCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); + void createContent(int width, int height, TestCanvas* canvas) override { + canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas->insertReorderBarrier(true); for (int x = dp(8); x < (width - dp(58)); x += dp(58)) { for (int y = dp(8); y < (height - dp(58)); y += dp(58)) { sp<RenderNode> card = createCard(x, y, dp(50), dp(50)); - renderer->drawRenderNode(card.get()); + canvas->drawRenderNode(card.get()); cards.push_back(card); } } - renderer->insertReorderBarrier(false); + canvas->insertReorderBarrier(false); } void doFrame(int frameNr) override { int curFrame = frameNr % 150; @@ -218,9 +211,9 @@ private: node->mutateStagingProperties().mutableOutline().setShouldClip(true); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); - TestCanvas* renderer = startRecording(node.get()); - renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); - endRecording(renderer, node.get()); + recordNode(*node, [](TestCanvas& canvas) { + canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); + }); return node; } }; @@ -233,15 +226,30 @@ static Benchmark _ShadowGrid2(BenchmarkInfo{ class RectGridAnimation : public TreeContentAnimation { public: - sp<RenderNode> card; - void createContent(int width, int height, TestCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); + sp<RenderNode> card = new RenderNode(); + void createContent(int width, int height, TestCanvas* canvas) override { + canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas->insertReorderBarrier(true); + + card->mutateStagingProperties().setLeftTopRightBottom(50, 50, 250, 250); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + recordNode(*card, [](TestCanvas& canvas) { + canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode); + + SkRegion region; + for (int xOffset = 0; xOffset < 200; xOffset+=2) { + for (int yOffset = 0; yOffset < 200; yOffset+=2) { + region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op); + } + } - card = createCard(40, 40, 200, 200); - renderer->drawRenderNode(card.get()); + SkPaint paint; + paint.setColor(0xff00ffff); + canvas.drawRegion(region, paint); + }); + canvas->drawRenderNode(card.get()); - renderer->insertReorderBarrier(false); + canvas->insertReorderBarrier(false); } void doFrame(int frameNr) override { int curFrame = frameNr % 150; @@ -249,29 +257,6 @@ public: card->mutateStagingProperties().setTranslationY(curFrame); card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - - TestCanvas* renderer = startRecording(node.get()); - renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode); - - SkRegion region; - for (int xOffset = 0; xOffset < width; xOffset+=2) { - for (int yOffset = 0; yOffset < height; yOffset+=2) { - region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op); - } - } - - SkPaint paint; - paint.setColor(0xff00ffff); - renderer->drawRegion(region, paint); - - endRecording(renderer, node.get()); - return node; - } }; static Benchmark _RectGrid(BenchmarkInfo{ "rectgrid", @@ -282,15 +267,22 @@ static Benchmark _RectGrid(BenchmarkInfo{ class OvalAnimation : public TreeContentAnimation { public: - sp<RenderNode> card; - void createContent(int width, int height, TestCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); - - card = createCard(40, 40, 400, 400); - renderer->drawRenderNode(card.get()); + sp<RenderNode> card = new RenderNode(); + void createContent(int width, int height, TestCanvas* canvas) override { + canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas->insertReorderBarrier(true); - renderer->insertReorderBarrier(false); + card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + recordNode(*card, [](TestCanvas& canvas) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(0xFF000000); + canvas.drawOval(0, 0, 200, 200, paint); + }); + canvas->drawRenderNode(card.get()); + + canvas->insertReorderBarrier(false); } void doFrame(int frameNr) override { @@ -299,22 +291,6 @@ public: card->mutateStagingProperties().setTranslationY(curFrame); card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - - TestCanvas* renderer = startRecording(node.get()); - - SkPaint paint; - paint.setAntiAlias(true); - paint.setColor(0xFF000000); - renderer->drawOval(0, 0, width, height, paint); - - endRecording(renderer, node.get()); - return node; - } }; static Benchmark _Oval(BenchmarkInfo{ "oval", @@ -325,7 +301,7 @@ static Benchmark _Oval(BenchmarkInfo{ class PartialDamageTest : public TreeContentAnimation { public: std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, TestCanvas* renderer) override { + void createContent(int width, int height, TestCanvas* canvas) override { static SkColor COLORS[] = { 0xFFF44336, 0xFF9C27B0, @@ -333,13 +309,13 @@ public: 0xFF4CAF50, }; - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { sp<RenderNode> card = createCard(x, y, dp(100), dp(100), COLORS[static_cast<int>((y / dp(116))) % 4]); - renderer->drawRenderNode(card.get()); + canvas->drawRenderNode(card.get()); cards.push_back(card); } } @@ -350,10 +326,10 @@ public: cards[0]->mutateStagingProperties().setTranslationY(curFrame); cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - TestCanvas* renderer = startRecording(cards[0].get()); - renderer->drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0), - SkXfermode::kSrcOver_Mode); - endRecording(renderer, cards[0].get()); + recordNode(*cards[0], [curFrame](TestCanvas& canvas) { + canvas.drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0), + SkXfermode::kSrcOver_Mode); + }); } static SkColor interpolateColor(float fraction, SkColor start, SkColor end) { @@ -378,9 +354,9 @@ private: node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - TestCanvas* renderer = startRecording(node.get()); - renderer->drawColor(color, SkXfermode::kSrcOver_Mode); - endRecording(renderer, node.get()); + recordNode(*node, [color](TestCanvas& canvas) { + canvas.drawColor(color, SkXfermode::kSrcOver_Mode); + }); return node; } }; @@ -393,16 +369,24 @@ static Benchmark _PartialDamage(BenchmarkInfo{ }); -class SimpleRectGridAnimation : public TreeContentAnimation { +class SaveLayerAnimation : public TreeContentAnimation { public: - sp<RenderNode> card; - void createContent(int width, int height, TestCanvas* renderer) override { - SkPaint paint; - paint.setColor(0xFF00FFFF); - renderer->drawRect(0, 0, width, height, paint); - - card = createCard(40, 40, 200, 200); - renderer->drawRenderNode(card.get()); + sp<RenderNode> card = new RenderNode(); + void createContent(int width, int height, TestCanvas* canvas) override { + canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background + + card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + recordNode(*card, [](TestCanvas& canvas) { + canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag); + canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped + canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag); + canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); // inner, clipped + canvas.restore(); + canvas.restore(); + }); + + canvas->drawRenderNode(card.get()); } void doFrame(int frameNr) override { int curFrame = frameNr % 150; @@ -410,24 +394,10 @@ public: card->mutateStagingProperties().setTranslationY(curFrame); card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - - TestCanvas* renderer = startRecording(node.get()); - SkPaint paint; - paint.setColor(0xFFFF00FF); - renderer->drawRect(0, 0, width, height, paint); - - endRecording(renderer, node.get()); - return node; - } }; -static Benchmark _SimpleRectGrid(BenchmarkInfo{ - "simplerectgrid", - "A simple collection of rects. " - "Low CPU/GPU load.", - TreeContentAnimation::run<SimpleRectGridAnimation> +static Benchmark _SaveLayer(BenchmarkInfo{ + "savelayer", + "A nested pair of clipped saveLayer operations. " + "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.", + TreeContentAnimation::run<SaveLayerAnimation> }); diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp index 82aebeabe038..bc1b69fa3729 100644 --- a/libs/hwui/unit_tests/BakedOpStateTests.cpp +++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp @@ -85,13 +85,5 @@ TEST(BakedOpState, constructAndReject) { } } -#define UNSUPPORTED_OP(Info, Type) \ - static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); } - -class Info { -public: - int index = 0; -}; - } } diff --git a/libs/hwui/unit_tests/FatVectorTests.cpp b/libs/hwui/unit_tests/FatVectorTests.cpp new file mode 100644 index 000000000000..fb760ac549cd --- /dev/null +++ b/libs/hwui/unit_tests/FatVectorTests.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015 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. + */ + +#include <gtest/gtest.h> +#include <utils/FatVector.h> + +#include <unit_tests/TestUtils.h> + +using namespace android; +using namespace android::uirenderer; + +template<class VectorType> +static bool allocationIsInternal(VectorType& v) { + // allocation array (from &v[0] to &v[0] + v.capacity) is + // located within the vector object itself + return (char*)(&v) <= (char*)(&v[0]) + && (char*)(&v + 1) >= (char*)(&v[0] + v.capacity()); +} + +TEST(FatVector, baseline) { + // Verify allocation behavior FatVector contrasts against - allocations are always external + std::vector<int> v; + for (int i = 0; i < 50; i++) { + v.push_back(i); + EXPECT_FALSE(allocationIsInternal(v)); + } +} + +TEST(FatVector, simpleAllocate) { + FatVector<int, 4> v; + EXPECT_EQ(4u, v.capacity()); + + // can insert 4 items into internal buffer + for (int i = 0; i < 4; i++) { + v.push_back(i); + EXPECT_TRUE(allocationIsInternal(v)); + } + + // then will fall back to external allocation + for (int i = 5; i < 50; i++) { + v.push_back(i); + EXPECT_FALSE(allocationIsInternal(v)); + } +} + +TEST(FatVector, shrink) { + FatVector<int, 10> v; + EXPECT_TRUE(allocationIsInternal(v)); + + // push into external alloc + v.resize(11); + EXPECT_FALSE(allocationIsInternal(v)); + + // shrinking back to internal alloc succeeds + // note that shrinking further will succeed, but is a waste + v.resize(10); + v.shrink_to_fit(); + EXPECT_TRUE(allocationIsInternal(v)); +} + +TEST(FatVector, destructorInternal) { + int count = 0; + { + // push 1 into external allocation, verify destruction happens once + FatVector<TestUtils::SignalingDtor, 0> v; + v.emplace_back(&count); + EXPECT_FALSE(allocationIsInternal(v)); + EXPECT_EQ(0, count); + } + EXPECT_EQ(1, count); +} + +TEST(FatVector, destructorExternal) { + int count = 0; + { + // push 10 into internal allocation, verify 10 destructors called + FatVector<TestUtils::SignalingDtor, 10> v; + for (int i = 0; i < 10; i++) { + v.emplace_back(&count); + EXPECT_TRUE(allocationIsInternal(v)); + } + EXPECT_EQ(0, count); + } + EXPECT_EQ(10, count); +} diff --git a/libs/hwui/unit_tests/LinearAllocatorTests.cpp b/libs/hwui/unit_tests/LinearAllocatorTests.cpp index 02cd77ae93db..0f6b2494c237 100644 --- a/libs/hwui/unit_tests/LinearAllocatorTests.cpp +++ b/libs/hwui/unit_tests/LinearAllocatorTests.cpp @@ -17,6 +17,8 @@ #include <gtest/gtest.h> #include <utils/LinearAllocator.h> +#include <unit_tests/TestUtils.h> + using namespace android; using namespace android::uirenderer; @@ -25,27 +27,6 @@ struct SimplePair { int two = 2; }; -class SignalingDtor { -public: - SignalingDtor() { - mDestroyed = nullptr; - } - SignalingDtor(bool* destroyedSignal) { - mDestroyed = destroyedSignal; - *mDestroyed = false; - } - virtual ~SignalingDtor() { - if (mDestroyed) { - *mDestroyed = true; - } - } - void setSignal(bool* destroyedSignal) { - mDestroyed = destroyedSignal; - } -private: - bool* mDestroyed; -}; - TEST(LinearAllocator, alloc) { LinearAllocator la; EXPECT_EQ(0u, la.usedSize()); @@ -62,31 +43,31 @@ TEST(LinearAllocator, alloc) { } TEST(LinearAllocator, dtor) { - bool destroyed[10]; + int destroyed[10] = { 0 }; { LinearAllocator la; for (int i = 0; i < 5; i++) { - la.alloc<SignalingDtor>()->setSignal(destroyed + i); + la.alloc<TestUtils::SignalingDtor>()->setSignal(destroyed + i); la.alloc<SimplePair>(); } la.alloc(100); for (int i = 0; i < 5; i++) { - auto sd = new (la) SignalingDtor(destroyed + 5 + i); + auto sd = new (la) TestUtils::SignalingDtor(destroyed + 5 + i); la.autoDestroy(sd); new (la) SimplePair(); } la.alloc(100); for (int i = 0; i < 10; i++) { - EXPECT_FALSE(destroyed[i]); + EXPECT_EQ(0, destroyed[i]); } } for (int i = 0; i < 10; i++) { - EXPECT_TRUE(destroyed[i]); + EXPECT_EQ(1, destroyed[i]); } } TEST(LinearAllocator, rewind) { - bool destroyed; + int destroyed = 0; { LinearAllocator la; auto addr = la.alloc(100); @@ -94,17 +75,16 @@ TEST(LinearAllocator, rewind) { la.rewindIfLastAlloc(addr, 100); EXPECT_GT(16u, la.usedSize()); size_t emptySize = la.usedSize(); - auto sigdtor = la.alloc<SignalingDtor>(); + auto sigdtor = la.alloc<TestUtils::SignalingDtor>(); sigdtor->setSignal(&destroyed); - EXPECT_FALSE(destroyed); + EXPECT_EQ(0, destroyed); EXPECT_LE(emptySize, la.usedSize()); la.rewindIfLastAlloc(sigdtor); - EXPECT_TRUE(destroyed); + EXPECT_EQ(1, destroyed); EXPECT_EQ(emptySize, la.usedSize()); - destroyed = false; } // Checking for a double-destroy case - EXPECT_EQ(destroyed, false); + EXPECT_EQ(1, destroyed); } TEST(LinearStdAllocator, simpleAllocate) { diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp index d02f89dd7088..ffb575f2ddd9 100644 --- a/libs/hwui/unit_tests/OpReordererTests.cpp +++ b/libs/hwui/unit_tests/OpReordererTests.cpp @@ -27,68 +27,67 @@ namespace android { namespace uirenderer { + /** - * Class that redirects static operation dispatch to virtual methods on a Client class. + * Virtual class implemented by each test to redirect static operation / state transitions to + * virtual methods. * - * The client is recreated for every op (so data cannot be persisted between operations), but the - * virtual dispatch allows for default behaviors to be specified without enumerating each operation - * for every test. + * Virtual dispatch allows for default behaviors to be specified (very common case in below tests), + * and allows Renderer vs Dispatching behavior to be merged. * * onXXXOp methods fail by default - tests should override ops they expect + * startLayer fails by default - tests should override if expected * startFrame/endFrame do nothing by default - tests should override to intercept */ -template<class CustomClient, class Arg> -class TestReceiver { +class TestRendererBase { public: -#define CLIENT_METHOD(Type) \ - virtual void on##Type(Arg&, const Type&, const BakedOpState&) { FAIL(); } - class Client { - public: - virtual ~Client() {}; - MAP_OPS(CLIENT_METHOD) - - virtual void startFrame(Arg& info) {} - virtual void endFrame(Arg& info) {} - }; + virtual ~TestRendererBase() {} + virtual OffscreenBuffer* startLayer(uint32_t width, uint32_t height) { ADD_FAILURE(); return nullptr; } + virtual void endLayer() { ADD_FAILURE(); } + virtual void startFrame(uint32_t width, uint32_t height) {} + virtual void endFrame() {} + + // define virtual defaults for direct +#define BASE_OP_METHOD(Type) \ + virtual void on##Type(const Type&, const BakedOpState&) { ADD_FAILURE(); } + MAP_OPS(BASE_OP_METHOD) + int getIndex() { return mIndex; } + +protected: + int mIndex = 0; +}; +/** + * Dispatches all static methods to similar formed methods on renderer, which fail by default but + * are overriden by subclasses per test. + */ +class TestDispatcher { +public: #define DISPATCHER_METHOD(Type) \ - static void on##Type(Arg& arg, const Type& op, const BakedOpState& state) { \ - CustomClient client; client.on##Type(arg, op, state); \ - } - MAP_OPS(DISPATCHER_METHOD) - - static void startFrame(Arg& info) { - CustomClient client; - client.startFrame(info); - } - - static void endFrame(Arg& info) { - CustomClient client; - client.endFrame(info); + static void on##Type(TestRendererBase& renderer, const Type& op, const BakedOpState& state) { \ + renderer.on##Type(op, state); \ } + MAP_OPS(DISPATCHER_METHOD); }; -class Info { -public: - int index = 0; -}; -// Receiver class which will fail if it receives any ops -class FailReceiver : public TestReceiver<FailReceiver, Info>::Client {}; +class FailRenderer : public TestRendererBase {}; -class SimpleReceiver : public TestReceiver<SimpleReceiver, Info>::Client { +class SimpleTestRenderer : public TestRendererBase { public: - void startFrame(Info& info) override { - EXPECT_EQ(0, info.index++); + void startFrame(uint32_t width, uint32_t height) override { + EXPECT_EQ(0, mIndex++); + EXPECT_EQ(100u, width); + EXPECT_EQ(200u, height); } - void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override { - EXPECT_EQ(1, info.index++); + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); } - void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override { - EXPECT_EQ(2, info.index++); + void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { + EXPECT_EQ(2, mIndex++); } - void endFrame(Info& info) override { - EXPECT_EQ(3, info.index++); + void endFrame() override { + EXPECT_EQ(3, mIndex++); } }; TEST(OpReorderer, simple) { @@ -97,12 +96,11 @@ TEST(OpReorderer, simple) { canvas.drawRect(0, 0, 100, 200, SkPaint()); canvas.drawBitmap(bitmap, 10, 10, nullptr); }); - OpReorderer reorderer; - reorderer.defer(200, 200, *dl); + OpReorderer reorderer(100, 200, *dl); - Info info; - reorderer.replayBakedOps<TestReceiver<SimpleReceiver, Info>>(info); - EXPECT_EQ(4, info.index); // 2 ops + start + end + SimpleTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end } @@ -113,22 +111,21 @@ TEST(OpReorderer, simpleRejection) { canvas.drawRect(0, 0, 400, 400, SkPaint()); canvas.restore(); }); - OpReorderer reorderer; - reorderer.defer(200, 200, *dl); + OpReorderer reorderer(200, 200, *dl); - Info info; - reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info); + FailRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); } static int SIMPLE_BATCHING_LOOPS = 5; -class SimpleBatchingReceiver : public TestReceiver<SimpleBatchingReceiver, Info>::Client { +class SimpleBatchingTestRenderer : public TestRendererBase { public: - void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override { - EXPECT_TRUE(info.index++ >= SIMPLE_BATCHING_LOOPS); + void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { + EXPECT_TRUE(mIndex++ >= SIMPLE_BATCHING_LOOPS); } - void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override { - EXPECT_TRUE(info.index++ < SIMPLE_BATCHING_LOOPS); + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_TRUE(mIndex++ < SIMPLE_BATCHING_LOOPS); } }; TEST(OpReorderer, simpleBatching) { @@ -146,18 +143,17 @@ TEST(OpReorderer, simpleBatching) { canvas.restore(); }); - OpReorderer reorderer; - reorderer.defer(200, 200, *dl); + OpReorderer reorderer(200, 200, *dl); - Info info; - reorderer.replayBakedOps<TestReceiver<SimpleBatchingReceiver, Info>>(info); - EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, info.index); // 2 x loops ops, because no merging (TODO: force no merging) + SimpleBatchingTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, renderer.getIndex()); // 2 x loops ops, because no merging (TODO: force no merging) } -class RenderNodeReceiver : public TestReceiver<RenderNodeReceiver, Info>::Client { +class RenderNodeTestRenderer : public TestRendererBase { public: - void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override { - switch(info.index++) { + void onRectOp(const RectOp& op, const BakedOpState& state) override { + switch(mIndex++) { case 0: EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clippedBounds); EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor()); @@ -167,7 +163,7 @@ public: EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); break; default: - FAIL(); + ADD_FAILURE(); } } }; @@ -196,17 +192,16 @@ TEST(OpReorderer, renderNode) { std::vector< sp<RenderNode> > nodes; nodes.push_back(parent.get()); - OpReorderer reorderer; - reorderer.defer(SkRect::MakeWH(200, 200), 200, 200, nodes); + OpReorderer reorderer(SkRect::MakeWH(200, 200), 200, 200, nodes); - Info info; - reorderer.replayBakedOps<TestReceiver<RenderNodeReceiver, Info>>(info); + RenderNodeTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); } -class ClippedReceiver : public TestReceiver<ClippedReceiver, Info>::Client { +class ClippedTestRenderer : public TestRendererBase { public: - void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override { - EXPECT_EQ(0, info.index++); + void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds); EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect); EXPECT_TRUE(state.computedState.transform.isIdentity()); @@ -221,19 +216,27 @@ TEST(OpReorderer, clipped) { std::vector< sp<RenderNode> > nodes; nodes.push_back(node.get()); - OpReorderer reorderer; - reorderer.defer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver + OpReorderer reorderer(SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver 200, 200, nodes); - Info info; - reorderer.replayBakedOps<TestReceiver<ClippedReceiver, Info>>(info); + ClippedTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); } -class SaveLayerSimpleReceiver : public TestReceiver<SaveLayerSimpleReceiver, Info>::Client { +class SaveLayerSimpleTestRenderer : public TestRendererBase { public: - void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override { - EXPECT_EQ(0, info.index++); + OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override { + EXPECT_EQ(0, mIndex++); + EXPECT_EQ(180u, width); + EXPECT_EQ(180u, height); + return nullptr; + } + void endLayer() override { + EXPECT_EQ(2, mIndex++); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds); EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clippedBounds); EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clipRect); @@ -242,8 +245,8 @@ public: expectedTransform.loadTranslate(-10, -10, 0); EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform); } - void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override { - EXPECT_EQ(1, info.index++); + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(3, mIndex++); EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds); EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clipRect); EXPECT_TRUE(state.computedState.transform.isIdentity()); @@ -256,33 +259,61 @@ TEST(OpReorderer, saveLayerSimple) { canvas.restore(); }); - OpReorderer reorderer; - reorderer.defer(200, 200, *dl); + OpReorderer reorderer(200, 200, *dl); - Info info; - reorderer.replayBakedOps<TestReceiver<SaveLayerSimpleReceiver, Info>>(info); - EXPECT_EQ(2, info.index); + SaveLayerSimpleTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); } -// saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as rect2, rect1, layerOp2, layerOp1 -class SaveLayerNestedReceiver : public TestReceiver<SaveLayerNestedReceiver, Info>::Client { +/* saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as: + * - startLayer2, rect2 endLayer2 + * - startLayer1, rect1, drawLayer2, endLayer1 + * - startFrame, layerOp1, endFrame + */ +class SaveLayerNestedTestRenderer : public TestRendererBase { public: - void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override { - const int index = info.index++; + OffscreenBuffer* startLayer(uint32_t width, uint32_t height) override { + const int index = mIndex++; if (index == 0) { + EXPECT_EQ(400u, width); + EXPECT_EQ(400u, height); + return (OffscreenBuffer*) 0x400; + } else if (index == 3) { + EXPECT_EQ(800u, width); + EXPECT_EQ(800u, height); + return (OffscreenBuffer*) 0x800; + } else { ADD_FAILURE(); } + return (OffscreenBuffer*) nullptr; + } + void endLayer() override { + int index = mIndex++; + EXPECT_TRUE(index == 2 || index == 6); + } + void startFrame(uint32_t width, uint32_t height) override { + EXPECT_EQ(7, mIndex++); + } + void endFrame() override { + EXPECT_EQ(9, mIndex++); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + const int index = mIndex++; + if (index == 1) { EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect - } else if (index == 1) { + } else if (index == 4) { EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect - } else { FAIL(); } + } else { ADD_FAILURE(); } } - void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override { - const int index = info.index++; - if (index == 2) { + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + const int index = mIndex++; + if (index == 5) { + EXPECT_EQ((OffscreenBuffer*)0x400, *op.layerHandle); EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer - } else if (index == 3) { + } else if (index == 8) { + EXPECT_EQ((OffscreenBuffer*)0x800, *op.layerHandle); EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer - } else { FAIL(); } + } else { ADD_FAILURE(); } } }; TEST(OpReorderer, saveLayerNested) { @@ -299,12 +330,11 @@ TEST(OpReorderer, saveLayerNested) { canvas.restore(); }); - OpReorderer reorderer; - reorderer.defer(800, 800, *dl); + OpReorderer reorderer(800, 800, *dl); - Info info; - reorderer.replayBakedOps<TestReceiver<SaveLayerNestedReceiver, Info>>(info); - EXPECT_EQ(4, info.index); + SaveLayerNestedTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(10, renderer.getIndex()); } TEST(OpReorderer, saveLayerContentRejection) { @@ -319,12 +349,11 @@ TEST(OpReorderer, saveLayerContentRejection) { canvas.restore(); canvas.restore(); }); - OpReorderer reorderer; - reorderer.defer(200, 200, *dl); - Info info; + OpReorderer reorderer(200, 200, *dl); + FailRenderer renderer; // should see no ops, even within the layer, since the layer should be rejected - reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info); + reorderer.replayBakedOps<TestDispatcher>(renderer); } } // namespace uirenderer diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp index c0231235027a..e8cdf461c783 100644 --- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp +++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp @@ -38,7 +38,7 @@ TEST(RecordingCanvas, emptyPlayback) { canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); canvas.restore(); }); - playbackOps(*dl, [](const RecordedOp& op) { FAIL(); }); + playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); }); } TEST(RecordingCanvas, testSimpleRectRecord) { @@ -135,7 +135,7 @@ TEST(RecordingCanvas, saveLayerSimple) { // TODO: add asserts break; default: - FAIL(); + ADD_FAILURE(); } }); EXPECT_EQ(3, count); diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h index 99ecc9b99284..5b09fdac4ff2 100644 --- a/libs/hwui/unit_tests/TestUtils.h +++ b/libs/hwui/unit_tests/TestUtils.h @@ -39,6 +39,24 @@ namespace uirenderer { class TestUtils { public: + class SignalingDtor { + public: + SignalingDtor() + : mSignal(nullptr) {} + SignalingDtor(int* signal) + : mSignal(signal) {} + void setSignal(int* signal) { + mSignal = signal; + } + ~SignalingDtor() { + if (mSignal) { + (*mSignal)++; + } + } + private: + int* mSignal; + }; + static bool matricesAreApproxEqual(const Matrix4& a, const Matrix4& b) { for (int i = 0; i < 16; i++) { if (!MathUtils::areEqual(a[i], b[i])) { diff --git a/libs/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h new file mode 100644 index 000000000000..c3c16c5ae27d --- /dev/null +++ b/libs/hwui/utils/FatVector.h @@ -0,0 +1,101 @@ +/* + * Copyright 2015, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ANDROID_FAT_VECTOR_H +#define ANDROID_FAT_VECTOR_H + +#include "utils/Macros.h" + +#include <stddef.h> +#include <type_traits> +#include <utils/Log.h> + +#include <vector> + +namespace android { +namespace uirenderer { + +template <typename T, size_t SIZE> +class InlineStdAllocator { +public: + struct Allocation { + PREVENT_COPY_AND_ASSIGN(Allocation); + public: + Allocation() {}; + // char array instead of T array, so memory is uninitialized, with no destructors run + char array[sizeof(T) * SIZE]; + bool inUse = false; + }; + + typedef T value_type; // needed to implement std::allocator + typedef T* pointer; // needed to implement std::allocator + + InlineStdAllocator(Allocation& allocation) + : mAllocation(allocation) {} + InlineStdAllocator(const InlineStdAllocator& other) + : mAllocation(other.mAllocation) {} + ~InlineStdAllocator() {} + + T* allocate(size_t num, const void* = 0) { + if (!mAllocation.inUse && num <= SIZE) { + mAllocation.inUse = true; + return (T*) mAllocation.array; + } else { + return (T*) malloc(num * sizeof(T)); + } + } + + void deallocate(pointer p, size_t num) { + if (p == (T*)mAllocation.array) { + mAllocation.inUse = false; + } else { + // 'free' instead of delete here - destruction handled separately + free(p); + } + } + Allocation& mAllocation; +}; + +/** + * std::vector with SIZE elements preallocated into an internal buffer. + * + * Useful for avoiding the cost of malloc in cases where only SIZE or + * fewer elements are needed in the common case. + */ +template <typename T, size_t SIZE> +class FatVector : public std::vector<T, InlineStdAllocator<T, SIZE>> { +public: + FatVector() : std::vector<T, InlineStdAllocator<T, SIZE>>( + InlineStdAllocator<T, SIZE>(mAllocation)) { + this->reserve(SIZE); + } +private: + typename InlineStdAllocator<T, SIZE>::Allocation mAllocation; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_FAT_VECTOR_H diff --git a/libs/hwui/utils/NinePatchImpl.cpp b/libs/hwui/utils/NinePatchImpl.cpp index f51f5df2402c..985f3fb66814 100644 --- a/libs/hwui/utils/NinePatchImpl.cpp +++ b/libs/hwui/utils/NinePatchImpl.cpp @@ -82,7 +82,7 @@ static void drawStretchyPatch(SkCanvas* canvas, SkIRect& src, const SkRect& dst, } } else { SLOW_CASE: - canvas->drawBitmapRect(bitmap, &src, dst, &paint); + canvas->drawBitmapRect(bitmap, SkRect::Make(src), dst, &paint); } } diff --git a/libs/hwui/utils/TimeUtils.h b/libs/hwui/utils/TimeUtils.h new file mode 100644 index 000000000000..8d42d7e55521 --- /dev/null +++ b/libs/hwui/utils/TimeUtils.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015 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. + */ +#ifndef UTILS_TIMEUTILS_H +#define UTILS_TIMEUTILS_H + +#include <utils/Timers.h> + +namespace android { +namespace uirenderer { + +constexpr nsecs_t operator"" _ms (unsigned long long ms) { + return milliseconds_to_nanoseconds(ms); +} + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif /* UTILS_TIMEUTILS_H */ diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java index bf3387bb8059..4d0d1bd64b0e 100644 --- a/location/java/android/location/Location.java +++ b/location/java/android/location/Location.java @@ -78,32 +78,51 @@ public class Location implements Parcelable { */ public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation"; + /** + * Bit mask for mFieldsMask indicating the presence of mAltitude. + */ + private static final byte HAS_ALTITUDE_MASK = 1; + /** + * Bit mask for mFieldsMask indicating the presence of mSpeed. + */ + private static final byte HAS_SPEED_MASK = 2; + /** + * Bit mask for mFieldsMask indicating the presence of mBearing. + */ + private static final byte HAS_BEARING_MASK = 4; + /** + * Bit mask for mFieldsMask indicating the presence of mAccuracy. + */ + private static final byte HAS_ACCURACY_MASK = 8; + /** + * Bit mask for mFieldsMask indicating location is from a mock provider. + */ + private static final byte HAS_MOCK_PROVIDER_MASK = 16; + + // Cached data to make bearing/distance computations more efficient for the case + // where distanceTo and bearingTo are called in sequence. Assume this typically happens + // on the same thread for caching purposes. + private static ThreadLocal<BearingDistanceCache> sBearingDistanceCache + = new ThreadLocal<BearingDistanceCache>() { + @Override + protected BearingDistanceCache initialValue() { + return new BearingDistanceCache(); + } + }; + private String mProvider; private long mTime = 0; private long mElapsedRealtimeNanos = 0; private double mLatitude = 0.0; private double mLongitude = 0.0; - private boolean mHasAltitude = false; private double mAltitude = 0.0f; - private boolean mHasSpeed = false; private float mSpeed = 0.0f; - private boolean mHasBearing = false; private float mBearing = 0.0f; - private boolean mHasAccuracy = false; private float mAccuracy = 0.0f; private Bundle mExtras = null; - private boolean mIsFromMockProvider = false; - // Cache the inputs and outputs of computeDistanceAndBearing - // so calls to distanceTo() and bearingTo() can share work - private double mLat1 = 0.0; - private double mLon1 = 0.0; - private double mLat2 = 0.0; - private double mLon2 = 0.0; - private float mDistance = 0.0f; - private float mInitialBearing = 0.0f; - // Scratchpad - private final float[] mResults = new float[2]; + // A bitmask of fields present in this object (see HAS_* constants defined above). + private byte mFieldsMask = 0; /** * Construct a new Location with a named provider. @@ -131,18 +150,14 @@ public class Location implements Parcelable { mProvider = l.mProvider; mTime = l.mTime; mElapsedRealtimeNanos = l.mElapsedRealtimeNanos; + mFieldsMask = l.mFieldsMask; mLatitude = l.mLatitude; mLongitude = l.mLongitude; - mHasAltitude = l.mHasAltitude; mAltitude = l.mAltitude; - mHasSpeed = l.mHasSpeed; mSpeed = l.mSpeed; - mHasBearing = l.mHasBearing; mBearing = l.mBearing; - mHasAccuracy = l.mHasAccuracy; mAccuracy = l.mAccuracy; mExtras = (l.mExtras == null) ? null : new Bundle(l.mExtras); - mIsFromMockProvider = l.mIsFromMockProvider; } /** @@ -152,18 +167,14 @@ public class Location implements Parcelable { mProvider = null; mTime = 0; mElapsedRealtimeNanos = 0; + mFieldsMask = 0; mLatitude = 0; mLongitude = 0; - mHasAltitude = false; mAltitude = 0; - mHasSpeed = false; mSpeed = 0; - mHasBearing = false; mBearing = 0; - mHasAccuracy = false; mAccuracy = 0; mExtras = null; - mIsFromMockProvider = false; } /** @@ -257,11 +268,13 @@ public class Location implements Parcelable { int deg = Integer.parseInt(degrees); double min; double sec = 0.0; + boolean secPresent = false; if (st.hasMoreTokens()) { min = Integer.parseInt(minutes); String seconds = st.nextToken(); sec = Double.parseDouble(seconds); + secPresent = true; } else { min = Double.parseDouble(minutes); } @@ -273,11 +286,15 @@ public class Location implements Parcelable { if ((deg < 0.0) || (deg > 179 && !isNegative180)) { throw new IllegalArgumentException("coordinate=" + coordinate); } - if (min < 0 || min > 59) { + + // min must be in [0, 59] if seconds are present, otherwise [0.0, 60.0) + if (min < 0 || min >= 60 || (secPresent && (min > 59))) { throw new IllegalArgumentException("coordinate=" + coordinate); } - if (sec < 0 || sec > 59) { + + // sec must be in [0.0, 60.0) + if (sec < 0 || sec >= 60) { throw new IllegalArgumentException("coordinate=" + coordinate); } @@ -291,7 +308,7 @@ public class Location implements Parcelable { } private static void computeDistanceAndBearing(double lat1, double lon1, - double lat2, double lon2, float[] results) { + double lat2, double lon2, BearingDistanceCache results) { // Based on http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf // using the "Inverse Formula" (section 4) @@ -376,19 +393,19 @@ public class Location implements Parcelable { } float distance = (float) (b * A * (sigma - deltaSigma)); - results[0] = distance; - if (results.length > 1) { - float initialBearing = (float) Math.atan2(cosU2 * sinLambda, - cosU1 * sinU2 - sinU1 * cosU2 * cosLambda); - initialBearing *= 180.0 / Math.PI; - results[1] = initialBearing; - if (results.length > 2) { - float finalBearing = (float) Math.atan2(cosU1 * sinLambda, - -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda); - finalBearing *= 180.0 / Math.PI; - results[2] = finalBearing; - } - } + results.mDistance = distance; + float initialBearing = (float) Math.atan2(cosU2 * sinLambda, + cosU1 * sinU2 - sinU1 * cosU2 * cosLambda); + initialBearing *= 180.0 / Math.PI; + results.mInitialBearing = initialBearing; + float finalBearing = (float) Math.atan2(cosU1 * sinLambda, + -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda); + finalBearing *= 180.0 / Math.PI; + results.mFinalBearing = finalBearing; + results.mLat1 = lat1; + results.mLat2 = lat2; + results.mLon1 = lon1; + results.mLon2 = lon2; } /** @@ -414,8 +431,16 @@ public class Location implements Parcelable { if (results == null || results.length < 1) { throw new IllegalArgumentException("results is null or has length < 1"); } + BearingDistanceCache cache = sBearingDistanceCache.get(); computeDistanceAndBearing(startLatitude, startLongitude, - endLatitude, endLongitude, results); + endLatitude, endLongitude, cache); + results[0] = cache.mDistance; + if (results.length > 1) { + results[1] = cache.mInitialBearing; + if (results.length > 2) { + results[2] = cache.mFinalBearing; + } + } } /** @@ -427,21 +452,14 @@ public class Location implements Parcelable { * @return the approximate distance in meters */ public float distanceTo(Location dest) { + BearingDistanceCache cache = sBearingDistanceCache.get(); // See if we already have the result - synchronized (mResults) { - if (mLatitude != mLat1 || mLongitude != mLon1 || - dest.mLatitude != mLat2 || dest.mLongitude != mLon2) { - computeDistanceAndBearing(mLatitude, mLongitude, - dest.mLatitude, dest.mLongitude, mResults); - mLat1 = mLatitude; - mLon1 = mLongitude; - mLat2 = dest.mLatitude; - mLon2 = dest.mLongitude; - mDistance = mResults[0]; - mInitialBearing = mResults[1]; - } - return mDistance; + if (mLatitude != cache.mLat1 || mLongitude != cache.mLon1 || + dest.mLatitude != cache.mLat2 || dest.mLongitude != cache.mLon2) { + computeDistanceAndBearing(mLatitude, mLongitude, + dest.mLatitude, dest.mLongitude, cache); } + return cache.mDistance; } /** @@ -455,21 +473,14 @@ public class Location implements Parcelable { * @return the initial bearing in degrees */ public float bearingTo(Location dest) { - synchronized (mResults) { - // See if we already have the result - if (mLatitude != mLat1 || mLongitude != mLon1 || - dest.mLatitude != mLat2 || dest.mLongitude != mLon2) { - computeDistanceAndBearing(mLatitude, mLongitude, - dest.mLatitude, dest.mLongitude, mResults); - mLat1 = mLatitude; - mLon1 = mLongitude; - mLat2 = dest.mLatitude; - mLon2 = dest.mLongitude; - mDistance = mResults[0]; - mInitialBearing = mResults[1]; - } - return mInitialBearing; + BearingDistanceCache cache = sBearingDistanceCache.get(); + // See if we already have the result + if (mLatitude != cache.mLat1 || mLongitude != cache.mLon1 || + dest.mLatitude != cache.mLat2 || dest.mLongitude != cache.mLon2) { + computeDistanceAndBearing(mLatitude, mLongitude, + dest.mLatitude, dest.mLongitude, cache); } + return cache.mInitialBearing; } /** @@ -585,7 +596,7 @@ public class Location implements Parcelable { * True if this location has an altitude. */ public boolean hasAltitude() { - return mHasAltitude; + return (mFieldsMask & HAS_ALTITUDE_MASK) != 0; } /** @@ -605,7 +616,7 @@ public class Location implements Parcelable { */ public void setAltitude(double altitude) { mAltitude = altitude; - mHasAltitude = true; + mFieldsMask |= HAS_ALTITUDE_MASK; } /** @@ -616,14 +627,14 @@ public class Location implements Parcelable { */ public void removeAltitude() { mAltitude = 0.0f; - mHasAltitude = false; + mFieldsMask &= ~HAS_ALTITUDE_MASK; } /** * True if this location has a speed. */ public boolean hasSpeed() { - return mHasSpeed; + return (mFieldsMask & HAS_SPEED_MASK) != 0; } /** @@ -642,7 +653,7 @@ public class Location implements Parcelable { */ public void setSpeed(float speed) { mSpeed = speed; - mHasSpeed = true; + mFieldsMask |= HAS_SPEED_MASK; } /** @@ -653,14 +664,14 @@ public class Location implements Parcelable { */ public void removeSpeed() { mSpeed = 0.0f; - mHasSpeed = false; + mFieldsMask &= ~HAS_SPEED_MASK; } /** * True if this location has a bearing. */ public boolean hasBearing() { - return mHasBearing; + return (mFieldsMask & HAS_BEARING_MASK) != 0; } /** @@ -692,7 +703,7 @@ public class Location implements Parcelable { bearing -= 360.0f; } mBearing = bearing; - mHasBearing = true; + mFieldsMask |= HAS_BEARING_MASK; } /** @@ -703,7 +714,7 @@ public class Location implements Parcelable { */ public void removeBearing() { mBearing = 0.0f; - mHasBearing = false; + mFieldsMask &= ~HAS_BEARING_MASK; } /** @@ -713,7 +724,7 @@ public class Location implements Parcelable { * accuracy. */ public boolean hasAccuracy() { - return mHasAccuracy; + return (mFieldsMask & HAS_ACCURACY_MASK) != 0; } /** @@ -751,7 +762,7 @@ public class Location implements Parcelable { */ public void setAccuracy(float accuracy) { mAccuracy = accuracy; - mHasAccuracy = true; + mFieldsMask |= HAS_ACCURACY_MASK; } /** @@ -762,7 +773,7 @@ public class Location implements Parcelable { */ public void removeAccuracy() { mAccuracy = 0.0f; - mHasAccuracy = false; + mFieldsMask &= ~HAS_ACCURACY_MASK; } /** @@ -780,7 +791,7 @@ public class Location implements Parcelable { @SystemApi public boolean isComplete() { if (mProvider == null) return false; - if (!mHasAccuracy) return false; + if (!hasAccuracy()) return false; if (mTime == 0) return false; if (mElapsedRealtimeNanos == 0) return false; return true; @@ -798,8 +809,8 @@ public class Location implements Parcelable { @SystemApi public void makeComplete() { if (mProvider == null) mProvider = "?"; - if (!mHasAccuracy) { - mHasAccuracy = true; + if (!hasAccuracy()) { + mFieldsMask |= HAS_ACCURACY_MASK; mAccuracy = 100.0f; } if (mTime == 0) mTime = System.currentTimeMillis(); @@ -838,7 +849,7 @@ public class Location implements Parcelable { s.append("Location["); s.append(mProvider); s.append(String.format(" %.6f,%.6f", mLatitude, mLongitude)); - if (mHasAccuracy) s.append(String.format(" acc=%.0f", mAccuracy)); + if (hasAccuracy()) s.append(String.format(" acc=%.0f", mAccuracy)); else s.append(" acc=???"); if (mTime == 0) { s.append(" t=?!?"); @@ -849,10 +860,10 @@ public class Location implements Parcelable { s.append(" et="); TimeUtils.formatDuration(mElapsedRealtimeNanos / 1000000L, s); } - if (mHasAltitude) s.append(" alt=").append(mAltitude); - if (mHasSpeed) s.append(" vel=").append(mSpeed); - if (mHasBearing) s.append(" bear=").append(mBearing); - if (mIsFromMockProvider) s.append(" mock"); + if (hasAltitude()) s.append(" alt=").append(mAltitude); + if (hasSpeed()) s.append(" vel=").append(mSpeed); + if (hasBearing()) s.append(" bear=").append(mBearing); + if (isFromMockProvider()) s.append(" mock"); if (mExtras != null) { s.append(" {").append(mExtras).append('}'); @@ -873,18 +884,14 @@ public class Location implements Parcelable { Location l = new Location(provider); l.mTime = in.readLong(); l.mElapsedRealtimeNanos = in.readLong(); + l.mFieldsMask = in.readByte(); l.mLatitude = in.readDouble(); l.mLongitude = in.readDouble(); - l.mHasAltitude = in.readInt() != 0; l.mAltitude = in.readDouble(); - l.mHasSpeed = in.readInt() != 0; l.mSpeed = in.readFloat(); - l.mHasBearing = in.readInt() != 0; l.mBearing = in.readFloat(); - l.mHasAccuracy = in.readInt() != 0; l.mAccuracy = in.readFloat(); l.mExtras = in.readBundle(); - l.mIsFromMockProvider = in.readInt() != 0; return l; } @@ -904,18 +911,14 @@ public class Location implements Parcelable { parcel.writeString(mProvider); parcel.writeLong(mTime); parcel.writeLong(mElapsedRealtimeNanos); + parcel.writeByte(mFieldsMask); parcel.writeDouble(mLatitude); parcel.writeDouble(mLongitude); - parcel.writeInt(mHasAltitude ? 1 : 0); parcel.writeDouble(mAltitude); - parcel.writeInt(mHasSpeed ? 1 : 0); parcel.writeFloat(mSpeed); - parcel.writeInt(mHasBearing ? 1 : 0); parcel.writeFloat(mBearing); - parcel.writeInt(mHasAccuracy ? 1 : 0); parcel.writeFloat(mAccuracy); parcel.writeBundle(mExtras); - parcel.writeInt(mIsFromMockProvider? 1 : 0); } /** @@ -940,7 +943,7 @@ public class Location implements Parcelable { * Attaches an extra {@link Location} to this Location. * * @param key the key associated with the Location extra - * @param location the Location to attach + * @param value the Location to attach * @hide */ public void setExtraLocation(String key, Location value) { @@ -956,7 +959,7 @@ public class Location implements Parcelable { * @return true if this Location came from a mock provider, false otherwise */ public boolean isFromMockProvider() { - return mIsFromMockProvider; + return (mFieldsMask & HAS_MOCK_PROVIDER_MASK) != 0; } /** @@ -967,6 +970,24 @@ public class Location implements Parcelable { */ @SystemApi public void setIsFromMockProvider(boolean isFromMockProvider) { - mIsFromMockProvider = isFromMockProvider; + if (isFromMockProvider) { + mFieldsMask |= HAS_MOCK_PROVIDER_MASK; + } else { + mFieldsMask &= ~HAS_MOCK_PROVIDER_MASK; + } + } + + /** + * Caches data used to compute distance and bearing (so successive calls to {@link #distanceTo} + * and {@link #bearingTo} don't duplicate work. + */ + private static class BearingDistanceCache { + private double mLat1 = 0.0; + private double mLon1 = 0.0; + private double mLat2 = 0.0; + private double mLon2 = 0.0; + private float mDistance = 0.0f; + private float mInitialBearing = 0.0f; + private float mFinalBearing = 0.0f; } } diff --git a/location/tests/locationtests/src/android/location/LocationTest.java b/location/tests/locationtests/src/android/location/LocationTest.java index 847ac7a000be..dc8d0f7b3876 100644 --- a/location/tests/locationtests/src/android/location/LocationTest.java +++ b/location/tests/locationtests/src/android/location/LocationTest.java @@ -16,6 +16,7 @@ package android.location; +import android.os.Parcel; import android.test.suitebuilder.annotation.SmallTest; import junit.framework.TestCase; @@ -225,6 +226,40 @@ public class LocationTest extends TestCase { assertEquals(message, loc.getBearing(), 0, 0); } + public void testParcel() { + final double expectedLat = 33; + final double expectedLon = -122; + final float expectedAccuracy = 15; + final float expectedSpeed = 5; + Location loc = new Location("test"); + loc.setLatitude(expectedLat); + loc.setLongitude(expectedLon); + loc.setAccuracy(expectedAccuracy); + loc.setSpeed(expectedSpeed); + + // Serialize location object into bytes via parcelable capability + Parcel parcel = Parcel.obtain(); + loc.writeToParcel(parcel, 0); + byte[] rawBytes = parcel.marshall(); + parcel.recycle(); + + // Turn the bytes back into a location object + parcel = Parcel.obtain(); + parcel.unmarshall(rawBytes, 0, rawBytes.length); + parcel.setDataPosition(0); + Location deserialized = Location.CREATOR.createFromParcel(parcel); + parcel.recycle(); + + assertEquals(expectedLat, deserialized.getLatitude()); + assertEquals(expectedLon, deserialized.getLongitude()); + assertEquals(expectedAccuracy, deserialized.getAccuracy()); + assertTrue(deserialized.hasAccuracy()); + assertEquals(expectedSpeed, deserialized.getSpeed()); + assertTrue(deserialized.hasSpeed()); + assertFalse(deserialized.hasBearing()); + assertFalse(deserialized.hasAltitude()); + assertFalse(deserialized.isFromMockProvider()); + } } diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java index ba867e15ee76..e0a8026221e7 100644 --- a/media/java/android/media/browse/MediaBrowser.java +++ b/media/java/android/media/browse/MediaBrowser.java @@ -249,7 +249,7 @@ public final class MediaBrowser { */ public @NonNull String getRoot() { if (!isConnected()) { - throw new IllegalStateException("getSessionToken() called while not connected (state=" + throw new IllegalStateException("getRoot() called while not connected (state=" + getStateLabel(mState) + ")"); } return mRootId; @@ -776,68 +776,88 @@ public final class MediaBrowser { */ private class MediaServiceConnection implements ServiceConnection { @Override - public void onServiceConnected(ComponentName name, IBinder binder) { - if (DBG) { - Log.d(TAG, "MediaServiceConnection.onServiceConnected name=" + name - + " binder=" + binder); - dump(); - } + public void onServiceConnected(final ComponentName name, final IBinder binder) { + postOrRun(new Runnable() { + @Override + public void run() { + if (DBG) { + Log.d(TAG, "MediaServiceConnection.onServiceConnected name=" + name + + " binder=" + binder); + dump(); + } - // Make sure we are still the current connection, and that they haven't called - // disconnect(). - if (!isCurrent("onServiceConnected")) { - return; - } + // Make sure we are still the current connection, and that they haven't called + // disconnect(). + if (!isCurrent("onServiceConnected")) { + return; + } - // Save their binder - mServiceBinder = IMediaBrowserService.Stub.asInterface(binder); + // Save their binder + mServiceBinder = IMediaBrowserService.Stub.asInterface(binder); - // We make a new mServiceCallbacks each time we connect so that we can drop - // responses from previous connections. - mServiceCallbacks = getNewServiceCallbacks(); - mState = CONNECT_STATE_CONNECTING; + // We make a new mServiceCallbacks each time we connect so that we can drop + // responses from previous connections. + mServiceCallbacks = getNewServiceCallbacks(); + mState = CONNECT_STATE_CONNECTING; - // Call connect, which is async. When we get a response from that we will - // say that we're connected. - try { - if (DBG) { - Log.d(TAG, "ServiceCallbacks.onConnect..."); - dump(); - } - mServiceBinder.connect(mContext.getPackageName(), mRootHints, mServiceCallbacks); - } catch (RemoteException ex) { - // Connect failed, which isn't good. But the auto-reconnect on the service - // will take over and we will come back. We will also get the - // onServiceDisconnected, which has all the cleanup code. So let that do it. - Log.w(TAG, "RemoteException during connect for " + mServiceComponent); - if (DBG) { - Log.d(TAG, "ServiceCallbacks.onConnect..."); - dump(); + // Call connect, which is async. When we get a response from that we will + // say that we're connected. + try { + if (DBG) { + Log.d(TAG, "ServiceCallbacks.onConnect..."); + dump(); + } + mServiceBinder.connect(mContext.getPackageName(), mRootHints, + mServiceCallbacks); + } catch (RemoteException ex) { + // Connect failed, which isn't good. But the auto-reconnect on the service + // will take over and we will come back. We will also get the + // onServiceDisconnected, which has all the cleanup code. So let that do + // it. + Log.w(TAG, "RemoteException during connect for " + mServiceComponent); + if (DBG) { + Log.d(TAG, "ServiceCallbacks.onConnect..."); + dump(); + } + } } - } + }); } @Override - public void onServiceDisconnected(ComponentName name) { - if (DBG) { - Log.d(TAG, "MediaServiceConnection.onServiceDisconnected name=" + name - + " this=" + this + " mServiceConnection=" + mServiceConnection); - dump(); - } + public void onServiceDisconnected(final ComponentName name) { + postOrRun(new Runnable() { + @Override + public void run() { + if (DBG) { + Log.d(TAG, "MediaServiceConnection.onServiceDisconnected name=" + name + + " this=" + this + " mServiceConnection=" + mServiceConnection); + dump(); + } - // Make sure we are still the current connection, and that they haven't called - // disconnect(). - if (!isCurrent("onServiceDisconnected")) { - return; - } + // Make sure we are still the current connection, and that they haven't called + // disconnect(). + if (!isCurrent("onServiceDisconnected")) { + return; + } + + // Clear out what we set in onServiceConnected + mServiceBinder = null; + mServiceCallbacks = null; - // Clear out what we set in onServiceConnected - mServiceBinder = null; - mServiceCallbacks = null; + // And tell the app that it's suspended. + mState = CONNECT_STATE_SUSPENDED; + mCallback.onConnectionSuspended(); + } + }); + } - // And tell the app that it's suspended. - mState = CONNECT_STATE_SUSPENDED; - mCallback.onConnectionSuspended(); + private void postOrRun(Runnable r) { + if (Thread.currentThread() == mHandler.getLooper().getThread()) { + r.run(); + } else { + mHandler.post(r); + } } /** diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html index 45fb5791dfa7..93dbf3f779ab 100644 --- a/media/java/android/media/midi/package.html +++ b/media/java/android/media/midi/package.html @@ -180,7 +180,8 @@ m.openDevice(info, new MidiManager.OnDeviceOpenedListener() { Log.e(TAG, "could not open device " + info); } else { ... - }, new Handler(Looper.getMainLooper()) + } + }, new Handler(Looper.getMainLooper()) ); </pre> diff --git a/opengl/java/android/opengl/GLES31.java b/opengl/java/android/opengl/GLES31.java index 3cbaa6079bf8..805930e343f2 100644 --- a/opengl/java/android/opengl/GLES31.java +++ b/opengl/java/android/opengl/GLES31.java @@ -24,9 +24,14 @@ public class GLES31 extends GLES30 { public static final int GL_VERTEX_SHADER_BIT = 0x00000001; public static final int GL_FRAGMENT_SHADER_BIT = 0x00000002; + public static final int GL_COMPUTE_SHADER_BIT = 0x00000020; + public static final int GL_ALL_SHADER_BITS = -1; // 0xFFFFFFFF + + public static final int GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT = 0x00000001; + public static final int GL_ELEMENT_ARRAY_BARRIER_BIT = 0x00000002; public static final int GL_UNIFORM_BARRIER_BIT = 0x00000004; public static final int GL_TEXTURE_FETCH_BARRIER_BIT = 0x00000008; - public static final int GL_COMPUTE_SHADER_BIT = 0x00000020; + public static final int GL_SHADER_IMAGE_ACCESS_BARRIER_BIT = 0x00000020; public static final int GL_COMMAND_BARRIER_BIT = 0x00000040; public static final int GL_PIXEL_BUFFER_BARRIER_BIT = 0x00000080; public static final int GL_TEXTURE_UPDATE_BARRIER_BIT = 0x00000100; @@ -35,7 +40,8 @@ public class GLES31 extends GLES30 { public static final int GL_TRANSFORM_FEEDBACK_BARRIER_BIT = 0x00000800; public static final int GL_ATOMIC_COUNTER_BARRIER_BIT = 0x00001000; public static final int GL_SHADER_STORAGE_BARRIER_BIT = 0x00002000; - public static final int GL_ALL_SHADER_BITS = -1; // 0xFFFFFFFF + public static final int GL_ALL_BARRIER_BITS = -1; // 0xFFFFFFFF + public static final int GL_TEXTURE_WIDTH = 0x1000; public static final int GL_TEXTURE_HEIGHT = 0x1001; diff --git a/packages/DocumentsUI/res/values/config.xml b/packages/DocumentsUI/res/values/config.xml index 73538278c80c..ad419aabca28 100644 --- a/packages/DocumentsUI/res/values/config.xml +++ b/packages/DocumentsUI/res/values/config.xml @@ -16,6 +16,6 @@ <resources> <bool name="productivity_device">true</bool> - <!-- intentionally unset. Vendors should set this in an overlay. --> + <!-- Intentionally unset. Vendors should set this in an overlay. --> <string name="trusted_quick_viewer_package"></string> </resources> diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 45a890702c0f..a09a22a19037 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -17,7 +17,6 @@ package com.android.documentsui; import static com.android.documentsui.Shared.DEBUG; -import static com.android.documentsui.State.ACTION_BROWSE; import static com.android.documentsui.State.ACTION_CREATE; import static com.android.documentsui.State.ACTION_MANAGE; import static com.android.documentsui.State.MODE_GRID; @@ -55,7 +54,6 @@ import android.os.CancellationSignal; import android.os.Looper; import android.os.OperationCanceledException; import android.os.Parcelable; -import android.os.SystemProperties; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.support.annotation.Nullable; @@ -95,6 +93,7 @@ import com.android.documentsui.BaseActivity.DocumentContext; import com.android.documentsui.MultiSelectManager.Selection; import com.android.documentsui.ProviderExecutor.Preemptable; import com.android.documentsui.RecentsProvider.StateColumns; +import com.android.documentsui.dirlist.FragmentTuner; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentStack; import com.android.documentsui.model.RootInfo; @@ -341,7 +340,7 @@ public class DirectoryFragment extends Fragment { mType = getArguments().getInt(EXTRA_TYPE); mStateKey = buildStateKey(root, doc); - mFragmentTuner = pickFragmentTuner(state); + mFragmentTuner = FragmentTuner.pick(state); mClipper = new DocumentClipper(context); if (mType == TYPE_RECENT_OPEN) { @@ -429,7 +428,6 @@ public class DirectoryFragment extends Fragment { // Kick off loader at least once getLoaderManager().restartLoader(LOADER_ID, null, mCallbacks); - mFragmentTuner.afterActivityCreated(this); updateDisplayState(); } @@ -1638,21 +1636,6 @@ public class DirectoryFragment extends Fragment { } } - private FragmentTuner pickFragmentTuner(final State state) { - return state.action == ACTION_BROWSE - ? new FilesTuner() - : new DefaultTuner(state.action); - } - - /** - * Interface for specializing the Fragment for the "host" Activity. - * Feel free to expand the role of this class to handle other specializations. - */ - private interface FragmentTuner { - void updateActionMenu(Menu menu, int dirType, boolean canDelete); - void afterActivityCreated(DirectoryFragment fragment); - } - /** * Abstract task providing support for loading documents *off* * the main thread. And if it isn't obvious, creating a list @@ -1673,65 +1656,6 @@ public class DirectoryFragment extends Fragment { abstract void onDocumentsReady(List<DocumentInfo> docs); } - /** - * Provides support for Platform specific specializations of DirectoryFragment. - */ - private static final class DefaultTuner implements FragmentTuner { - - private final boolean mManaging; - - public DefaultTuner(int action) { - mManaging = (action == ACTION_MANAGE); - } - - @Override - public void updateActionMenu(Menu menu, int dirType, boolean canDelete) { - boolean copyEnabled = mManaging && dirType != TYPE_RECENT_OPEN; - // TODO: The selection needs to be deletable. - boolean moveEnabled = - SystemProperties.getBoolean("debug.documentsui.enable_move", false); - menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(copyEnabled); - - final MenuItem open = menu.findItem(R.id.menu_open); - final MenuItem share = menu.findItem(R.id.menu_share); - final MenuItem delete = menu.findItem(R.id.menu_delete); - final MenuItem copyTo = menu.findItem(R.id.menu_copy_to); - final MenuItem moveTo = menu.findItem(R.id.menu_move_to); - - open.setVisible(!mManaging); - share.setVisible(mManaging); - delete.setVisible(mManaging && canDelete); - copyTo.setVisible(copyEnabled); - copyTo.setEnabled(copyEnabled); - moveTo.setVisible(moveEnabled); - moveTo.setEnabled(moveEnabled); - } - - @Override - public void afterActivityCreated(DirectoryFragment fragment) {} - } - - /** - * Provides support for Files activity specific specializations of DirectoryFragment. - */ - private static final class FilesTuner implements FragmentTuner { - @Override - public void updateActionMenu(Menu menu, int dirType, boolean canDelete) { - - menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(dirType != TYPE_RECENT_OPEN); - - menu.findItem(R.id.menu_share).setVisible(true); - menu.findItem(R.id.menu_delete).setVisible(canDelete); - - menu.findItem(R.id.menu_open).setVisible(false); - menu.findItem(R.id.menu_copy_to).setVisible(true); - menu.findItem(R.id.menu_move_to).setVisible(true); - } - - @Override - public void afterActivityCreated(DirectoryFragment fragment) {} - } - boolean isSelected(int position) { return mSelectionManager.getSelection().contains(position); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java index 3b985ec21cac..627ba756680f 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java @@ -334,12 +334,12 @@ public class FilesActivity extends BaseActivity { startActivity(intent); return; } catch (SecurityException e) { - // carry on to regular view mode. + // Carry on to regular view mode. Log.e(TAG, "Caught security error: " + e.getLocalizedMessage()); } } - // fallback to traditional VIEW action... + // Fallback to traditional VIEW action... intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setData(doc.derivedUri); diff --git a/packages/DocumentsUI/src/com/android/documentsui/Menus.java b/packages/DocumentsUI/src/com/android/documentsui/Menus.java index 3f43a3d30c8d..5277d2bb86e9 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/Menus.java +++ b/packages/DocumentsUI/src/com/android/documentsui/Menus.java @@ -19,14 +19,14 @@ package com.android.documentsui; import android.view.Menu; import android.view.MenuItem; -final class Menus { +public final class Menus { private Menus() {} /** * Disables hidden menu items so that they are not invokable via command shortcuts */ - static void disableHiddenItems(Menu menu, MenuItem... exclusions) { + public static void disableHiddenItems(Menu menu, MenuItem... exclusions) { for (int i = 0; i < menu.size(); i++) { MenuItem item = menu.getItem(i); if (item.isVisible()) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java index 6a424a684a87..605c53043744 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java +++ b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java @@ -92,6 +92,8 @@ final class QuickViewIntentBuilder { intent.setClipData(mClipData); return intent; + } else { + Log.e(TAG, "Can't resolve trusted quick view package: " + trustedPkg); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index 0dcd02da442b..d75b6fdf77bc 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -287,33 +287,24 @@ public class RootsFragment extends Fragment { super(context, 0); final List<RootItem> libraries = new ArrayList<>(); - final List<RootItem> clouds = new ArrayList<>(); - final List<RootItem> locals = new ArrayList<>(); - - for (RootInfo root : roots) { - RootItem item = new RootItem(root); - switch (root.derivedType) { - case RootInfo.TYPE_LOCAL: - locals.add(item); - break; - case RootInfo.TYPE_CLOUD: - clouds.add(item); - break; - default: - libraries.add(item); - break; + final List<RootItem> others = new ArrayList<>(); + + for (final RootInfo root : roots) { + final RootItem item = new RootItem(root); + if (root.isLibrary()) { + libraries.add(item); + } else { + others.add(item); } } final RootComparator comp = new RootComparator(); Collections.sort(libraries, comp); - Collections.sort(locals, comp); - Collections.sort(clouds, comp); + Collections.sort(others, comp); addAll(libraries); add(new SpacerItem()); - addAll(locals); - addAll(clouds); + addAll(others); if (includeApps != null) { final PackageManager pm = context.getPackageManager(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java new file mode 100644 index 000000000000..ca85cff8a32b --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2015 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.documentsui.dirlist; + +import static com.android.documentsui.State.ACTION_BROWSE; +import static com.android.documentsui.State.ACTION_MANAGE; +import static com.android.internal.util.Preconditions.checkArgument; + +import android.os.SystemProperties; +import android.view.Menu; +import android.view.MenuItem; + +import com.android.documentsui.DirectoryFragment; +import com.android.documentsui.Menus; +import com.android.documentsui.R; +import com.android.documentsui.State; + +/** + * Providers support for specializing the DirectoryFragment to the "host" Activity. + * Feel free to expand the role of this class to handle other specializations. + */ +public abstract class FragmentTuner { + public static FragmentTuner pick(State state) { + switch (state.action) { + case ACTION_BROWSE: + return new FilesTuner(); + case ACTION_MANAGE: + return new ManageTuner(); + default: + return new DocumentsTuner(); + } + } + + public abstract void updateActionMenu(Menu menu, int dirType, boolean canDelete); + + /** + * Provides support for Platform specific specializations of DirectoryFragment. + */ + private static final class DocumentsTuner extends FragmentTuner { + @Override + public void updateActionMenu(Menu menu, int dirType, boolean canDelete) { + + boolean copyEnabled = dirType != DirectoryFragment.TYPE_RECENT_OPEN; + boolean moveEnabled = + SystemProperties.getBoolean("debug.documentsui.enable_move", false); + menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(copyEnabled); + + final MenuItem open = menu.findItem(R.id.menu_open); + final MenuItem share = menu.findItem(R.id.menu_share); + final MenuItem delete = menu.findItem(R.id.menu_delete); + final MenuItem copyTo = menu.findItem(R.id.menu_copy_to); + final MenuItem moveTo = menu.findItem(R.id.menu_move_to); + + open.setVisible(true); + share.setVisible(false); + delete.setVisible(false); + copyTo.setVisible(copyEnabled); + copyTo.setEnabled(copyEnabled); + moveTo.setVisible(moveEnabled); + moveTo.setEnabled(moveEnabled); + } + } + + /** + * Provides support for Platform specific specializations of DirectoryFragment. + */ + private static final class ManageTuner extends FragmentTuner { + + @Override + public void updateActionMenu(Menu menu, int dirType, boolean canDelete) { + checkArgument(dirType != DirectoryFragment.TYPE_RECENT_OPEN); + + boolean moveEnabled = + SystemProperties.getBoolean("debug.documentsui.enable_move", false); + menu.findItem(R.id.menu_copy_to_clipboard).setEnabled(true); + + final MenuItem open = menu.findItem(R.id.menu_open); + final MenuItem share = menu.findItem(R.id.menu_share); + final MenuItem delete = menu.findItem(R.id.menu_delete); + final MenuItem copyTo = menu.findItem(R.id.menu_copy_to); + final MenuItem moveTo = menu.findItem(R.id.menu_move_to); + + open.setVisible(false); + share.setVisible(false); + delete.setVisible(canDelete); + copyTo.setVisible(true); + copyTo.setEnabled(true); + moveTo.setVisible(moveEnabled); + moveTo.setEnabled(moveEnabled); + } + } + + /** + * Provides support for Files activity specific specializations of DirectoryFragment. + */ + private static final class FilesTuner extends FragmentTuner { + @Override + public void updateActionMenu(Menu menu, int dirType, boolean canDelete) { + + MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard); + MenuItem paste = menu.findItem(R.id.menu_paste_from_clipboard); + copy.setEnabled(dirType != DirectoryFragment.TYPE_RECENT_OPEN); + + menu.findItem(R.id.menu_share).setVisible(true); + menu.findItem(R.id.menu_delete).setVisible(canDelete); + + menu.findItem(R.id.menu_open).setVisible(false); + menu.findItem(R.id.menu_copy_to).setVisible(true); + menu.findItem(R.id.menu_move_to).setVisible(true); + + Menus.disableHiddenItems(menu, copy, paste); + } + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java index 17e6a801c53e..501392ca93f5 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java @@ -51,7 +51,8 @@ public class RootInfo implements Durable, Parcelable { public static final int TYPE_RECENTS = 4; public static final int TYPE_DOWNLOADS = 5; public static final int TYPE_LOCAL = 6; - public static final int TYPE_CLOUD = 7; + public static final int TYPE_MTP = 7; + public static final int TYPE_CLOUD = 8; public String authority; public String rootId; @@ -184,6 +185,8 @@ public class RootInfo implements Durable, Parcelable { derivedType = TYPE_AUDIO; } else if (isRecents()) { derivedType = TYPE_RECENTS; + } else if (isMtp()) { + derivedType = TYPE_MTP; } else { derivedType = TYPE_CLOUD; } @@ -216,6 +219,15 @@ public class RootInfo implements Durable, Parcelable { && "audio_root".equals(rootId); } + public boolean isMtp() { + return "com.android.mtp.documents".equals(authority); + } + + public boolean isLibrary() { + return derivedType == TYPE_IMAGES || derivedType == TYPE_VIDEO || derivedType == TYPE_AUDIO + || derivedType == TYPE_RECENTS || derivedType == TYPE_DOWNLOADS; + } + @Override public String toString() { return "Root{authority=" + authority + ", rootId=" + rootId + ", title=" + title + "}"; diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk b/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk deleted file mode 100644 index 8baadba982dd..000000000000 --- a/packages/DocumentsUI/testing/TestDocumentsProvider/Android.mk +++ /dev/null @@ -1,14 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := $(call all-subdir-java-files) - -LOCAL_PACKAGE_NAME := TestDocumentsProvider -LOCAL_CERTIFICATE := platform -LOCAL_MODULE_TAGS := tests -#LOCAL_SDK_VERSION := current - -LOCAL_PROGUARD_ENABLED := disabled -LOCAL_DEX_PREOPT := false - -include $(BUILD_PACKAGE) diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml b/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml deleted file mode 100644 index 66988a17db8b..000000000000 --- a/packages/DocumentsUI/testing/TestDocumentsProvider/AndroidManifest.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 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. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.documentsui.testing"> - <application> - <provider android:name="TestDocumentsProvider" - android:authorities="com.android.documentsui.testing" - android:exported="true" - android:grantUriPermissions="true" - android:permission="android.permission.MANAGE_DOCUMENTS"> - <intent-filter> - <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> - </intent-filter> - </provider> - </application> -</manifest> diff --git a/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java b/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java deleted file mode 100644 index 63ff0de56998..000000000000 --- a/packages/DocumentsUI/testing/TestDocumentsProvider/src/com/android/documentsui/testing/TestDocumentsProvider.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (C) 2015 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.documentsui.testing; - -import android.database.Cursor; -import android.database.MatrixCursor; -import android.database.MatrixCursor.RowBuilder; -import android.os.AsyncTask; -import android.os.CancellationSignal; -import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract.Document; -import android.provider.DocumentsContract.Root; -import android.provider.DocumentsProvider; -import android.util.Log; - -import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class TestDocumentsProvider extends DocumentsProvider { - private static final String TAG = "TestDocumentsProvider"; - - private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { - Root.COLUMN_ROOT_ID, - Root.COLUMN_FLAGS, - Root.COLUMN_ICON, - Root.COLUMN_TITLE, - Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, - }; - - private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { - Document.COLUMN_DOCUMENT_ID, - Document.COLUMN_MIME_TYPE, - Document.COLUMN_DISPLAY_NAME, - Document.COLUMN_LAST_MODIFIED, - Document.COLUMN_FLAGS, - Document.COLUMN_SIZE, - }; - - private static String[] resolveRootProjection(String[] projection) { - return projection != null ? projection : DEFAULT_ROOT_PROJECTION; - } - - private static String[] resolveDocumentProjection(String[] projection) { - return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; - } - - @Override - public boolean onCreate() { - resetRoots(); - return true; - } - - @Override - public Cursor queryRoots(String[] projection) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); - - RowBuilder row = result.newRow(); - row.add(Root.COLUMN_ROOT_ID, "local"); - row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY); - row.add(Root.COLUMN_TITLE, "TEST-Local"); - row.add(Root.COLUMN_SUMMARY, "TEST-LocalSummary"); - row.add(Root.COLUMN_DOCUMENT_ID, "doc:local"); - - row = result.newRow(); - row.add(Root.COLUMN_ROOT_ID, "create"); - row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD); - row.add(Root.COLUMN_TITLE, "TEST-Create"); - row.add(Root.COLUMN_DOCUMENT_ID, "doc:create"); - - return result; - } - - private Map<String, Doc> mDocs = new HashMap<>(); - - private Doc mLocalRoot; - private Doc mCreateRoot; - - private Doc buildDoc(String docId, String displayName, String mimeType) { - final Doc doc = new Doc(); - doc.docId = docId; - doc.displayName = displayName; - doc.mimeType = mimeType; - mDocs.put(doc.docId, doc); - return doc; - } - - public void resetRoots() { - Log.d(TAG, "resetRoots()"); - - mDocs.clear(); - - mLocalRoot = buildDoc("doc:local", null, Document.MIME_TYPE_DIR); - - mCreateRoot = buildDoc("doc:create", null, Document.MIME_TYPE_DIR); - mCreateRoot.flags = Document.FLAG_DIR_SUPPORTS_CREATE; - - { - Doc file1 = buildDoc("doc:file1", "FILE1", "mime1/file1"); - file1.contents = "fileone".getBytes(); - file1.flags = Document.FLAG_SUPPORTS_WRITE; - mLocalRoot.children.add(file1); - mCreateRoot.children.add(file1); - } - - { - Doc file2 = buildDoc("doc:file2", "FILE2", "mime2/file2"); - file2.contents = "filetwo".getBytes(); - file2.flags = Document.FLAG_SUPPORTS_WRITE; - mLocalRoot.children.add(file2); - mCreateRoot.children.add(file2); - } - - Doc dir1 = buildDoc("doc:dir1", "DIR1", Document.MIME_TYPE_DIR); - mLocalRoot.children.add(dir1); - - { - Doc file3 = buildDoc("doc:file3", "FILE3", "mime3/file3"); - file3.contents = "filethree".getBytes(); - file3.flags = Document.FLAG_SUPPORTS_WRITE; - dir1.children.add(file3); - } - - Doc dir2 = buildDoc("doc:dir2", "DIR2", Document.MIME_TYPE_DIR); - mCreateRoot.children.add(dir2); - - { - Doc file4 = buildDoc("doc:file4", "FILE4", "mime4/file4"); - file4.contents = "filefour".getBytes(); - file4.flags = Document.FLAG_SUPPORTS_WRITE; - dir2.children.add(file4); - } - } - - private static class Doc { - public String docId; - public int flags; - public String displayName; - public long size; - public String mimeType; - public long lastModified; - public byte[] contents; - public List<Doc> children = new ArrayList<>(); - - public void include(MatrixCursor result) { - final RowBuilder row = result.newRow(); - row.add(Document.COLUMN_DOCUMENT_ID, docId); - row.add(Document.COLUMN_DISPLAY_NAME, displayName); - row.add(Document.COLUMN_SIZE, size); - row.add(Document.COLUMN_MIME_TYPE, mimeType); - row.add(Document.COLUMN_FLAGS, flags); - row.add(Document.COLUMN_LAST_MODIFIED, lastModified); - } - } - - @Override - public boolean isChildDocument(String parentDocumentId, String documentId) { - for (Doc doc : mDocs.get(parentDocumentId).children) { - if (doc.docId.equals(documentId)) { - return true; - } - if (Document.MIME_TYPE_DIR.equals(doc.mimeType)) { - return isChildDocument(doc.docId, documentId); - } - } - return false; - } - - @Override - public String createDocument(String parentDocumentId, String mimeType, String displayName) - throws FileNotFoundException { - final String docId = "doc:" + System.currentTimeMillis(); - final Doc doc = buildDoc(docId, displayName, mimeType); - doc.flags = Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_RENAME; - mDocs.get(parentDocumentId).children.add(doc); - return docId; - } - - @Override - public String renameDocument(String documentId, String displayName) - throws FileNotFoundException { - mDocs.get(documentId).displayName = displayName; - return null; - } - - @Override - public void deleteDocument(String documentId) throws FileNotFoundException { - mDocs.remove(documentId); - for (Doc doc : mDocs.values()) { - doc.children.remove(documentId); - } - } - - @Override - public Cursor queryDocument(String documentId, String[] projection) - throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); - mDocs.get(documentId).include(result); - return result; - } - - @Override - public Cursor queryChildDocuments(String parentDocumentId, String[] projection, - String sortOrder) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); - for (Doc doc : mDocs.get(parentDocumentId).children) { - doc.include(result); - } - return result; - } - - @Override - public ParcelFileDescriptor openDocument(String documentId, String mode, - CancellationSignal signal) throws FileNotFoundException { - final Doc doc = mDocs.get(documentId); - if (doc == null) { - throw new FileNotFoundException(); - } - final ParcelFileDescriptor[] pipe; - try { - pipe = ParcelFileDescriptor.createPipe(); - } catch (IOException e) { - throw new IllegalStateException(e); - } - if (mode.contains("w")) { - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - synchronized (doc) { - try { - final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream( - pipe[0]); - doc.contents = readFullyNoClose(is); - is.close(); - doc.notifyAll(); - } catch (IOException e) { - Log.w(TAG, "Failed to stream", e); - } - } - return null; - } - }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); - return pipe[1]; - } else { - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - synchronized (doc) { - try { - final OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream( - pipe[1]); - while (doc.contents == null) { - doc.wait(); - } - os.write(doc.contents); - os.close(); - } catch (IOException e) { - Log.w(TAG, "Failed to stream", e); - } catch (InterruptedException e) { - Log.w(TAG, "Interuppted", e); - } - } - return null; - } - }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); - return pipe[0]; - } - } - - private static byte[] readFullyNoClose(InputStream in) throws IOException { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int count; - while ((count = in.read(buffer)) != -1) { - bytes.write(buffer, 0, count); - } - return bytes.toByteArray(); - } -} diff --git a/packages/DocumentsUI/tests/Android.mk b/packages/DocumentsUI/tests/Android.mk index cf486b1d1a5d..2a540d4d77e9 100644 --- a/packages/DocumentsUI/tests/Android.mk +++ b/packages/DocumentsUI/tests/Android.mk @@ -17,4 +17,3 @@ LOCAL_CERTIFICATE := platform include $(BUILD_PACKAGE) -include $(LOCAL_PATH)/../testing/TestDocumentsProvider/Android.mk diff --git a/packages/InputDevices/res/values-am/strings.xml b/packages/InputDevices/res/values-am/strings.xml index efabbbfb85ae..3186addcf64e 100644 --- a/packages/InputDevices/res/values-am/strings.xml +++ b/packages/InputDevices/res/values-am/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"እንግሊዘኛ (ዩ. ኤስ.)፣ አለም አቀፍ ቅጥ"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"እንግሊዘኛ (ዩ. ኤስ.)፣ የኮልማርክ ቅጥ"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"እንግሊዘኛ (ዩ. ኤስ.)፣ የድቮራክ ቅጥ"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"እንግሊዝኛ (አሜሪካ)፣ የሥራሰው ቅጥ"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ጀርመን"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"ፈረንሳይኛ"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ፈረንሳይኛ (ካናዳ)"</string> diff --git a/packages/InputDevices/res/values-bn-rBD/strings.xml b/packages/InputDevices/res/values-bn-rBD/strings.xml index bfbf3d384a0c..a0ce313c7f91 100644 --- a/packages/InputDevices/res/values-bn-rBD/strings.xml +++ b/packages/InputDevices/res/values-bn-rBD/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ইংরেজি (US), আন্তর্জাতিক শৈলী"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ইংরেজি (US), কোলেম্যাক শৈলী"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ইংরেজি (US), ডিভোরাক শৈলী"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ইংরেজি (US), ওয়ার্কম্যান শৈলী"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"জার্মান"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"ফরাসী"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ফরাসী (কানাডা)"</string> diff --git a/packages/InputDevices/res/values-cs/strings.xml b/packages/InputDevices/res/values-cs/strings.xml index 614ba5698b5f..3e7b9c84aecc 100644 --- a/packages/InputDevices/res/values-cs/strings.xml +++ b/packages/InputDevices/res/values-cs/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"anglické (USA), mezinárodní"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"anglické (USA), styl Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"anglické (USA), styl Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"anglické (USA), styl Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"německé"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"francouzské"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francouzské (Kanada)"</string> diff --git a/packages/InputDevices/res/values-de/strings.xml b/packages/InputDevices/res/values-de/strings.xml index ce256235b185..6af00309071d 100644 --- a/packages/InputDevices/res/values-de/strings.xml +++ b/packages/InputDevices/res/values-de/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Englisch (USA), international"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Englisch (USA), Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Englisch (USA), Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Englisch (USA), Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Deutsch"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Französisch"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Französisch (Kanada)"</string> diff --git a/packages/InputDevices/res/values-el/strings.xml b/packages/InputDevices/res/values-el/strings.xml index 0a99dbb4a2a3..da6dca2fe242 100644 --- a/packages/InputDevices/res/values-el/strings.xml +++ b/packages/InputDevices/res/values-el/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Αγγλικά (ΗΠΑ), τύπου International"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Αγγλικά (ΗΠΑ), τύπου Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Αγγλικά (ΗΠΑ), τύπου Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Αγγλικά (ΗΠΑ), στυλ Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Γερμανικά"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Γαλλικά"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Γαλλικά (Καναδά)"</string> diff --git a/packages/InputDevices/res/values-es-rUS/strings.xml b/packages/InputDevices/res/values-es-rUS/strings.xml index 20d677baab79..6acf07b5a630 100644 --- a/packages/InputDevices/res/values-es-rUS/strings.xml +++ b/packages/InputDevices/res/values-es-rUS/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglés (EE. UU.), internacional"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglés (EE. UU.), Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglés (EE. UU.), Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglés (EE. UU.), Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemán"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francés"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francés (Canadá)"</string> diff --git a/packages/InputDevices/res/values-fa/strings.xml b/packages/InputDevices/res/values-fa/strings.xml index a05d071f6332..f2848a978eba 100644 --- a/packages/InputDevices/res/values-fa/strings.xml +++ b/packages/InputDevices/res/values-fa/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"انگلیسی (ایالات متحده)، سبک بینالمللی"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"انگلیسی (ایالات متحده)، سبک Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"انگلیسی (ایالات متحده)، سبک Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"انگلیسی (ایالات متحده)، سبک ورکمن"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"آلمانی"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"فرانسوی"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"فرانسوی (کانادا)"</string> diff --git a/packages/InputDevices/res/values-fi/strings.xml b/packages/InputDevices/res/values-fi/strings.xml index 250333ccbc04..284efc8a93a7 100644 --- a/packages/InputDevices/res/values-fi/strings.xml +++ b/packages/InputDevices/res/values-fi/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"englanti (Yhdysvallat), kansainvälinen"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"englanti (Yhdysvallat), Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"englanti (Yhdysvallat), Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"englanti (Yhdysvallat), Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"saksa"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"ranska"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ranska (Kanada)"</string> diff --git a/packages/InputDevices/res/values-fr-rCA/strings.xml b/packages/InputDevices/res/values-fr-rCA/strings.xml index 3842584c9b6f..b26a0eac16e1 100644 --- a/packages/InputDevices/res/values-fr-rCA/strings.xml +++ b/packages/InputDevices/res/values-fr-rCA/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglais (États-Unis), international"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglais (États-Unis), type Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglais (États-Unis), type Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Anglais (États-Unis), style Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Allemand"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Français"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Français (Canada)"</string> diff --git a/packages/InputDevices/res/values-fr/strings.xml b/packages/InputDevices/res/values-fr/strings.xml index 679dba911d5a..a428a23c091b 100644 --- a/packages/InputDevices/res/values-fr/strings.xml +++ b/packages/InputDevices/res/values-fr/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Anglais (États-Unis), international"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Anglais (États-Unis), type Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Anglais (États-Unis), type Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Anglais (États-Unis), style Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Allemand"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Français"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Français (Canada)"</string> diff --git a/packages/InputDevices/res/values-gl-rES/strings.xml b/packages/InputDevices/res/values-gl-rES/strings.xml index ab76a1490ee1..bb0f2a06ec1f 100644 --- a/packages/InputDevices/res/values-gl-rES/strings.xml +++ b/packages/InputDevices/res/values-gl-rES/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglés (EUA), estilo internacional"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglés (EUA), estilo Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglés (EUA), estilo Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglés (EUA), estilo Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemán"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francés"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francés (Canadá)"</string> diff --git a/packages/InputDevices/res/values-gu-rIN/strings.xml b/packages/InputDevices/res/values-gu-rIN/strings.xml index 63c3874ec7b1..e83b0ca49dad 100644 --- a/packages/InputDevices/res/values-gu-rIN/strings.xml +++ b/packages/InputDevices/res/values-gu-rIN/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"અંગ્રેજી (યુએસ), આંતરરાષ્ટ્રીય શૈલી"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"અંગ્રેજી (યુએસ), કોલેમેક શૈલી"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"અંગ્રેજી (યુએસ), ડ્વોરક શૈલી"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"અંગ્રેજી (યુએસ), વર્કમૅન શૈલી"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"જર્મન"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"ફ્રેન્ચ"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ફ્રેન્ચ (કેનેડા)"</string> diff --git a/packages/InputDevices/res/values-hr/strings.xml b/packages/InputDevices/res/values-hr/strings.xml index 829f8174f0df..27066adb4162 100644 --- a/packages/InputDevices/res/values-hr/strings.xml +++ b/packages/InputDevices/res/values-hr/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"engleska (SAD), međunarodna"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"engleska (SAD), Colemakov raspored"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"engleska (SAD), Dvorakov raspored"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engleska (SAD), raspored Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"njemačka"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"francuska"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francuska (Kanada)"</string> diff --git a/packages/InputDevices/res/values-hy-rAM/strings.xml b/packages/InputDevices/res/values-hy-rAM/strings.xml index f47aba249da4..0d116457c3ab 100644 --- a/packages/InputDevices/res/values-hy-rAM/strings.xml +++ b/packages/InputDevices/res/values-hy-rAM/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Անգլերեն (ԱՄՆ), միջազգային տեսակ"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Անգլերեն (ԱՄՆ), Colemak տեսակ"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Անգլերեն (ԱՄՆ), Dvorak տեսակ"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Անգլերեն (ԱՄՆ), Workman տեսակ"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Գերմաներեն"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Ֆրանսերեն"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Ֆրանսերեն (Կանադա)"</string> diff --git a/packages/InputDevices/res/values-is-rIS/strings.xml b/packages/InputDevices/res/values-is-rIS/strings.xml index 35a5a1751118..de91275044fa 100644 --- a/packages/InputDevices/res/values-is-rIS/strings.xml +++ b/packages/InputDevices/res/values-is-rIS/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Enskt (Bandaríkin), alþjóðlegt"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Enskt (Bandaríkin), Colemak-stíll"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Enskt (Bandaríkin), Dvorak-stíll"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Enska (Bandaríkin), Workman-stíll"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Þýskt"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franskt"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franskt (Kanada)"</string> diff --git a/packages/InputDevices/res/values-iw/strings.xml b/packages/InputDevices/res/values-iw/strings.xml index 0c8b4e95fb35..b3bd576c5c1a 100644 --- a/packages/InputDevices/res/values-iw/strings.xml +++ b/packages/InputDevices/res/values-iw/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"אנגלית (ארה\"ב), סגנון בינ\"ל"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"אנגלית (ארה\"ב), סגנון Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"אנגלית (ארה\"ב), סגנון Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"אנגלית (ארה\"ב), סגנון Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"גרמנית"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"צרפתית"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"צרפתית (קנדה)"</string> diff --git a/packages/InputDevices/res/values-ja/strings.xml b/packages/InputDevices/res/values-ja/strings.xml index 7fbddd75f313..2b3daf53ef4c 100644 --- a/packages/InputDevices/res/values-ja/strings.xml +++ b/packages/InputDevices/res/values-ja/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英語(アメリカ)、インターナショナル配列"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英語(アメリカ)、Colemak配列"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英語(アメリカ)、Dvorak配列"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"英語(アメリカ)、Workman配列"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ドイツ語"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"フランス語"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"フランス語(カナダ)"</string> diff --git a/packages/InputDevices/res/values-ka-rGE/strings.xml b/packages/InputDevices/res/values-ka-rGE/strings.xml index 56bd3e36e61c..66d147e0f335 100644 --- a/packages/InputDevices/res/values-ka-rGE/strings.xml +++ b/packages/InputDevices/res/values-ka-rGE/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ინგლისური (აშშ), საერთაშორისო სტილი"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ინგლისური (აშშ), Colemak სტილი"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ინგლისური (აშშ), Dvorak სტილი"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ინგლისური (აშშ), Workman სტილი"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"გერმანული"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"ფრანგული"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ფრანგული (კანადა)"</string> diff --git a/packages/InputDevices/res/values-kk-rKZ/strings.xml b/packages/InputDevices/res/values-kk-rKZ/strings.xml index df2da884d3d9..d25354233062 100644 --- a/packages/InputDevices/res/values-kk-rKZ/strings.xml +++ b/packages/InputDevices/res/values-kk-rKZ/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Ағылшын (АҚШ), Халықаралық стилі"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Ағылшын (АҚШ), Colemak стилі"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Ағылшын (АҚШ), Dvorak стилі"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Ағылшын (АҚШ), Workman стилі"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Неміс"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Француз"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Француз (Канада)"</string> diff --git a/packages/InputDevices/res/values-kn-rIN/strings.xml b/packages/InputDevices/res/values-kn-rIN/strings.xml index f88076c84d1c..243e65977757 100644 --- a/packages/InputDevices/res/values-kn-rIN/strings.xml +++ b/packages/InputDevices/res/values-kn-rIN/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ಇಂಗ್ಲಿಷ್ (US), ಅಂತರರಾಷ್ಟ್ರೀಯ ಶೈಲಿ"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ಇಂಗ್ಲಿಷ್ (US), ಕೋಲ್ಮಾರ್ಕ್ ಶೈಲಿ"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ಇಂಗ್ಲಿಷ್ (US), ಡಿವೊರಾಕ್ ಶೈಲಿ"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ಇಂಗ್ಲೀಷ್ (US), ವರ್ಕ್ಮನ್ ಶೈಲಿ"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ಜರ್ಮನ್"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"ಫ್ರೆಂಚ್"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ಫ್ರೆಂಚ್ (ಕೆನಡಾ)"</string> diff --git a/packages/InputDevices/res/values-ko/strings.xml b/packages/InputDevices/res/values-ko/strings.xml index d2153758a957..775821094925 100644 --- a/packages/InputDevices/res/values-ko/strings.xml +++ b/packages/InputDevices/res/values-ko/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"영어(미국), 글로벌 스타일"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"영어(미국), 콜맥 스타일"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"영어(미국), 드보락 스타일"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"영어(미국), 워크맨 스타일"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"독일어"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"프랑스어"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"프랑스어(캐나다)"</string> diff --git a/packages/InputDevices/res/values-ky-rKG/strings.xml b/packages/InputDevices/res/values-ky-rKG/strings.xml index ee9cc5aea86f..aa7473320394 100644 --- a/packages/InputDevices/res/values-ky-rKG/strings.xml +++ b/packages/InputDevices/res/values-ky-rKG/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Англис (АКШ), эл аралык"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Англис (АКШ), Colemak стили"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Англис (АКШ), Dvorak стили"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Англисче (АКШ), Workman стили"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Немис"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Француз"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Француз (Канада)"</string> diff --git a/packages/InputDevices/res/values-lo-rLA/strings.xml b/packages/InputDevices/res/values-lo-rLA/strings.xml index 33c9f6123658..05b1b8351513 100644 --- a/packages/InputDevices/res/values-lo-rLA/strings.xml +++ b/packages/InputDevices/res/values-lo-rLA/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ອັງກິດ (ສະຫະລັດຯ), ແບບສາກົນ"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ອັງກິດ (ສະຫະລັດຯ), ແບບ Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ອັງກິດ (ສະຫະລັດຯ), ແບບ Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ອັງກິດ (ສະຫະລັດ), ແບບຄົນເຮັດວຽກ"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ເຢຍລະມັນ"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"ຝຣັ່ງ"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ຝຣັ່ງ (ຄານາດາ)"</string> diff --git a/packages/InputDevices/res/values-ml-rIN/strings.xml b/packages/InputDevices/res/values-ml-rIN/strings.xml index b42bdb71048a..0faa40ee2e39 100644 --- a/packages/InputDevices/res/values-ml-rIN/strings.xml +++ b/packages/InputDevices/res/values-ml-rIN/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ഇംഗ്ലീഷ് (യു.എസ്), അന്തർദ്ദേശീയ ശൈലി"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ഇംഗ്ലീഷ് (യു.എസ്), കോൽമാക് ശൈലി"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ഇംഗ്ലീഷ് (യു.എസ്), ദ്വരോക്ക് ശൈലി"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ഇംഗ്ലീഷ് (യുഎസ്), വർക്ക്മാൻ ശൈലി"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ജര്മ്മന്"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"ഫ്രഞ്ച്"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ഫ്രഞ്ച് (കാനഡ)"</string> diff --git a/packages/InputDevices/res/values-nb/strings.xml b/packages/InputDevices/res/values-nb/strings.xml index 378cdc1eb2f2..37604e333c1c 100644 --- a/packages/InputDevices/res/values-nb/strings.xml +++ b/packages/InputDevices/res/values-nb/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelsk (USA), internasjonal stil"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelsk (USA), Colemak-stil"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelsk (USA), Dvorak-stil"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engelsk (USA), workman-stil"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tysk"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransk"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransk (Canada)"</string> diff --git a/packages/InputDevices/res/values-ne-rNP/strings.xml b/packages/InputDevices/res/values-ne-rNP/strings.xml index 571d7174e3bb..4c3dec37f40c 100644 --- a/packages/InputDevices/res/values-ne-rNP/strings.xml +++ b/packages/InputDevices/res/values-ne-rNP/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"अङ्ग्रेजी (अमेरिकी), अन्तर्राष्ट्रिय शैली"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"अङ्ग्रेजी (अमेरिकी), कोलमाक शैली"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"अङ्ग्रेजी (अमेरिकी), डेभोर्याक शैली"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"अंग्रेजी (अमेरिका), वर्कम्यान शैली"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"जर्मन"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"फ्रान्सेली"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"फ्रेंच (क्यानाडा)"</string> diff --git a/packages/InputDevices/res/values-pl/strings.xml b/packages/InputDevices/res/values-pl/strings.xml index 7175705cffe1..2ae815e32456 100644 --- a/packages/InputDevices/res/values-pl/strings.xml +++ b/packages/InputDevices/res/values-pl/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Angielski (USA), międzynarodowy"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Angielski (USA), Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Angielski (USA), Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Angielski (USA), Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Niemiecki"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francuski"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francuski (Kanada)"</string> diff --git a/packages/InputDevices/res/values-pt-rBR/strings.xml b/packages/InputDevices/res/values-pt-rBR/strings.xml index 15c2d377b1c6..a1503a4ebcb3 100644 --- a/packages/InputDevices/res/values-pt-rBR/strings.xml +++ b/packages/InputDevices/res/values-pt-rBR/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglês (EUA), estilo internacional"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglês (EUA), estilo Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglês (EUA), estilo Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglês (EUA), estilo Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemão"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francês"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francês (Canadá)"</string> diff --git a/packages/InputDevices/res/values-pt/strings.xml b/packages/InputDevices/res/values-pt/strings.xml index 15c2d377b1c6..a1503a4ebcb3 100644 --- a/packages/InputDevices/res/values-pt/strings.xml +++ b/packages/InputDevices/res/values-pt/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglês (EUA), estilo internacional"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglês (EUA), estilo Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglês (EUA), estilo Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Inglês (EUA), estilo Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Alemão"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Francês"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Francês (Canadá)"</string> diff --git a/packages/InputDevices/res/values-ro/strings.xml b/packages/InputDevices/res/values-ro/strings.xml index ccfe5681f59d..795e9a2abd05 100644 --- a/packages/InputDevices/res/values-ro/strings.xml +++ b/packages/InputDevices/res/values-ro/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engleză (SUA), stil internațional"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engleză (SUA), stil Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engleză (SUA), stil Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engleză (SUA), stil Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Germană"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franceză"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franceză (Canada)"</string> diff --git a/packages/InputDevices/res/values-ru/strings.xml b/packages/InputDevices/res/values-ru/strings.xml index 7d3038e2e55f..ac4c81b490be 100644 --- a/packages/InputDevices/res/values-ru/strings.xml +++ b/packages/InputDevices/res/values-ru/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"английский (США, международная)"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"английский (США, Colemak)"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"английский (США, Dvorak)"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"английский (США, Workman)"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"немецкий"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"французский"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"французский (Канада)"</string> diff --git a/packages/InputDevices/res/values-sl/strings.xml b/packages/InputDevices/res/values-sl/strings.xml index f9e461081241..a643c8ee5e0e 100644 --- a/packages/InputDevices/res/values-sl/strings.xml +++ b/packages/InputDevices/res/values-sl/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"angleška (ZDA), mednarodni slog"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"angleška (ZDA), slog Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"angleška (ZDA), slog Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"angleška (ZDA), slog Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"nemška"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"francoska"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"francoska (Kanada)"</string> diff --git a/packages/InputDevices/res/values-sr/strings.xml b/packages/InputDevices/res/values-sr/strings.xml index a216bc38e545..b06f6fc9bd6f 100644 --- a/packages/InputDevices/res/values-sr/strings.xml +++ b/packages/InputDevices/res/values-sr/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"енглеска (САД), међународни стил"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"енглеска (САД), Colemak стил"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"енглеска (САД), Dvorak стил"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"енглеска (САД), Workman стил"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"немачка"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"француска"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"француска (Канада)"</string> diff --git a/packages/InputDevices/res/values-sv/strings.xml b/packages/InputDevices/res/values-sv/strings.xml index 23f2c981184d..89cb54eee02b 100644 --- a/packages/InputDevices/res/values-sv/strings.xml +++ b/packages/InputDevices/res/values-sv/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelskt (USA), internationellt"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelskt (USA), colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Engelskt (USA), dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Engelskt (USA), workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Tyskt"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Franskt"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Franskt (Kanada)"</string> diff --git a/packages/InputDevices/res/values-sw/strings.xml b/packages/InputDevices/res/values-sw/strings.xml index 720d427cfe26..1f447b0cc740 100644 --- a/packages/InputDevices/res/values-sw/strings.xml +++ b/packages/InputDevices/res/values-sw/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Kiingereza (Marekani), Muundo wa Kimataifa"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Kiingereza (Marekani), Muundo wa Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Kiingereza (Marekani), Muundo wa Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Kiingereza (US), mtindo wa Workman"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Kijerumani"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Kifaransa"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Kifaransa (Kanada)"</string> diff --git a/packages/InputDevices/res/values-ta-rIN/strings.xml b/packages/InputDevices/res/values-ta-rIN/strings.xml index 43d0bd18e280..32efe7b2ff7c 100644 --- a/packages/InputDevices/res/values-ta-rIN/strings.xml +++ b/packages/InputDevices/res/values-ta-rIN/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ஆங்கிலம் (யூஎஸ்), சர்வதேச நடை"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ஆங்கிலம் (யூஎஸ்), கோல்மாக் நடை"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ஆங்கிலம் (யூஎஸ்), டிவாரக் நடை"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ஆங்கிலம் (யூஎஸ்), வொர்க்மென் நடை"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"ஜெர்மன்"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"ஃபிரெஞ்ச்"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ஃபிரெஞ்ச் (கனடா)"</string> diff --git a/packages/InputDevices/res/values-te-rIN/strings.xml b/packages/InputDevices/res/values-te-rIN/strings.xml index d214ae610223..e07d4c876d72 100644 --- a/packages/InputDevices/res/values-te-rIN/strings.xml +++ b/packages/InputDevices/res/values-te-rIN/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"ఇంగ్లీష్ (US), అంతర్జాతీయ శైలి"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"ఇంగ్లీష్ (US), కొల్మాక్ శైలి"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"ఇంగ్లీష్ (US), ద్వోరక్ శైలి"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"ఆంగ్లం (US), వర్క్మాన్ శైలి"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"జర్మన్"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"ఫ్రెంచ్"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"ఫ్రెంచ్ (కెనడా)"</string> diff --git a/packages/InputDevices/res/values-tr/strings.xml b/packages/InputDevices/res/values-tr/strings.xml index 3dd0d3bdbea2..a8d9a0f10a2f 100644 --- a/packages/InputDevices/res/values-tr/strings.xml +++ b/packages/InputDevices/res/values-tr/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"İngilizce (ABD) Uluslararası stil"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"İngilizce (ABD) Colemak stili"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"İngilizce (ABD) Dvorak stili"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"İngilizce (ABD), Workman stili"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Almanca"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransızca"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransızca (Kanada)"</string> diff --git a/packages/InputDevices/res/values-ur-rPK/strings.xml b/packages/InputDevices/res/values-ur-rPK/strings.xml index 2c5a8b866514..3d2f618be1c5 100644 --- a/packages/InputDevices/res/values-ur-rPK/strings.xml +++ b/packages/InputDevices/res/values-ur-rPK/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"انگریزی (امریکہ)، انٹرنیشنل سٹائل"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"انگریزی (امریکہ)، کول مارک سٹائل"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"انگریزی (امریکہ)، ڈوراک سٹائل"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"انگریزی (US)، ورک مین اسٹائل"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"جرمن"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"فرانسیسی"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"فرانسیسی (کینیڈا)"</string> diff --git a/packages/InputDevices/res/values-uz-rUZ/strings.xml b/packages/InputDevices/res/values-uz-rUZ/strings.xml index ee2032846a2e..9c55615af30a 100644 --- a/packages/InputDevices/res/values-uz-rUZ/strings.xml +++ b/packages/InputDevices/res/values-uz-rUZ/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Inglizcha (AQSH), xalqaro uslubda"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Inglizcha (AQSH), Kolemak uslubida"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Inglizcha (AQSH), Dvorak uslubida"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Ingliz (AQSH), ishchi uslubda"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Nemischa"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Fransuzcha"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Fransuzcha (Kanada)"</string> diff --git a/packages/InputDevices/res/values-zh-rCN/strings.xml b/packages/InputDevices/res/values-zh-rCN/strings.xml index 2ba266781920..c61dccb66674 100644 --- a/packages/InputDevices/res/values-zh-rCN/strings.xml +++ b/packages/InputDevices/res/values-zh-rCN/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"英语(美国),国际风格"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"英语(美国),Colemak 风格"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"英语(美国),Dvorak 风格"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"英语(美国),Workman 风格"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"德语"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"法语"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"法语(加拿大)"</string> diff --git a/packages/InputDevices/res/values-zu/strings.xml b/packages/InputDevices/res/values-zu/strings.xml index 80899d0c14b1..0dcffb0518f8 100644 --- a/packages/InputDevices/res/values-zu/strings.xml +++ b/packages/InputDevices/res/values-zu/strings.xml @@ -8,8 +8,7 @@ <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"I-English (US), isitayela sakwamanye amazwe"</string> <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"I-English (US), isitayela se-Colemak"</string> <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"I-English (US), isitayela se-Dvorak"</string> - <!-- no translation found for keyboard_layout_english_us_workman_label (2944541595262173111) --> - <skip /> + <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"I-English (US), isitayela sokusebenza"</string> <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Isi-German"</string> <string name="keyboard_layout_french_label" msgid="813450119589383723">"Isi-French"</string> <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Isi-French (Canada)"</string> diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java index 11e937b44476..df4a9f0b5325 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java @@ -2,6 +2,7 @@ package com.android.mtp; import android.content.ContentValues; import android.content.Context; +import android.content.res.Resources; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; @@ -19,8 +20,15 @@ import java.nio.charset.StandardCharsets; * Database for MTP objects. * The object handle which is identifier for object in MTP protocol is not stable over sessions. * When we resume the process, we need to remap our document ID with MTP's object handle. - * The database object remembers the map of document ID and fullpath, and helps to remap object - * handle and document ID by comparing fullpath. + * + * If the remote MTP device is backed by typical file system, the file name + * is unique among files in a directory. However, MTP protocol itself does + * not guarantee the uniqueness of name so we cannot use fullpath as ID. + * + * Instead of fullpath, we use artificial ID generated by MtpDatabase itself. The database object + * remembers the map of document ID and object handle, and remaps new object handle with document ID + * by comparing the directory structure and object name. + * * TODO: Remove @VisibleForTesting annotation when we start to use this class. */ @VisibleForTesting @@ -30,10 +38,9 @@ class MtpDatabase { private static final String TABLE_MTP_DOCUMENTS = "MtpDocuments"; - static final String COLUMN_DEVICE_ID = "deviceId"; - static final String COLUMN_STORAGE_ID = "storageId"; - static final String COLUMN_OBJECT_HANDLE = "objectHandle"; - static final String COLUMN_FULL_PATH = "fullPath"; + static final String COLUMN_DEVICE_ID = "device_id"; + static final String COLUMN_STORAGE_ID = "storage_id"; + static final String COLUMN_OBJECT_HANDLE = "object_handle"; private static class OpenHelper extends SQLiteOpenHelper { private static final String CREATE_TABLE_QUERY = @@ -43,7 +50,6 @@ class MtpDatabase { COLUMN_DEVICE_ID + " INTEGER NOT NULL," + COLUMN_STORAGE_ID + " INTEGER NOT NULL," + COLUMN_OBJECT_HANDLE + " INTEGER," + - COLUMN_FULL_PATH + " TEXT NOT NULL," + DocumentsContract.Document.COLUMN_MIME_TYPE + " TEXT," + DocumentsContract.Document.COLUMN_DISPLAY_NAME + " TEXT NOT NULL," + DocumentsContract.Document.COLUMN_SUMMARY + " TEXT," + @@ -86,17 +92,15 @@ class MtpDatabase { } @VisibleForTesting - void putRootDocument(MtpRoot root) throws Exception { + void putRootDocument(Resources resources, MtpRoot root) throws Exception { database.beginTransaction(); try { final ContentValues values = new ContentValues(); values.put(COLUMN_DEVICE_ID, root.mDeviceId); values.put(COLUMN_STORAGE_ID, root.mStorageId); values.putNull(COLUMN_OBJECT_HANDLE); - values.put( - COLUMN_FULL_PATH, "/" + root.mDeviceId + "/" + escape(root.mDescription)); values.put(Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR); - values.put(Document.COLUMN_DISPLAY_NAME, root.mDescription); + values.put(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources)); values.putNull(Document.COLUMN_SUMMARY); values.putNull(Document.COLUMN_LAST_MODIFIED); values.putNull(Document.COLUMN_ICON); @@ -113,7 +117,7 @@ class MtpDatabase { } @VisibleForTesting - void putDocument(int deviceId, String parentFullPath, MtpObjectInfo info) throws Exception { + void putDocument(int deviceId, MtpObjectInfo info) throws Exception { database.beginTransaction(); try { final String mimeType = CursorHelper.formatTypeToMimeType(info.getFormat()); @@ -134,9 +138,7 @@ class MtpDatabase { values.put(COLUMN_DEVICE_ID, deviceId); values.put(COLUMN_STORAGE_ID, info.getStorageId()); values.put(COLUMN_OBJECT_HANDLE, info.getObjectHandle()); - values.put(COLUMN_FULL_PATH, parentFullPath + "/" + escape(info.getName())); - values.put( - Document.COLUMN_MIME_TYPE, CursorHelper.formatTypeToMimeType(info.getFormat())); + values.put(Document.COLUMN_MIME_TYPE, mimeType); values.put(Document.COLUMN_DISPLAY_NAME, info.getName()); values.putNull(Document.COLUMN_SUMMARY); values.put( diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java index 7ce325423978..8e1335fb8f27 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java @@ -1,6 +1,5 @@ package com.android.mtp; - import android.database.Cursor; import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; @@ -15,7 +14,6 @@ public class MtpDatabaseTest extends AndroidTestCase { MtpDatabase.COLUMN_DEVICE_ID, MtpDatabase.COLUMN_STORAGE_ID, MtpDatabase.COLUMN_OBJECT_HANDLE, - MtpDatabase.COLUMN_FULL_PATH, DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_SUMMARY, @@ -25,6 +23,8 @@ public class MtpDatabaseTest extends AndroidTestCase { DocumentsContract.Document.COLUMN_SIZE }; + private final TestResources resources = new TestResources(); + @Override public void tearDown() { MtpDatabase.deleteDatabase(getContext()); @@ -32,35 +32,10 @@ public class MtpDatabaseTest extends AndroidTestCase { public void testPutRootDocument() throws Exception { final MtpDatabase database = new MtpDatabase(getContext()); - final MtpRoot root = new MtpRoot( - 0, - 1, - "Device A", - "Storage", - 1000, - 2000, - ""); - database.putRootDocument(root); - - final MtpRoot duplicatedNameRoot = new MtpRoot( - 0, - 2, - "Device A", - "Storage", - 1000, - 2000, - ""); - database.putRootDocument(duplicatedNameRoot); - - final MtpRoot strangeNameRoot = new MtpRoot( - 0, - 3, - "Device A", - "/@#%&<>Storage", - 1000, - 2000, - ""); - database.putRootDocument(strangeNameRoot); + database.putRootDocument(resources, new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, "")); + database.putRootDocument(resources, new MtpRoot(0, 2, "Device", "Storage", 1000, 2000, "")); + database.putRootDocument( + resources, new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 1000, 2000, "")); final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES); assertEquals(3, cursor.getCount()); @@ -70,22 +45,23 @@ public class MtpDatabaseTest extends AndroidTestCase { assertEquals("deviceId", 0, cursor.getInt(1)); assertEquals("storageId", 1, cursor.getInt(2)); assertTrue("objectHandle", cursor.isNull(3)); - assertEquals("fullPath", "/0/Storage", cursor.getString(4)); - assertEquals("mimeType", DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(5)); - assertEquals("displayName", "Storage", cursor.getString(6)); - assertTrue("summary", cursor.isNull(7)); - assertTrue("lastModified", cursor.isNull(8)); - assertTrue("icon", cursor.isNull(9)); - assertEquals("flag", 0, cursor.getInt(10)); - assertEquals("size", 1000, cursor.getInt(11)); + assertEquals("mimeType", DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(4)); + assertEquals("displayName", "Device Storage", cursor.getString(5)); + assertTrue("summary", cursor.isNull(6)); + assertTrue("lastModified", cursor.isNull(7)); + assertTrue("icon", cursor.isNull(8)); + assertEquals("flag", 0, cursor.getInt(9)); + assertEquals("size", 1000, cursor.getInt(10)); cursor.moveToNext(); assertEquals("documentId", 2, cursor.getInt(0)); - assertEquals("fullPath", "/0/Storage", cursor.getString(4)); + assertEquals("displayName", "Device Storage", cursor.getString(5)); cursor.moveToNext(); assertEquals("documentId", 3, cursor.getInt(0)); - assertEquals("fullPath", "/0/%2F%40%23%25%26%3C%3EStorage", cursor.getString(4)); + assertEquals("displayName", "Device /@#%&<>Storage", cursor.getString(5)); + + cursor.close(); } public void testPutDocument() throws Exception { @@ -96,7 +72,7 @@ public class MtpDatabaseTest extends AndroidTestCase { builder.setStorageId(5); builder.setFormat(MtpConstants.FORMAT_TEXT); builder.setCompressedSize(1000); - database.putDocument(0, "/0/Storage", builder.build()); + database.putDocument(0, builder.build()); final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES); assertEquals(1, cursor.getCount()); @@ -105,17 +81,16 @@ public class MtpDatabaseTest extends AndroidTestCase { assertEquals("deviceId", 0, cursor.getInt(1)); assertEquals("storageId", 5, cursor.getInt(2)); assertEquals("objectHandle", 100, cursor.getInt(3)); - assertEquals("fullPath", "/0/Storage/test.txt", cursor.getString(4)); - assertEquals("mimeType", "text/plain", cursor.getString(5)); - assertEquals("displayName", "test.txt", cursor.getString(6)); - assertTrue("summary", cursor.isNull(7)); - assertTrue("lastModified", cursor.isNull(8)); - assertTrue("icon", cursor.isNull(9)); + assertEquals("mimeType", "text/plain", cursor.getString(4)); + assertEquals("displayName", "test.txt", cursor.getString(5)); + assertTrue("summary", cursor.isNull(6)); + assertTrue("lastModified", cursor.isNull(7)); + assertTrue("icon", cursor.isNull(8)); assertEquals( "flag", DocumentsContract.Document.FLAG_SUPPORTS_DELETE | DocumentsContract.Document.FLAG_SUPPORTS_WRITE, - cursor.getInt(10)); - assertEquals("size", 1000, cursor.getInt(11)); + cursor.getInt(9)); + assertEquals("size", 1000, cursor.getInt(10)); } } diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index 0c03814cc4e8..5765f0a90501 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -16,9 +16,6 @@ package com.android.mtp; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; import android.database.Cursor; import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; @@ -26,7 +23,6 @@ import android.net.Uri; import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract; import android.test.AndroidTestCase; -import android.test.mock.MockResources; import android.test.suitebuilder.annotation.SmallTest; import java.io.FileNotFoundException; @@ -39,16 +35,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { private TestContentResolver mResolver; private MtpDocumentsProvider mProvider; private TestMtpManager mMtpManager; - private final MockResources mResources = new MockResources() { - @Override - public String getString(int id) throws NotFoundException { - switch (id) { - case R.string.root_name: - return "%1$s %2$s"; - } - throw new NotFoundException(); - } - }; + private final TestResources mResources = new TestResources(); @Override public void setUp() throws IOException { diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java new file mode 100644 index 000000000000..eb80e3b60e6a --- /dev/null +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResources.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 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.mtp; + +import android.test.mock.MockResources; + +class TestResources extends MockResources { + @Override + public String getString(int id) throws NotFoundException { + switch (id) { + case R.string.root_name: + return "%1$s %2$s"; + } + throw new NotFoundException(); + } +} diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 5d622a069412..be5c0fe143b2 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -214,6 +214,7 @@ android:resumeWhilePausing="true" android:screenOrientation="behind" android:resizeableActivity="true" + android:configChanges="orientation|screenSize|smallestScreenSize" android:theme="@style/RecentsTheme.Wallpaper"> <intent-filter> <action android:name="com.android.systemui.recents.TOGGLE_RECENTS" /> diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml index 61870707882b..cc35c51c3ee8 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml @@ -35,18 +35,22 @@ <com.android.systemui.qs.QuickQSPanel android:id="@+id/quick_qs_panel" android:background="#0000" - android:layout_width="142dp" + android:layout_width="144dp" android:layout_height="match_parent" - android:layout_alignParentEnd="true" /> + android:layout_alignParentEnd="true" + android:layout_marginEnd="12dp" /> <LinearLayout android:id="@+id/expanded_group" android:layout_width="wrap_content" android:layout_height="match_parent" + android:gravity="center" android:clipChildren="false" android:clipToPadding="false" android:orientation="horizontal" - android:layout_alignParentEnd="true"> + android:layout_alignParentEnd="true" + android:layout_marginEnd="10dp"> + <com.android.systemui.statusbar.AlphaOptimizedFrameLayout android:id="@+id/settings_button_container" android:layout_width="48dp" @@ -74,7 +78,8 @@ <ImageView android:layout_width="48dp" - android:layout_height="match_parent" + android:layout_height="48dp" + android:padding="12dp" android:src="@drawable/ic_expand_less" android:tint="@android:color/white" /> </LinearLayout> diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 41ed64cacaeb..0e31d8dc9394 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Vou uit"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Vou in"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Skerm is vasgespeld"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Dit hou dit in sig totdat jy dit ontspeld. Raak en hou Terug en Oorsig op dieselfde tyd om te ontspeld."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Dit hou dit in sig totdat jy ontspeld. Raak en hou Oorsig om te ontspeld."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Het dit"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Nee, dankie"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Versteek <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 673710dcfd75..ec6ec93722a3 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"አስፋ"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ሰብስብ"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"ማያ ገጽ ተሰክቷል"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"ይህ እስከሚነቅሉት ድረስ ድረስ በዕይታ ውስጥ እንዲቆይ ያደርገዋል። ለመንቀል በተመሳሳይ ጊዜ ተመለስን እና አጠቃላይ ዕይታን አንድ ላይ ነክተው ይያዙ።"</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ይህ እስከሚነቅሉት ድረስ በዕይታ ውስጥ ያቆየዋል። እንዲነቀል ለማድረግ አጠቃላይ ዕይታን ነካ አድርገው ይያዙት።"</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"ገባኝ"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"አይ፣ አመሰግናለሁ"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ይደበቅ?"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index c2c7e655723e..02fc3fec2cc9 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -397,8 +397,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"توسيع"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"تصغير"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"تم تثبيت الشاشة"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"يساعد هذا على استمرار العرض حتى يتم إلغاء التثبيت. ويمكنك لمس \"رجوع\" و\"عرض عام\" مع الاستمرار في وقت واحد لإلغاء التثبيت."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"يساعد هذا على استمرار العرض حتى يتم إلغاء التثبيت. ويمكنك لمس \"عرض عام\" مع الاستمرار في وقت واحد لإلغاء التثبيت."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"حسنًا"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"لا، شكرًا"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"هل تريد إخفاء <xliff:g id="TILE_LABEL">%1$s</xliff:g>؟"</string> diff --git a/packages/SystemUI/res/values-az-rAZ/strings.xml b/packages/SystemUI/res/values-az-rAZ/strings.xml index a949dbbe8cfd..aba22f81c6a8 100644 --- a/packages/SystemUI/res/values-az-rAZ/strings.xml +++ b/packages/SystemUI/res/values-az-rAZ/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Genişləndirin"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Yığcamlaşdırın"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Ekrana sancaq taxıldı"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Sancaq götürülənə qədər bu, görünəcək. Sancağı götürmək üçün Geri və İcmal düymələrinə eyni vaxtda toxunun və saxlayın."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Sancaq götürülənə qədər bu, görünəcək. Sancağı götürmək üçün Geri və İcmal düymələrinə toxunun və saxlayın."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Anladım!"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Yox, çox sağ olun"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> gizlədilsin?"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index a59f4c513027..50ac61f8847b 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Разгъване"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Свиване"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Екранът е фиксиран"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Така екранът ще се показва, докато не го освободите. За да направите това, докоснете и задръжте бутона за връщане назад и този за общ преглед едновременно."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Така екранът ще се показва, докато не го освободите. За да направите това, докоснете и задръжте бутона за общ преглед."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Разбрах"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Не, благодаря"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Да се скрие ли „<xliff:g id="TILE_LABEL">%1$s</xliff:g>“?"</string> diff --git a/packages/SystemUI/res/values-bn-rBD/strings.xml b/packages/SystemUI/res/values-bn-rBD/strings.xml index f4adc14d95b8..f32cfcc85dc5 100644 --- a/packages/SystemUI/res/values-bn-rBD/strings.xml +++ b/packages/SystemUI/res/values-bn-rBD/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"প্রসারিত করুন"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"সঙ্কুচিত করুন"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"স্ক্রীন পিন করা হয়েছে"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"এটি আপনার আনপিন না করা পর্যন্ত এটিকে দর্শনে রাখে৷ আনপিন করতে একই সময়ে ফিরুন এবং ওভারভিউ এ স্পর্শ করে ধরে রাখুন৷"</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"এটি আপনার আনপিন না করা পর্যন্ত এটিকে দর্শনে রাখে৷ আনপিন করতে ওভারভিউ এ স্পর্শ করে ধরে রাখুন৷"</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"বুঝেছি"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"না থাক"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> লুকাবেন?"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 00f96a737e3f..429f70bf112a 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Amplia"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Replega"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"La pantalla està fixada"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Continuarà a la visualització fins que n\'anul·lis la fixació. Per fer-ho, toca i mantén premuts els botons Enrere i Visió general a la vegada."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Continuarà a la visualització fins que n\'anul·lis la fixació. Per fer-ho, toca i mantén premut el botó Visió general."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"D\'acord"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"No, gràcies"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vols amagar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index e429a06ea7d1..ee77cf9dad53 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -397,8 +397,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Rozbalit"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sbalit"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Obrazovka je připnuta"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Obsah bude připnut v zobrazení, dokud jej neuvolníte. Chcete-li jej uvolnit, stiskněte a podržte současně tlačítka Zpět a Přehled."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Obsah bude připnut v zobrazení, dokud jej neuvolníte. Uvolníte jej stisknutím a podržením tlačítka Přehled."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Rozumím"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Ne, děkuji"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Skrýt <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index e4436c8eebef..f749756d8e1f 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Udvid"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Skjul"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Skærmen er fastgjort"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Dette fastholder den i visningen, indtil du frigør den. Tryk på Tilbage og Oversigt på samme tid, og hold dem nede for at frigøre denne skærm."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Dette fastholder den i visningen, indtil du frigør den. Tryk på Oversigt, og hold den nede for at frigøre denne skærm."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"OK, det er forstået"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Nej tak"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vil du skjule <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index b1130833b8c6..cfaecaf4b0e6 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Maximieren"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Minimieren"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Bildschirm ist fixiert"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Der Bildschirm wird solange angezeigt, bis Sie die Fixierung aufheben. Berühren und halten Sie \"Zurück\" und \"Übersicht\" gleichzeitig, um die Fixierung aufzuheben."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Der Bildschirm wird solange angezeigt, bis Sie die Fixierung aufheben. Berühren und halten Sie \"Übersicht\", wenn Sie die Fixierung aufheben möchten."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Nein danke"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ausblenden?"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 27a019c4a0ac..6fc1b594f7c7 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Ανάπτυξη"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Σύμπτυξη"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Η οθόνη καρφιτσώθηκε"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Με αυτόν τον τρόπο παραμένει σε προβολή έως ότου την ξεκαρφιτσώσετε. Αγγίξτε παρατεταμένα \"Επιστροφή\" και \"Επισκόπηση\" ταυτόχρονα για ξεκαρφίτσωμα."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Με αυτόν τον τρόπο παραμένει σε προβολή έως ότου την ξεκαρφιτσώσετε. Αγγίξτε παρατεταμένα \"Επισκόπηση\" για ξεκαρφίτσωμα."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Το κατάλαβα"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Όχι"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Απόκρυψη <xliff:g id="TILE_LABEL">%1$s</xliff:g>;"</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index b1f71a25758c..ffdc3af75091 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Expand"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Collapse"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Screen is pinned"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"This keeps it in view until you unpin. Touch and hold Back and Overview at the same time to unpin."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"This keeps it in view until you unpin. Touch and hold Overview to unpin."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Hide <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index b1f71a25758c..ffdc3af75091 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Expand"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Collapse"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Screen is pinned"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"This keeps it in view until you unpin. Touch and hold Back and Overview at the same time to unpin."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"This keeps it in view until you unpin. Touch and hold Overview to unpin."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Hide <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index b1f71a25758c..ffdc3af75091 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Expand"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Collapse"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Screen is pinned"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"This keeps it in view until you unpin. Touch and hold Back and Overview at the same time to unpin."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"This keeps it in view until you unpin. Touch and hold Overview to unpin."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Hide <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index bca484af15a9..d4bb0a661259 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Contraer"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Pantalla fija"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Esta función mantiene fija la vista de la pantalla hasta que la desactivas. Mantén presionados los botones Atrás y Recientes al mismo tiempo para anular la fijación."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Esta función mantiene fija la vista de la pantalla hasta que la desactivas. Mantén presionado el botón Recientes para anular la fijación."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Entendido"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"No, gracias"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"¿Ocultar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 3c89db881dc1..d3cae624ac16 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Mostrar"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Ocultar"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Pantalla fijada"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"La pantalla se mantendrá visible hasta que dejes de fijarla. Para ello, mantén pulsados los botones de retroceso e información general."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"La pantalla se mantendrá visible hasta que dejes de fijarla. Para ello, mantén pulsado el botón de información general."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Entendido"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"No, gracias"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"¿Ocultar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-et-rEE/strings.xml b/packages/SystemUI/res/values-et-rEE/strings.xml index 01e5848d5142..0dfbdfc6e86d 100644 --- a/packages/SystemUI/res/values-et-rEE/strings.xml +++ b/packages/SystemUI/res/values-et-rEE/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Laiendamine"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Ahendamine"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Ekraan on kinnitatud"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"See hoiab selle kuval, kuni selle vabastate. Vabastamiseks puudutage ning hoidke korraga all nuppe Tagasi ja Ülevaade."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"See hoiab selle kuval, kuni selle vabastate. Vabastamiseks puudutage ja hoidke all nuppu Ülevaade."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Selge"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Tänan, ei"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Kas peita <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-eu-rES/strings.xml b/packages/SystemUI/res/values-eu-rES/strings.xml index 613b44a1cdf0..769164dcd65e 100644 --- a/packages/SystemUI/res/values-eu-rES/strings.xml +++ b/packages/SystemUI/res/values-eu-rES/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Zabaldu"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Tolestu"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Pantaila ainguratuta dago"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Horrela, ikusgai mantenduko da aingura kendu arte. Aingura kentzeko, eduki ukituta aldi berean \"Atzera\" eta \"Ikuspegi orokorra\" botoiak."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Horrela, ikusgai mantenduko da, aingura kendu arte. Aingura kentzeko, eduki ukituta \"Ikuspegi orokorra\" botoia."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Ados"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Ez, eskerrik asko"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ezkutatu nahi duzu?"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 996722599a4a..6d9d43f7a448 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -219,7 +219,7 @@ <string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"داده موقتاً متوقف شده است"</string> <string name="data_usage_disabled_dialog" msgid="8453242888903772524">"چون به محدودیت داده تنظیم شده رسیدهاید، دستگاه مصرف داده را برای باقیمانده این دوره موقتاً متوقف کرده است.\n\nاگر ادامه دهید شاید موجب کسر هزینه از طرف شرکت مخابراتی شما شود."</string> <string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"از سرگیری"</string> - <string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"اتصال اینترنتی وجود ندارد"</string> + <string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"اتصال اینترنتی ندارید"</string> <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi متصل شد"</string> <string name="gps_notification_searching_text" msgid="8574247005642736060">"جستجو برای GPS"</string> <string name="gps_notification_found_text" msgid="4619274244146446464">"مکان تنظیم شده توسط GPS"</string> @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"بزرگ کردن"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"کوچک کردن"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"صفحه نمایش پین شد"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"تا زمانی که پین را بردارید، در نما نگهداشته میشود. برای برداشتن پین، برگشت و نمای کلی را به صورت همزمان لمس کنید و نگهدارید."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"تا زمانی که پین را بردارید، در نما نگهداشته میشود. برای برداشتن پین، نمای کلی را لمس کنید و نگهدارید."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"متوجه شدم"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"نه سپاسگزارم"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> مخفی شود؟"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 2d138cbe695a..79bf369a129f 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Laajenna."</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Tiivistä."</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Näyttö on kiinnitetty"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Tämä pitää sen näkyvissä, kunnes poistat kiinnityksen. Kosketa Edellinen- ja Viimeisimmät-kohtaa samanaikaisesti pitkään kiinnityksen poistamiseksi."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Tämä pitää sen näkyvissä, kunnes poistat kiinnityksen. Kosketa Viimeisimmät-kohtaa pitkään kiinnityksen poistamiseksi."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Selvä"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Ei kiitos"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Piilotetaanko <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 979ee3534092..605e4c5bd089 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Développer"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Réduire"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"L\'écran est épinglé"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Cet écran est épinglé jusqu\'à ce que vous annuliez l\'opération. Pour annuler l\'épinglage, maintenez un doigt simultanément sur « Retour » et « Aperçu »."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Cet écran est épinglé jusqu\'à ce que vous annuliez l\'opération. Pour annuler l\'épinglage, maintenez le doigt sur « Aperçu »."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Non, merci"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Masquer <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 9ab6613e022c..6beed78e2cc4 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -327,8 +327,8 @@ <string name="interruption_level_priority_twoline" msgid="1564715335217164124">"Priorité\nuniquement"</string> <string name="interruption_level_alarms_twoline" msgid="3266909566410106146">"Alarmes\nuniquement"</string> <string name="keyguard_indication_charging_time" msgid="1757251776872835768">"Charge en cours… (chargé à 100 %% dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string> - <string name="keyguard_indication_charging_time_fast" msgid="9018981952053914986">"Charge rapide… (chargé à 100 % dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string> - <string name="keyguard_indication_charging_time_slowly" msgid="955252797961724952">"Charge lente… (chargé à 100 % dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string> + <string name="keyguard_indication_charging_time_fast" msgid="9018981952053914986">"Charge rapide… (chargé à 100 %% dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string> + <string name="keyguard_indication_charging_time_slowly" msgid="955252797961724952">"Charge lente… (chargé à 100 %% dans <xliff:g id="CHARGING_TIME_LEFT">%s</xliff:g>)"</string> <string name="accessibility_multi_user_switch_switcher" msgid="7305948938141024937">"Changer d\'utilisateur"</string> <string name="accessibility_multi_user_switch_switcher_with_current" msgid="8434880595284601601">"Changer d\'utilisateur (utilisateur actuel : <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>)"</string> <string name="accessibility_multi_user_switch_inactive" msgid="1424081831468083402">"Utilisateur actuel : <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>"</string> @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Développer"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Réduire"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Écran épinglé"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Cet écran est épinglé jusqu\'à annulation de l\'opération. Pour annuler l\'épinglage, appuyez simultanément sur \"Retour\" et \"Aperçu\" de manière prolongée."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Cet écran est épinglé jusqu\'à annulation de l\'opération. Pour annuler l\'épinglage, appuyez de manière prolongée sur \"Aperçu\"."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Non, merci"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Masquer <xliff:g id="TILE_LABEL">%1$s</xliff:g> ?"</string> diff --git a/packages/SystemUI/res/values-gl-rES/strings.xml b/packages/SystemUI/res/values-gl-rES/strings.xml index bc441507cb8f..9a66d879c5c6 100644 --- a/packages/SystemUI/res/values-gl-rES/strings.xml +++ b/packages/SystemUI/res/values-gl-rES/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Ampliar"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Contraer"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"A pantalla está fixada"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"A pantalla manterase visible ata que anules a fixación. Para facelo, mantén premido Atrás e Visión xeral ao mesmo tempo."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"A pantalla manterase visible ata que anules a fixación. Para facelo, mantén premido Visión xeral."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"De acordo"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Non, grazas"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Queres ocultar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-gu-rIN/strings.xml b/packages/SystemUI/res/values-gu-rIN/strings.xml index ba8c517bb5ff..fe604484079f 100644 --- a/packages/SystemUI/res/values-gu-rIN/strings.xml +++ b/packages/SystemUI/res/values-gu-rIN/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"વિસ્તૃત કરો"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"સંકુચિત કરો"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"સ્ક્રીન પિન કરેલ છે"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"તમે જ્યાં સુધી અનપિન કરશો નહીં ત્યાં સુધી આ તેને દૃશ્યમાં રાખે છે. અનપિન કરવા માટે બેકને ટચ કરો અને પકડો અને તે જ સમયે વિહંગાવલોકન કરો."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"તમે જ્યાં સુધી અનપિન કરશો નહીં ત્યાં સુધી આ તેને દૃશ્યમાં રાખે છે. અનપિન કરવા માટે વિહંગાવલોકનને ટચ કરો અને પકડો."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"સમજાઈ ગયું"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"નહીં આભાર"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ને છુપાવીએ?"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 669ce5a41e6f..a433ca51d736 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"विस्तृत करें"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"संक्षिप्त करें"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"स्क्रीन पिन कर दी गई है"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"इससे वह तब तक दृश्य में रहता है जब तक कि आप उसे अनपिन नहीं कर देते. अनपिन करने के लिए वापस जाएं और अवलोकन करें को एक ही समय पर स्पर्श करके रखें."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"इससे वह तब तक दृश्य में बना रहता है जब तक कि आप उसे अनपिन नहीं कर देते. अनपिन करने के लिए अवलोकन करें को स्पर्श करके रखें."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"समझ लिया"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"नहीं, रहने दें"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> को छिपाएं?"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index e928cbce01c1..b72ac3d56bfd 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -394,8 +394,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Proširivanje"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sažimanje"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Zaslon je prikvačen"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Zaslon će tako ostati u prvom planu dok ga ne otkvačite. Istovremeno dodirnite i držite Natrag i Pregled da biste ga otkvačili."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Zaslon će tako ostati u prvom planu dok ga ne otkvačite. Dodirnite i držite Pregled da biste ga otkvačili."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Shvaćam"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Ne, hvala"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Želite li sakriti pločicu <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index a347ede61622..d27813aca312 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Kibontás"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Összecsukás"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"A képernyő rögzítve van"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Megjelenítve tartja addig, amíg Ön fel nem oldja fel a rögzítést. A rögzítés feloldásához tartsa egyszerre lenyomva a Vissza és az Áttekintés lehetőséget."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Megjelenítve tartja addig, amíg Ön fel nem oldja a rögzítést. A feloldáshoz tartsa lenyomva az Áttekintés lehetőséget."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Értem"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Nem, köszönöm"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Elrejti ezt: <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-hy-rAM/strings.xml b/packages/SystemUI/res/values-hy-rAM/strings.xml index 18f6c8892679..125b19da1291 100644 --- a/packages/SystemUI/res/values-hy-rAM/strings.xml +++ b/packages/SystemUI/res/values-hy-rAM/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Ընդարձակել"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Կոծկել"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Էկրանն ամրացված է"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Էկրանը կմնա տեսադաշտում, մինչև այն ապամրացնեք: Ապամրացնելու համար միաժամանակ հպեք և պահեք Համատեսքի և Հետ կոճակները:"</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Էկրանը կմնա տեսադաշտում, մինչև այն ապամրացնեք: Ապամրացնելու համար հպեք և պահեք Համատեսքի կոճակը:"</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Հասկանալի է"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Ոչ, շնորհակալություն"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Թաքցնե՞լ <xliff:g id="TILE_LABEL">%1$s</xliff:g>-ը:"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index afcac631b137..1370ef402f82 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Luaskan"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Ciutkan"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Layar dipasangi pin"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Ini akan terus ditampilkan sampai Anda melepas pin. Sentuh dan tahan tombol Kembali dan Ringkasan secara bersamaan untuk melepas pin."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ini akan terus ditampilkan sampai Anda melepas pin. Sentuh dan tahan tombol Ringkasan untuk melepas pin."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Mengerti"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Lain kali"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Sembunyikan <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-is-rIS/strings.xml b/packages/SystemUI/res/values-is-rIS/strings.xml index 48d0d1a3be25..a57e66cdc59a 100644 --- a/packages/SystemUI/res/values-is-rIS/strings.xml +++ b/packages/SystemUI/res/values-is-rIS/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Stækka"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Minnka"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Skjárinn er festur"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Þetta heldur þessu opnu þangað til þú losar. Haltu bakk- og yfirlitshnöppunum inni á sama tíma til að losa."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Þetta heldur þessu opnu þangað til þú losar. Haltu yfirlitshnappinum inni til að losa."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Ég skil"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Nei, takk"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Fela <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index dea033010ff7..77be0171465c 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Espandi"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Comprimi"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"La schermata è bloccata"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"La schermata rimane visibile finché la sblocchi. Tocca e tieni premuti contemporaneamente Indietro e Panoramica per sbloccare."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"La schermata rimane visibile finché la sblocchi. Tocca Panoramica e tieni premuto per sbloccare."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"No, grazie"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Nascondere <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 772a3fa95315..20cb1ea5660a 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"הרחב"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"כווץ"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"המסך מוצמד"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"שומר בתצוגה עד לביטול ההצמדה. גע והחזק בו-זמנית ב\'הקודם\' ו\'סקירה\' כדי לבטל הצמדה."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"שומר בתצוגה עד לביטול ההצמדה. גע והחזק בו-זמנית ב\'הקודם\' ו\'סקירה\' כדי לבטל הצמדה."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"הבנתי"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"לא, תודה"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"להסתיר<xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 6e570e312961..3d40c73e65c5 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"展開"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"折りたたむ"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"画面が固定されました"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"固定を解除するまで画面が常に表示されるようになります。[戻る]と[最近]を同時に押し続けると固定が解除されます。"</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"固定を解除するまで画面が常に表示されるようになります。[最近]を押し続けると固定が解除されます。"</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"はい"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"いいえ"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>を非表示にしますか?"</string> diff --git a/packages/SystemUI/res/values-ka-rGE/strings.xml b/packages/SystemUI/res/values-ka-rGE/strings.xml index 6f2612395f49..8f42d5b50150 100644 --- a/packages/SystemUI/res/values-ka-rGE/strings.xml +++ b/packages/SystemUI/res/values-ka-rGE/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"გავრცობა"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ჩაკეცვა"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"ეკრანი ჩამაგრებულია"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"ამით ის ხედში დარჩება, სანამ ჩამაგრებას არ გააუქმებთ. ჩამაგრების გასაუქმებლად შეეხეთ და დააყოვნეთ „დაბრუნება“-ზე და „მიმოხილვა“-ზე ერთდროულად."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ამით ის ხედში დარჩება, სანამ ჩამაგრებას არ გააუქმებთ. ჩამაგრების გასაუქმებლად შეეხეთ და დააყოვნეთ „მიმოხილვა“-ზე."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"გასაგებია"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"არა, გმადლობთ"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"დაიმალოს <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-kk-rKZ/strings.xml b/packages/SystemUI/res/values-kk-rKZ/strings.xml index 2668de4ccd69..192518a52246 100644 --- a/packages/SystemUI/res/values-kk-rKZ/strings.xml +++ b/packages/SystemUI/res/values-kk-rKZ/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Жаю"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Жию"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Экран түйрелді"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Бұл сіз оны босатқанша оны көрсетіп тұрады. Босату үшін «Кері» және «Шолу» түймелерін бір уақытта басып тұрыңыз."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Бұл сіз оны босатқанша оны көрсетіп тұрады. Босату үшін «Шолу» түймесін бір уақытта басып тұрыңыз."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Түсіндім"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Жоқ, рақмет"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> жасыру керек пе?"</string> diff --git a/packages/SystemUI/res/values-km-rKH/strings.xml b/packages/SystemUI/res/values-km-rKH/strings.xml index 08d761f418f8..284af53566f5 100644 --- a/packages/SystemUI/res/values-km-rKH/strings.xml +++ b/packages/SystemUI/res/values-km-rKH/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"ពង្រីក"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"បង្រួម"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"អេក្រង់ត្រូវបានភ្ជាប់"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"រក្សាទុកវាក្នុងទិដ្ឋភាពរហូតដល់អ្នកផ្ដាច់។ ប៉ះ ហើយសង្កត់ថយក្រោយ និងទិដ្ឋភាពនៅពេលតែមួយដើម្បីផ្ដាច់។"</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"វារក្សាទុកក្នុងទិដ្ឋភាពរហូតដល់អ្នកផ្ដាច់។ ប៉ះ និងសង្កត់ទិដ្ឋភាពដើម្បីផ្ដាច់។"</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"យល់ហើយ"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"ទេ អរគុណ"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"លាក់ <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-kn-rIN/strings.xml b/packages/SystemUI/res/values-kn-rIN/strings.xml index 2cf1697ce4cb..484551bd6fbf 100644 --- a/packages/SystemUI/res/values-kn-rIN/strings.xml +++ b/packages/SystemUI/res/values-kn-rIN/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"ವಿಸ್ತರಿಸು"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ಸಂಕುಚಿಸು"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"ಪರದೆಯನ್ನು ಪಿನ್ ಮಾಡಲಾಗಿದೆ"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"ನೀವು ಅನ್ಪಿನ್ ಮಾಡುವವರೆಗೆ ಅದನ್ನು ವೀಕ್ಷಣೆಯಲ್ಲಿಡುತ್ತದೆ. ಅನ್ಪಿನ್ ಮಾಡಲು ಒಂದೇ ಸಮಯದಲ್ಲಿ ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಒತ್ತಿ ಹಿಡಿದುಕೊಳ್ಳಿ ಹಾಗೂ ಅವಲೋಕಿಸಿ."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ನೀವು ಅನ್ಪಿನ್ ಮಾಡುವವರೆಗೆ ಅದನ್ನು ವೀಕ್ಷಣೆಯಲ್ಲಿಡುತ್ತದೆ. ಅನ್ಪಿನ್ ಮಾಡಲು ಅವಲೋಕನವನ್ನು ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"ತಿಳಿಯಿತು"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"ಧನ್ಯವಾದಗಳು"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ಮರೆಮಾಡುವುದೇ?"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 4e8be286367c..c7f548970c1a 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"펼치기"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"접기"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"화면 고정됨"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"고정 해제하기 전까지 계속 표시됩니다. 고정 해제하려면 뒤로와 최근 사용을 동시에 길게 터치합니다."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"고정 해제하기 전까지 계속 표시됩니다. 고정 해제하려면 최근 사용을 길게 터치합니다."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"확인"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"거부"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>을(를) 숨기시겠습니까?"</string> diff --git a/packages/SystemUI/res/values-ky-rKG/strings.xml b/packages/SystemUI/res/values-ky-rKG/strings.xml index fc6897f51d30..f57c9fcac9eb 100644 --- a/packages/SystemUI/res/values-ky-rKG/strings.xml +++ b/packages/SystemUI/res/values-ky-rKG/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Жайып көрсөтүү"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Жыйнап коюу"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Экран кадалган"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Бул бошотулмайынча көрүнө берет. Бошотуу үчүн, бир убакта Артка жана Карап чыгууну коё бербей басып туруңуз."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Бул бошотулмайынча көрүнө берет. Бошотуу үчүн, Карап чыгууну коё бербей басып туруңуз."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Түшүндүм"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Жок, рахмат"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> жашырылсынбы?"</string> diff --git a/packages/SystemUI/res/values-lo-rLA/strings.xml b/packages/SystemUI/res/values-lo-rLA/strings.xml index 6bb20437f2ab..18f3111ff4f9 100644 --- a/packages/SystemUI/res/values-lo-rLA/strings.xml +++ b/packages/SystemUI/res/values-lo-rLA/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"ຂະຫຍາຍ"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ຫຍໍ້ລົງ"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"ປັກໝຸດໜ້າຈໍແລ້ວ"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"ມັນຈະຮັກສາໜ້າຈໍໄວ້ໃນມຸມມອງຂອງທ່ານຈົນກວ່າທ່ານຈະຖອດໝຸດ. ແຕະປຸ່ມ ກັບຄືນ ແລະ ພາບຮວມ ຄ້າງໄວ້ພ້ອມກັນເພື່ອຖອດໝຸດ."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ມັນຈະຮັກສາໜ້າຈໍໄວ້ໃນມຸມມອງຂອງທ່ານຈົນກວ່າທ່ານຈະຖອດໝຸດ. ແຕະປຸ່ມ ພາບຮວມ ຄ້າງໄວ້ເພື່ອຖອດໝຸດ."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"ເຂົ້າໃຈແລ້ວ"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"ບໍ່, ຂອບໃຈ"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"ເຊື່ອງ <xliff:g id="TILE_LABEL">%1$s</xliff:g> ຫຼືບໍ່?"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 8883cb6c3a66..307b7d908fb1 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Išskleisti"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sutraukti"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Ekranas prisegtas"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Tai bus rodoma, kol atsegsite. Kad atsegtumėte, tuo pačiu metu palieskite ir laikykite „Atgal“ ir „Apžvalga“."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Tai bus rodoma, kol atsegsite. Kad atsegtumėte, palieskite ir laikykite „Apžvalga“."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Supratau"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Ne, ačiū"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Slėpti „<xliff:g id="TILE_LABEL">%1$s</xliff:g>“?"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 47ca06a6db29..3660cda7691e 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -394,8 +394,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Izvērst"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Sakļaut"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Ekrāns ir piesprausts"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Šādi tas būs redzams līdz brīdim, kad to atspraudīsiet. Lai atspraustu, vienlaikus pieskarieties vienumiem “Atpakaļ” un “Pārskats” un turiet tos nospiestus."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Šādi tas būs redzams līdz brīdim, kad to atspraudīsiet. Lai atspraustu, pieskarieties vienumam “Pārskats” un turiet to nospiestu."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Sapratu!"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Nē, paldies"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vai paslēpt vienumu <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-mk-rMK/strings.xml b/packages/SystemUI/res/values-mk-rMK/strings.xml index 891ffd89ec07..f8d0417b555f 100644 --- a/packages/SystemUI/res/values-mk-rMK/strings.xml +++ b/packages/SystemUI/res/values-mk-rMK/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Прошири"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Собери"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Екранот е прикачен"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Ќе се гледа сè додека не го откачите. Допрете и држете Назад и Краток преглед истовремено за откачување."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ќе се гледа сè додека не го откачите. Допрете и држете Краток преглед за откачување."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Сфатив"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Не, фала"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Сокриј <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-ml-rIN/strings.xml b/packages/SystemUI/res/values-ml-rIN/strings.xml index 64d4184eff08..516e846bdae3 100644 --- a/packages/SystemUI/res/values-ml-rIN/strings.xml +++ b/packages/SystemUI/res/values-ml-rIN/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"വികസിപ്പിക്കുക"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ചുരുക്കുക"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"സ്ക്രീൻ പിൻ ചെയ്തു"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"നിങ്ങൾ അൺപിൻ ചെയ്യുന്നതുവരെ ഇത് കാണുന്ന വിധത്തിൽ നിലനിർത്തുന്നു. അൺപിൻ ചെയ്യാൻ \'മടങ്ങുക\', \'ചുരുക്കവിവരണം\' എന്നിവ ഒരേ സമയം സ്പർശിച്ച് പിടിക്കുക."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"നിങ്ങൾ അൺപിൻ ചെയ്യുന്നതുവരെ ഇത് കാണുന്ന വിധത്തിൽ നിലനിർത്തുന്നു. അൺപിൻ ചെയ്യുന്നതിന് \'ചുരുക്കവിവരണം\' സ്പർശിച്ചുപിടിക്കുക."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"മനസ്സിലായി"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"വേണ്ട, നന്ദി"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> എന്നത് മറയ്ക്കണോ?"</string> diff --git a/packages/SystemUI/res/values-mn-rMN/strings.xml b/packages/SystemUI/res/values-mn-rMN/strings.xml index 740ee0d5476f..8cb8df181445 100644 --- a/packages/SystemUI/res/values-mn-rMN/strings.xml +++ b/packages/SystemUI/res/values-mn-rMN/strings.xml @@ -391,8 +391,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Дэлгэх"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Хураах"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Дэлгэц эхэнд байрлуулагдсан"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Таныг эхэнд нээхийг болиулах хүртэл харагдана. Хүрээд, Back дээр удаан дараад хаахдаа Overview-ийг дар"</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Таныг эхэнд нээхийг болиулах хүртэл харагдана. Хаахын тулд хүрээдOverview-ийг дар"</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Ойлголоо"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Үгүй"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>-ийг нуух уу?"</string> diff --git a/packages/SystemUI/res/values-mr-rIN/strings.xml b/packages/SystemUI/res/values-mr-rIN/strings.xml index 00d7bc8b5fcb..cfa6b5ffac54 100644 --- a/packages/SystemUI/res/values-mr-rIN/strings.xml +++ b/packages/SystemUI/res/values-mr-rIN/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"विस्तृत करा"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"संकुचित करा"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"स्क्रीन पिन केलेली आहे"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"हे आपण अनपिन करेपर्यंत दृश्यामध्ये ते ठेवते. अनपिन करण्यासाठी एकाच वेळी परत आणि अलीकडील ला स्पर्श करा आणि धरून ठेवा आणि विहंगावलोकन करा."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"हे आपण अनपिन करेपर्यंत दृश्यामध्ये ते ठेवते. अनपिन करण्यासाठी स्पर्श करा आणि धरून ठेवा."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"समजले"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"नाही धन्यवाद"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> लपवायचे?"</string> diff --git a/packages/SystemUI/res/values-ms-rMY/strings.xml b/packages/SystemUI/res/values-ms-rMY/strings.xml index 10865dd616ec..6ff9a612e52d 100644 --- a/packages/SystemUI/res/values-ms-rMY/strings.xml +++ b/packages/SystemUI/res/values-ms-rMY/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Kembangkan"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Runtuhkan"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Skrin telah disemat"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Ini akan memastikan skrin kelihatan sehingga anda menyahsemat. Sentuh dan tahan Kembali dan Gambaran Keseluruhan pada masa yang sama untuk menyahsemat."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ini akan memastikan skrin kelihatan sehingga anda menyahsemat. Sentuh dan tahan Gambaran Keseluruhan untuk menyahsemat."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Faham"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Tidak"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Sembunyikan <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-my-rMM/strings.xml b/packages/SystemUI/res/values-my-rMM/strings.xml index 5cb608725bb0..e4b3e5400a4b 100644 --- a/packages/SystemUI/res/values-my-rMM/strings.xml +++ b/packages/SystemUI/res/values-my-rMM/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"တိုးချဲ့ရန်"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ခေါက်သိမ်းရန်..."</string> <string name="screen_pinning_title" msgid="3273740381976175811">"မျက်နှာပြင် ပင်ထိုးပြီးပါပြီ"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"သင်ပင်ဖြုတ်သည့် တိုင်အောင် ၎င်းအား မြင်ကွင်းတွင် ထားရှိပါမည်။ ပင်ဖြုတ်ရန် အနောက်နှင့် ခြုံငုံကြည့်ခြင်းကို ဖိ၍ နှိပ်ထားနိုင်သည်။"</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"သင်ပင်ဖြုတ်သည့် တိုင်အောင် ၎င်းအား မြင်ကွင်းတွင် ထားရှိပါမည်။ ပင်ဖြုတ်ရန် ခြုံငုံကြည့်ခြင်းကို ဖိ၍ နှိပ်ထားနိုင်သည်။"</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"အဲဒါ ရပြီ"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"မလို ကျေးဇူးပဲ"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> ဝှက်မည်လား?"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 39b404a5bdca..2882d7a81a4b 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Utvid"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Skjul"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Skjermen er låst"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"På denne måten blir skjermen synlig frem til du låser den opp. Trykk på og hold inne Tilbake og Oversikt samtidig for å låse opp."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"På denne måten blir skjermen synlig frem til du låser den opp. Trykk på og hold inne Tilbake og Oversikt for å låse opp."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Skjønner"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Nei takk"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vil du skjule <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-ne-rNP/strings.xml b/packages/SystemUI/res/values-ne-rNP/strings.xml index 2a312f3a1a97..19d84ce0cccf 100644 --- a/packages/SystemUI/res/values-ne-rNP/strings.xml +++ b/packages/SystemUI/res/values-ne-rNP/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"विस्तार गर्नुहोस्"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"संक्षिप्त पार्नुहोस्"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"पर्दा राखेका छ"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"तपाईँ अनपिन सम्म यो दृश्य मा राख्छ। छुनुहोस् र अनपिन फिर्ता र सिंहावलोकन नै समय मा पकड।"</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"तपाईँ अनपिन सम्म यो दृश्य मा राख्छ। छुनुहोस् र अनपिन गर्न सिंहावलोकन पकड।"</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"बुझेँ"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"धन्यवाद पर्दैन"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"लुकाउनुहुन्छ <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> @@ -440,7 +440,7 @@ <string name="qs_rearrange" msgid="8060918697551068765">"द्रुत सेटिङहरू पुनः व्यवस्थित गर्नुहोस्"</string> <string name="show_brightness" msgid="6613930842805942519">"द्रुत सेटिङहरूमा उज्यालो देखाउनुहोस्"</string> <string name="experimental" msgid="6198182315536726162">"प्रयोगात्मक"</string> - <string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लुटुथ सक्रिय पार्नुहुन्छ?"</string> + <string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लुटुथ सक्रिय पार्ने हो?"</string> <string name="enable_bluetooth_message" msgid="9106595990708985385">"आफ्नो ट्याब्लेटसँग किबोर्ड जोड्न, पहिले तपाईँले ब्लुटुथ सक्रिय गर्नुपर्छ।"</string> <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"सक्रिय पार्नुहोस्"</string> </resources> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 6bbb340e7c58..95ddeab7c6fb 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Uitvouwen"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Samenvouwen"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Scherm is vastgezet"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Het scherm blijft zichtbaar totdat u het u losmaakt. Houd \'Terug\' en \'Overzicht\' tegelijkertijd aangeraakt om het los te maken."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Het scherm blijft zichtbaar totdat u het u losmaakt. Houd \'Overzicht\' aangeraakt om het los te maken."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Ik snap het"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Nee, bedankt"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> verbergen?"</string> diff --git a/packages/SystemUI/res/values-pa-rIN/strings.xml b/packages/SystemUI/res/values-pa-rIN/strings.xml index 61b68c1fd485..6cb50b3be0e9 100644 --- a/packages/SystemUI/res/values-pa-rIN/strings.xml +++ b/packages/SystemUI/res/values-pa-rIN/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"ਵਿਸਤਾਰ ਕਰੋ"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ਨਸ਼ਟ ਕਰੋ"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"ਸਕ੍ਰੀਨ ਪਿੰਨ ਕੀਤੀ"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"ਇਹ ਇਸਨੂੰ ਦ੍ਰਿਸ਼ ਵਿੱਚ ਰੱਖਦਾ ਹੈ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਅਨਪਿਨ ਨਹੀਂ ਕਰਦੇ। ਅਨਪਿਨ ਕਰਨ ਲਈ ਪਿੱਛੇ ਅਤੇ ਰੂਪ-ਰੇਖਾ ਨੂੰ ਇੱਕੋ ਸਮੇਂ ਛੋਹਵੋ ਅਤੇ ਹੋਲਡ ਕਰੋ।"</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ਇਹ ਇਸਨੂੰ ਦ੍ਰਿਸ਼ ਵਿੱਚ ਰੱਖਦਾ ਹੈ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਅਨਪਿਨ ਨਹੀਂ ਕਰਦੇ। ਅਨਪਿਨ ਕਰਨ ਲਈ ਰੂਪ-ਰੇਖਾ ਨੂੰ ਛੋਹਵੋ ਅਤੇ ਹੋਲਡ ਕਰੋ।"</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"ਸਮਝ ਗਿਆ"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"ਨਹੀਂ ਧੰਨਵਾਦ"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"ਕੀ <xliff:g id="TILE_LABEL">%1$s</xliff:g> ਨੂੰ ਲੁਕਾਉਣਾ ਹੈ?"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index caa308109eab..1821cdd2fa73 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Rozwiń"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Zwiń"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Ekran jest przypięty"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Ekran będzie widoczny, dopóki go nie odepniesz. Aby to zrobić, kliknij i przytrzymaj jednocześnie Wstecz i Przegląd."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ekran będzie widoczny, dopóki go nie odepniesz. Aby to zrobić, kliknij i przytrzymaj Przegląd."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Nie, dziękuję"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Ukryć <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index b82eb3275058..68ecfc8f2930 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Recolher"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"A tela está fixada"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Ela é mantida à vista até que seja liberada. Toque em \"Voltar\" e \"Visão Geral\" e mantenha essas opções pressionadas ao mesmo tempo para liberar."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ela é mantida à vista até que seja liberada. Toque em \"Visão geral\" e mantenha essa opção pressionada para liberar."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Entendi"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Não, obrigado"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Esconder <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index c13790848e7b..61a76f97f816 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Reduzir"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"O ecrã está fixado"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Será mantido na vista até soltar. Toque sem soltar em Anterior e Vista geral em simultâneo para soltar."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Será mantido na vista até soltar. Toque sem soltar em Vista geral para soltar."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Compreendi"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Não, obrigado"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Pretende ocultar <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index b82eb3275058..68ecfc8f2930 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Expandir"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Recolher"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"A tela está fixada"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Ela é mantida à vista até que seja liberada. Toque em \"Voltar\" e \"Visão Geral\" e mantenha essas opções pressionadas ao mesmo tempo para liberar."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ela é mantida à vista até que seja liberada. Toque em \"Visão geral\" e mantenha essa opção pressionada para liberar."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Entendi"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Não, obrigado"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Esconder <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index b5819ea1aef0..3af273de6f0c 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -255,7 +255,7 @@ <string name="quick_settings_location_off_label" msgid="7464544086507331459">"Localizarea este dezactivată"</string> <string name="quick_settings_media_device_label" msgid="1302906836372603762">"Dispozitiv media"</string> <string name="quick_settings_rssi_label" msgid="7725671335550695589">"RSSI"</string> - <string name="quick_settings_rssi_emergency_only" msgid="2713774041672886750">"Numai apeluri de urgenţă"</string> + <string name="quick_settings_rssi_emergency_only" msgid="2713774041672886750">"Numai apeluri de urgență"</string> <string name="quick_settings_settings_label" msgid="5326556592578065401">"Setări"</string> <string name="quick_settings_time_label" msgid="4635969182239736408">"Ora"</string> <string name="quick_settings_user_label" msgid="5238995632130897840">"Eu"</string> @@ -394,8 +394,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Extindeți"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Restrângeți"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Ecranul este fixat"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Ecranul este afișat până anulați fixarea. Apăsați lung pe Înapoi și pe Vizualizare generală simultan pentru a anula fixarea."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ecranul este afișat până anulați fixarea. Apăsați lung pe Vizualizare generală pentru a anula fixarea."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Am înțeles"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Nu, mulțumesc"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Ascundeți <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 080668411bfd..296876c30d3b 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -397,8 +397,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Развернуть"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Свернуть"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Блокировка в приложении включена"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Это приложение останется активным, пока вы не отмените блокировку, одновременно нажав кнопки \"Назад\" и \"Обзор\"."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Приложение останется активным, пока вы не отмените блокировку, одновременно нажав кнопки Назад и Обзор."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"ОК"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Нет, спасибо"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Скрыть параметр \"<xliff:g id="TILE_LABEL">%1$s</xliff:g>\"?"</string> diff --git a/packages/SystemUI/res/values-si-rLK/strings.xml b/packages/SystemUI/res/values-si-rLK/strings.xml index 39147ff772d4..185fc05769a7 100644 --- a/packages/SystemUI/res/values-si-rLK/strings.xml +++ b/packages/SystemUI/res/values-si-rLK/strings.xml @@ -393,8 +393,7 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"දිග හරින්න"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"හකුළන්න"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"තීරය අමුණන ලදි"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"ඔබ ගලවන තෙක් එය දර්ශනය තුළ මෙය තබයි. ගැලවීමට ආපසු සහ දළ විශ්ලේෂණය එකම වේලාවක ස්පර්ෂ කර අල්ලා සිටින්න."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ඔබ ගලවන තෙක් එය දර්ශනය තුළ මෙය තබයි. ගැලවීමට දළ විශ්ලේෂණය ස්පර්ෂ කර අල්ලා සිටින්න."</string> + <string name="screen_pinning_description" msgid="3577937698406151604">"මෙය ඔබ ගලවන තෙක් එය දසුන තුළ තබයි. ගැලවීමට ස්පර්ශ කර අල්ලාගෙන සිටින්න."</string> <string name="screen_pinning_positive" msgid="3783985798366751226">"හරි, තේරුණා"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"එපා ස්තූතියි"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> සඟවන්නද?"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 86c8fe28a7f6..ebfd03594268 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -397,8 +397,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Rozbaliť"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Zbaliť"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Obrazovka je pripnutá"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Obsah bude pripnutý v zobrazení, dokým ho neuvoľníte. Ak ho chcete uvoľniť, stlačte a podržte súčasne tlačidlá Späť a Prehľad."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Obsah bude pripnutý v zobrazení, dokým ho neuvoľníte. Uvoľníte ho stlačením a podržaním tlačidla Prehľad."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Dobre"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Nie, vďaka"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Skryť <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index c5dd188d1cda..d87b9a4784e8 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Razširi"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Strni"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Zaslon je pripet"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"S tem ostane zaslon v pogledu, dokler ga ne odpnete. Hkrati pridržite tipko za nazaj in tipko za pregled, če ga želite odpeti."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"S tem ostane zaslon v pogledu, dokler ga ne odpnete. Pridržite tipko za pregled, če ga želite odpeti."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Razumem"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Ne, hvala"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Želite skriti <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-sq-rAL/strings.xml b/packages/SystemUI/res/values-sq-rAL/strings.xml index fcd65f7269d2..5519e6ba00d8 100644 --- a/packages/SystemUI/res/values-sq-rAL/strings.xml +++ b/packages/SystemUI/res/values-sq-rAL/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Zgjeroje"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Mbylle"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Ekrani u gozhdua"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Kjo e ruan në pamje derisa e heq nga gozhdimi. Prek dhe mbaj shtypur njëkohësisht \"Prapa\" dhe \"Përmbledhje\" për ta hequr nga gozhdimi."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Kjo e ruan në pamje derisa e heq nga gozhdimi. Prek dhe mbaj shtypur njëkohësisht \"Përmbledhje\" për ta hequr nga gozhdimi."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"E kuptova!"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Jo, faleminderit!"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Të fshihet <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 2c55a5c7d81e..67959f5c37f5 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -394,8 +394,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Прошири"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Скупи"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Екран је закачен"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"На овај начин ово остаје приказано док га не откачите. Истовремено додирните и задржите Назад и Преглед да бисте га откачили."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"На овај начин ово остаје приказано док га не откачите. Додирните и задржите Преглед да бисте га откачили."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Важи"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Не, хвала"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Желите ли да сакријете <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index e8b176568b1a..48fe51268d6f 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Utöka"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Komprimera"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Skärmen har fästs"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Med den här funktionen är skärmen synlig tills du lossar den. Tryck länge på Tillbaka och Översikt samtidigt om du vill lossa skärmen."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Detta visar skärmen tills du lossar den. Tryck länge på Översikt om du vill lossa skärmen."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Nej tack"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Vill du dölja <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index bbe271d0fdcc..2b65540fa070 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Panua"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Kunja"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Skrini imebandikwa"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Hii itaendelea kuonyesha hadi ubandue. Gusa na ushikilie Nyuma na Muhtasari kwa wakati mmoja ili ubandue."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Hii itaendelea kuonyesha hadi uibandue. Gusa na ushikilie Muhtasari ili ubandue."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Nimeelewa"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Hapana, asante"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Ungependa kuficha <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-ta-rIN/strings.xml b/packages/SystemUI/res/values-ta-rIN/strings.xml index 08bef13f58a0..1f4a4a20338b 100644 --- a/packages/SystemUI/res/values-ta-rIN/strings.xml +++ b/packages/SystemUI/res/values-ta-rIN/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"விரிவாக்கு"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"சுருக்கு"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"திரை பொருத்தப்பட்டது"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"பொருத்தியதை விலக்கும்வரை இதைக் காட்சியில் வைக்கும். விலக்க, பின் மற்றும் மேலோட்டப் பார்வையை ஒரே நேரத்தில் தொட்டுப் பிடிக்கவும்."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"பொருத்தியதை விலக்கும்வரை இதைக் காட்சியில் வைக்கும். விலக்க, மேலோட்டப் பார்வையைத் தொட்டுப் பிடிக்கவும்."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"புரிந்தது"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"வேண்டாம்"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>ஐ மறைக்கவா?"</string> diff --git a/packages/SystemUI/res/values-te-rIN/strings.xml b/packages/SystemUI/res/values-te-rIN/strings.xml index 46bc7048452f..d0b3439a453b 100644 --- a/packages/SystemUI/res/values-te-rIN/strings.xml +++ b/packages/SystemUI/res/values-te-rIN/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"విస్తరింపజేయండి"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"కుదించండి"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"స్క్రీన్ పిన్ చేయబడింది"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"ఇది మీరు అన్పిన్ చేసే వరకు దీన్ని వీక్షణలో ఉంచుతుంది. అన్పిన్ చేయడానికి వెనుకకు మరియు స్థూలదృష్టిని ఒకేసారి తాకి, ఉంచండి."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"ఇది మీరు అన్పిన్ చేసే వరకు దీన్ని వీక్షణలో ఉంచుతుంది. అన్పిన్ చేయడానికి స్థూలదృష్టిని తాకి, ఉంచండి."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"అర్థమైంది"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"వద్దు, ధన్యవాదాలు"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g>ని దాచాలా?"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index f7e7be002450..cb2921d05bf2 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"ขยาย"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"ยุบ"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"ตรึงหน้าจอแล้ว"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"การดำเนินการนี้จะเปิดหน้าจอนี้ไว้เสมอจนกว่าคุณจะเลิกตรึง แตะ \"กลับ\" และ \"ภาพรวม\" พร้อมกันค้างไว้เพื่อเลิกตรึง"</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"การดำเนินการนี้จะเปิดหน้าจอนี้ไว้เสมอจนกว่าคุณจะเลิกตรึง แตะ \"ภาพรวม\" ค้างไว้เพื่อเลิกตรึง"</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"รับทราบ"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"ไม่เป็นไร ขอบคุณ"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"ซ่อน <xliff:g id="TILE_LABEL">%1$s</xliff:g> ไหม"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 92a6cdc55d15..43969e9f8023 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Palawakin"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"I-collapse"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Naka-pin ang screen"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Pinapanatili nitong nasa view ito hanggang sa mag-unpin ka. Pindutin nang matagal ang Bumalik at Pangkalahatang-ideya nang sabay upang mag-unpin."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Pinapanatili nitong nasa view ito hanggang sa mag-unpin ka. Pindutin nang matagal ang Pangkalahatang-ideya upang mag-unpin."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Nakuha ko"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Hindi, salamat na lang"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Itago ang <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 2b9d6da6afb5..7a044ef67ab5 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Genişlet"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Daralt"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Ekran sabitlendi"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Böylece siz sabitlemesini kaldırana kadar görüntülenmeye devam eder. Sabitlemeyi kaldırmak için Geri ve Genel Bakış öğesine aynı anda dokunun ve basılı tutun."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Böylece siz sabitlemesini kaldırana kadar görüntülenmeye devam eder. Sabitlemeyi kaldırmak için Genel Bakış\'a dokunun ve basılı tutun."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Anladım"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Hayır, teşekkürler"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> gizlensin mi?"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index c040439158f3..37030acab2fd 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Розгорнути"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Згорнути"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Екран закріплено"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Закріпить екран, щоб ви могли постійно його бачити, доки не відкріпите. Щоб відкріпити, одночасно натисніть і втримуйте кнопки \"Назад\" і \"Огляд\"."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Закріпить екран, щоб ви могли постійно його бачити, доки не відкріпите. Щоб відкріпити, натисніть і втримуйте кнопку \"Огляд\"."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Зрозуміло"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Ні, дякую"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Сховати <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-ur-rPK/strings.xml b/packages/SystemUI/res/values-ur-rPK/strings.xml index b0a5336788b8..0712f980804d 100644 --- a/packages/SystemUI/res/values-ur-rPK/strings.xml +++ b/packages/SystemUI/res/values-ur-rPK/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"پھیلائیں"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"سکیڑیں"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"اسکرین پن کردہ ہے"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"یہ اس کو اس وقت تک منظر میں رکھتا ہے جب تک آپ اس سے پن نہیں ہٹا دیتے۔ پن ہٹانے کیلئے واپس اور عمومی جائزہ کو ایک ساتھ ٹچ کریں اور پکڑ کر رکھیں۔"</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"یہ اس کو اس وقت تک منظر میں رکھتا ہے جب تک آپ اس سے پن نہیں ہٹا دیتے۔ پن ہٹانے کیلئے عمومی جائزہ کو ٹچ کریں اور پکڑ کر رکھیں۔"</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"سمجھ آ گئی"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"نہیں شکریہ"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> کو چھپائیں؟"</string> diff --git a/packages/SystemUI/res/values-uz-rUZ/strings.xml b/packages/SystemUI/res/values-uz-rUZ/strings.xml index 9b9e2cdbb830..ad3dcc74c06e 100644 --- a/packages/SystemUI/res/values-uz-rUZ/strings.xml +++ b/packages/SystemUI/res/values-uz-rUZ/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Yoyish"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Yig‘ish"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Ekran qadaldi"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Ekran yechilmaguncha u qadalgan holatda qoladi. Uni yechish uchun “Orqaga” va “Umumiy nazar” tugmalarini bir vaqtda bosing va ushlab turing."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Ekran yechilmaguncha u qadalgan holatda qoladi. Uni yechish uchun “Umumiy nazar” tugmasini bosing va ushlab turing."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"OK"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Yo‘q, kerakmas"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"<xliff:g id="TILE_LABEL">%1$s</xliff:g> berkitilsinmi?"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index d4ed7a5be183..f13033bbb96d 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Mở rộng"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Thu gọn"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Màn hình được ghim"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Thao tác này sẽ duy trì hiển thị màn hình cho đến khi bạn bỏ ghim. Chạm và giữ nút Quay lại và Tổng quan cùng một lúc để bỏ ghim."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Thao tác này sẽ duy trì hiển thị màn hình cho đến khi bạn bỏ ghim. Chạm và giữ nút Quay lại để bỏ ghim."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Ok"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Không, cảm ơn"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Ẩn <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 05416ca04e3a..ade7636edafe 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"展开"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"收起"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"已固定屏幕"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"这将会固定显示此屏幕,直到您取消固定为止。触摸并同时按住“返回”和“概览”即可取消固定屏幕。"</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"这将会固定显示此屏幕,直到您取消固定为止。触摸并按住“概览”即可取消固定屏幕。"</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"知道了"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"不用了"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"要隐藏“<xliff:g id="TILE_LABEL">%1$s</xliff:g>”吗?"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index ba4d3cb5a099..aa6e065ca23d 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"展開"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"收合"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"螢幕已固定"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"在您取消固定前,它會保持在檢視狀態。輕觸並同時按住 [返回] 和 [概覽],即可取消固定。"</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"在您取消固定前,它會保持在檢視狀態。輕觸並按住 [概覽] 即可取消固定。"</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"知道了"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"不用了,謝謝"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"隱藏 <xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 6e0c852b53b5..ffaaefe430c4 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -395,8 +395,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"展開"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"收合"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"螢幕已固定"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"這會讓目前的螢幕畫面保持顯示狀態,直到取消固定為止。同時按住返回按鈕和總覽按鈕即可取消固定。"</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"這會讓目前的螢幕畫面保持顯示狀態,直到取消固定為止。按住總覽按鈕即可取消固定。"</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"知道了"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"不用了,謝謝"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"隱藏<xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 147f6b6848cd..f1df7145fdb7 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -393,8 +393,8 @@ <string name="accessibility_volume_expand" msgid="5946812790999244205">"Nweba"</string> <string name="accessibility_volume_collapse" msgid="3609549593031810875">"Goqa"</string> <string name="screen_pinning_title" msgid="3273740381976175811">"Isikrini siphiniwe"</string> - <string name="screen_pinning_description" msgid="1346522416878235405">"Lokhu kukugcina kubukeka uze ususe ukuphina. Thinta futhi ubambe u-Emuva no-Ukubuka konke ngesikhathi esisodwa ukuze ususe ukuphina."</string> - <string name="screen_pinning_description_accessible" msgid="8518446209564202557">"Lokhu kukugcina kubukeka uze ususe ukuphina. Thinta futhi ubambe u-Ukubuka konke ukuze ususe ukuphina."</string> + <!-- no translation found for screen_pinning_description (3577937698406151604) --> + <skip /> <string name="screen_pinning_positive" msgid="3783985798366751226">"Ngiyitholile"</string> <string name="screen_pinning_negative" msgid="3741602308343880268">"Cha ngiyabonga"</string> <string name="quick_settings_reset_confirmation_title" msgid="748792586749897883">"Fihla i-<xliff:g id="TILE_LABEL">%1$s</xliff:g>?"</string> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index bae801716c19..0c638a2b7164 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -136,7 +136,7 @@ <integer name="touch_acceptance_delay">700</integer> <!-- The duration in seconds to wait before the dismiss buttons are shown. --> - <integer name="recents_task_bar_dismiss_delay_seconds">1</integer> + <integer name="recents_task_bar_dismiss_delay_seconds">1000</integer> <!-- The min animation duration for animating views that are currently visible. --> <integer name="recents_filter_animate_current_views_duration">250</integer> @@ -269,9 +269,6 @@ <!-- Duration of the expansion animation in the volume dialog --> <item name="volume_expand_animation_duration" type="integer">300</item> - <!-- Whether to show a "shelf" of apps at the bottom of the screen. --> - <bool name="config_enableAppShelf">false</bool> - <!-- Whether to show the full screen user switcher. --> <bool name="config_enableFullscreenUserSwitcher">false</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index cbc92f2d985b..a4137b99eb7a 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -128,6 +128,8 @@ <dimen name="qs_new_tile_height">100dp</dimen> <dimen name="qs_quick_actions_height">88dp</dimen> <dimen name="qs_quick_actions_padding">25dp</dimen> + <dimen name="qs_quick_tile_size">48dp</dimen> + <dimen name="qs_quick_tile_padding">12dp</dimen> <dimen name="qs_page_indicator_size">12dp</dimen> <dimen name="qs_tile_icon_size">24dp</dimen> <dimen name="qs_tile_text_size">12sp</dimen> @@ -230,6 +232,9 @@ <!-- The size of the lock-to-app button icon. --> <dimen name="recents_lock_to_app_icon_size">28dp</dimen> + <!-- The amount to allow the stack to overscroll. --> + <dimen name="recents_stack_overscroll">24dp</dimen> + <!-- Space reserved for the cards behind the top card in the top stack --> <dimen name="top_stack_peek_amount">12dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3a5680dc209f..7c86f96ba848 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -999,9 +999,7 @@ <!-- Screen pinning dialog title. --> <string name="screen_pinning_title">Screen is pinned</string> <!-- Screen pinning dialog description. --> - <string name="screen_pinning_description">This keeps it in view until you unpin. Touch and hold Back and Overview at the same time to unpin.</string> - <!-- Screen pinning dialog description when in accessibility mode. --> - <string name="screen_pinning_description_accessible">This keeps it in view until you unpin. Touch and hold Overview to unpin.</string> + <string name="screen_pinning_description">This keeps it in view until you unpin. Touch and hold Back to unpin.</string> <!-- Screen pinning positive response. --> <string name="screen_pinning_positive">Got it</string> <!-- Screen pinning negative response. --> diff --git a/packages/SystemUI/src/com/android/systemui/QSQuickTileView.java b/packages/SystemUI/src/com/android/systemui/QSQuickTileView.java new file mode 100644 index 000000000000..33626505ff79 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/QSQuickTileView.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 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.content.Context; +import android.view.View; +import android.widget.ImageView; +import com.android.systemui.qs.QSTile; +import com.android.systemui.qs.QSTileBaseView; + +public class QSQuickTileView extends QSTileBaseView { + + private final int mPadding; + private final ImageView mIcon; + + public QSQuickTileView(Context context) { + super(context); + mPadding = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding); + mIcon = createIcon(); + addView(mIcon); + } + + protected ImageView createIcon() { + final ImageView icon = new ImageView(mContext); + icon.setId(android.R.id.icon); + icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + return icon; + } + + @Override + public void init(OnClickListener click, OnClickListener clickSecondary, + OnLongClickListener longClick) { + setClickable(true); + setOnClickListener(click); + } + + @Override + protected void handleStateChanged(QSTile.State state) { + mIcon.setImageDrawable(state.icon.getDrawable(getContext())); + setContentDescription(state.contentDescription); + } + + @Override + public boolean setType(int type) { + return false; + } + + @Override + public View updateAccessibilityOrder(View previousView) { + return this; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + mIcon.measure(exactly(getMeasuredWidth() - 2 * mPadding), + exactly(getMeasuredHeight() - 2 * mPadding)); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + layout(mIcon, mPadding, mPadding); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java index 1d0bfe7dc6f1..9a4cd9365283 100644 --- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java @@ -27,4 +27,9 @@ public interface RecentsComponent { void cancelPreloadingRecents(); void showNextAffiliatedTask(); void showPrevAffiliatedTask(); + + /** + * Docks the top-most task and opens recents. + */ + void dockTopTask(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index c6bcfc4b1a85..794e9005fa31 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -566,7 +566,7 @@ public class QSPanel extends FrameLayout implements Tunable { public static final class TileRecord extends Record { public QSTile<?> tile; - public QSTileView tileView; + public QSTileBaseView tileView; public int row; public int col; public boolean scanState; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java index 61cb224f5df2..5d928d6c3ade 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java @@ -98,7 +98,7 @@ public abstract class QSTile<TState extends State> implements Listenable { return mHost; } - public QSTileView createTileView(Context context) { + public QSTileBaseView createTileView(Context context) { return new QSTileView(context); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java new file mode 100644 index 000000000000..72fc88de11ea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileBaseView.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2015 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.qs; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +public abstract class QSTileBaseView extends ViewGroup { + + public static final int QS_TYPE_NORMAL = 0; + public static final int QS_TYPE_DUAL = 1; + public static final int QS_TYPE_QUICK = 2; + + private final H mHandler = new H(); + + public QSTileBaseView(Context context) { + super(context); + } + + public QSTileBaseView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + + public void onStateChanged(QSTile.State state) { + mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget(); + } + + public abstract void init(OnClickListener click, OnClickListener clickSecondary, + OnLongClickListener longClick); + public abstract View updateAccessibilityOrder(View previousView); + public abstract boolean setType(int type); + + protected abstract void handleStateChanged(QSTile.State state); + + protected static int exactly(int size) { + return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); + } + + protected static void layout(View child, int left, int top) { + child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight()); + } + + private class H extends Handler { + private static final int STATE_CHANGED = 1; + public H() { + super(Looper.getMainLooper()); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == STATE_CHANGED) { + handleStateChanged((QSTile.State) msg.obj); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java index d914beb4e3de..cc264a00776d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java @@ -44,18 +44,13 @@ import com.android.systemui.qs.QSTile.State; import java.util.Objects; /** View that represents a standard quick settings tile. **/ -public class QSTileView extends ViewGroup { +public class QSTileView extends QSTileBaseView { private static final Typeface CONDENSED = Typeface.create("sans-serif-condensed", Typeface.NORMAL); - public static final int QS_TYPE_NORMAL = 0; - public static final int QS_TYPE_DUAL = 1; - public static final int QS_TYPE_QUICK = 2; - protected final Context mContext; private final View mIcon; private final View mDivider; - private final H mHandler = new H(); private final int mIconSizePx; private final int mTileSpacingPx; private int mTilePaddingTopPx; @@ -291,10 +286,6 @@ public class QSTileView extends ViewGroup { return MeasureSpec.EXACTLY; } - private static int exactly(int size) { - return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); - } - @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int w = getMeasuredWidth(); @@ -334,10 +325,6 @@ public class QSTileView extends ViewGroup { mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad); } - private static void layout(View child, int left, int top) { - child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight()); - } - protected void handleStateChanged(QSTile.State state) { if (mIcon instanceof ImageView) { setIcon((ImageView) mIcon, state); @@ -369,10 +356,6 @@ public class QSTileView extends ViewGroup { } } - public void onStateChanged(QSTile.State state) { - mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget(); - } - /** * Update the accessibility order for this view. * @@ -392,17 +375,4 @@ public class QSTileView extends ViewGroup { firstView.setAccessibilityTraversalAfter(previousView.getId()); return lastView; } - - private class H extends Handler { - private static final int STATE_CHANGED = 1; - public H() { - super(Looper.getMainLooper()); - } - @Override - public void handleMessage(Message msg) { - if (msg.what == STATE_CHANGED) { - handleStateChanged((State) msg.obj); - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index a2d9ef020985..6a053bee36cd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -19,6 +19,8 @@ package com.android.systemui.qs; import android.content.Context; import android.content.res.ColorStateList; import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; @@ -58,6 +60,7 @@ public class QuickQSPanel extends QSPanel { ArrayList<QSTile<?>> quickTiles = new ArrayList<>(); for (QSTile<?> tile : tiles) { if (tile.getTileType() == QSTileView.QS_TYPE_QUICK) { + Log.d("QSPanel", "Adding " + tile.getTileSpec()); quickTiles.add(tile); } if (quickTiles.size() == 2) { @@ -71,16 +74,17 @@ public class QuickQSPanel extends QSPanel { public HeaderTileLayout(Context context) { super(context); + setGravity(Gravity.CENTER_VERTICAL); setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - int qsCompensation = (int) - context.getResources().getDimension(R.dimen.qs_header_neg_padding); - setPadding(0, qsCompensation, 0, 0); + + int padding = + mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding); ImageView downArrow = new ImageView(context); downArrow.setImageResource(R.drawable.ic_expand_more); downArrow.setImageTintList(ColorStateList.valueOf(context.getResources().getColor( android.R.color.white, null))); downArrow.setLayoutParams(generateLayoutParams()); - downArrow.setPadding(0, -qsCompensation, 0, 0); + downArrow.setPadding(padding, padding, padding, padding); addView(downArrow); setOrientation(LinearLayout.HORIZONTAL); } @@ -95,7 +99,9 @@ public class QuickQSPanel extends QSPanel { } private LayoutParams generateLayoutParams() { - LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT); + int size = + mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size); + LayoutParams lp = new LayoutParams(0, size); lp.weight = 1; return lp; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index f3ad9d803394..e2d2ffbebf93 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -27,13 +27,12 @@ import android.view.ViewGroup; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.qs.QSTile; -import com.android.systemui.qs.QSTileView; +import com.android.systemui.qs.QSTileBaseView; import com.android.systemui.qs.SignalTileView; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.IconState; import com.android.systemui.statusbar.policy.NetworkController.MobileDataController; import com.android.systemui.statusbar.policy.NetworkController.MobileDataController.DataUsageInfo; -import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; import com.android.systemui.statusbar.policy.SignalCallbackAdapter; /** Quick settings tile: Cellular **/ @@ -74,7 +73,7 @@ public class CellularTile extends QSTile<QSTile.SignalState> { } @Override - public QSTileView createTileView(Context context) { + public QSTileBaseView createTileView(Context context) { return new SignalTileView(context); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QAirplaneTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QAirplaneTile.java index 13ccaaade1cf..b77191eeb83c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QAirplaneTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QAirplaneTile.java @@ -16,9 +16,12 @@ package com.android.systemui.qs.tiles; +import android.content.Context; +import com.android.systemui.QSQuickTileView; +import com.android.systemui.qs.QSTileBaseView; import com.android.systemui.qs.QSTileView; -/** Quick settings tile: Wifi **/ +/** Quick settings tile: Airplane mode **/ public class QAirplaneTile extends AirplaneModeTile { public QAirplaneTile(Host host) { @@ -26,6 +29,11 @@ public class QAirplaneTile extends AirplaneModeTile { } @Override + public QSTileBaseView createTileView(Context context) { + return new QSQuickTileView(context); + } + + @Override public int getTileType() { return QSTileView.QS_TYPE_QUICK; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QBluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QBluetoothTile.java index 02975cbace07..4fe7e45846a3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QBluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QBluetoothTile.java @@ -16,6 +16,9 @@ package com.android.systemui.qs.tiles; +import android.content.Context; +import com.android.systemui.QSQuickTileView; +import com.android.systemui.qs.QSTileBaseView; import com.android.systemui.qs.QSTileView; /** Quick settings tile: Bluetooth **/ @@ -26,6 +29,11 @@ public class QBluetoothTile extends BluetoothTile { } @Override + public QSTileBaseView createTileView(Context context) { + return new QSQuickTileView(context); + } + + @Override public int getTileType() { return QSTileView.QS_TYPE_QUICK; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QFlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QFlashlightTile.java index 31035cdb5b25..e115755e98ed 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QFlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QFlashlightTile.java @@ -16,9 +16,12 @@ package com.android.systemui.qs.tiles; +import android.content.Context; +import com.android.systemui.QSQuickTileView; +import com.android.systemui.qs.QSTileBaseView; import com.android.systemui.qs.QSTileView; -/** Quick settings tile: Wifi **/ +/** Quick settings tile: Flashlight **/ public class QFlashlightTile extends FlashlightTile { public QFlashlightTile(Host host) { @@ -26,6 +29,11 @@ public class QFlashlightTile extends FlashlightTile { } @Override + public QSTileBaseView createTileView(Context context) { + return new QSQuickTileView(context); + } + + @Override public int getTileType() { return QSTileView.QS_TYPE_QUICK; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java index 3675f02b9dee..8b3013a96f9c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QLockTile.java @@ -15,11 +15,14 @@ */ package com.android.systemui.qs.tiles; +import android.content.Context; import com.android.internal.logging.MetricsLogger; +import com.android.systemui.QSQuickTileView; +import com.android.systemui.R; import com.android.systemui.qs.QSTile; +import com.android.systemui.qs.QSTileBaseView; import com.android.systemui.qs.QSTileView; import com.android.systemui.statusbar.policy.KeyguardMonitor; -import com.android.systemui.R; public class QLockTile extends QSTile<QSTile.State> implements KeyguardMonitor.Callback { @@ -31,6 +34,11 @@ public class QLockTile extends QSTile<QSTile.State> implements KeyguardMonitor.C } @Override + public QSTileBaseView createTileView(Context context) { + return new QSQuickTileView(context); + } + + @Override public int getTileType() { return QSTileView.QS_TYPE_QUICK; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRotationLockTile.java index e066bab04b32..5f5cab503d4a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRotationLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRotationLockTile.java @@ -16,6 +16,9 @@ package com.android.systemui.qs.tiles; +import android.content.Context; +import com.android.systemui.QSQuickTileView; +import com.android.systemui.qs.QSTileBaseView; import com.android.systemui.qs.QSTileView; /** Quick settings tile: Rotation **/ @@ -26,6 +29,11 @@ public class QRotationLockTile extends RotationLockTile { } @Override + public QSTileBaseView createTileView(Context context) { + return new QSQuickTileView(context); + } + + @Override public int getTileType() { return QSTileView.QS_TYPE_QUICK; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QWifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QWifiTile.java index b1572754fb6b..f0fe87d2261f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QWifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QWifiTile.java @@ -16,6 +16,9 @@ package com.android.systemui.qs.tiles; +import android.content.Context; +import com.android.systemui.QSQuickTileView; +import com.android.systemui.qs.QSTileBaseView; import com.android.systemui.qs.QSTileView; import com.android.systemui.statusbar.policy.WifiIcons; @@ -27,6 +30,11 @@ public class QWifiTile extends WifiTile { } @Override + public QSTileBaseView createTileView(Context context) { + return new QSQuickTileView(context); + } + + @Override public int getTileType() { return QSTileView.QS_TYPE_QUICK; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index 91e02183818f..3763618bae21 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -30,6 +30,7 @@ import com.android.systemui.R; import com.android.systemui.qs.QSDetailItems; import com.android.systemui.qs.QSDetailItems.Item; import com.android.systemui.qs.QSTile; +import com.android.systemui.qs.QSTileBaseView; import com.android.systemui.qs.QSTileView; import com.android.systemui.qs.SignalTileView; import com.android.systemui.statusbar.policy.NetworkController; @@ -94,7 +95,7 @@ public class WifiTile extends QSTile<QSTile.SignalState> { } @Override - public QSTileView createTileView(Context context) { + public QSTileBaseView createTileView(Context context) { return new SignalTileView(context); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index cdb6b932de9f..ebfacac1b1fb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -37,6 +37,8 @@ public class Constants { public static final boolean EnableTaskFiltering = false; // Enables dismiss-all public static final boolean EnableDismissAll = false; + // Enables fast-toggling + public static final boolean EnableFastToggleRecents = false; // Enables the thumbnail alpha on the front-most task public static final boolean EnableThumbnailAlphaOnFrontmost = false; // This disables the search bar integration @@ -62,8 +64,6 @@ public class Constants { } public static class TaskStackView { - public static final int TaskStackMinOverscrollRange = 32; - public static final int TaskStackMaxOverscrollRange = 128; public static final int FilterStartDelay = 25; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index c216f9788d87..4d40cb7072f7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -54,6 +54,7 @@ public class Recents extends SystemUI private static SystemServicesProxy sSystemServicesProxy; private static RecentsTaskLoader sTaskLoader; + private static RecentsConfiguration sConfiguration; private Handler mHandler; private RecentsImpl mImpl; @@ -129,10 +130,15 @@ public class Recents extends SystemUI return sSystemServicesProxy; } + public static RecentsConfiguration getConfiguration() { + return sConfiguration; + } + @Override public void start() { sSystemServicesProxy = new SystemServicesProxy(mContext); sTaskLoader = new RecentsTaskLoader(mContext); + sConfiguration = new RecentsConfiguration(mContext); mHandler = new Handler(); mImpl = new RecentsImpl(mContext); @@ -284,6 +290,11 @@ public class Recents extends SystemUI } @Override + public void dockTopTask() { + mImpl.dockTopTask(); + } + + @Override public void showNextAffiliatedTask() { mImpl.showNextAffiliatedTask(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index c416967599f6..6c8bf0f96bff 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -32,6 +32,7 @@ import android.os.Bundle; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.ViewStub; @@ -41,16 +42,21 @@ import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent; import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent; import com.android.systemui.recents.events.activity.HideRecentsEvent; +import com.android.systemui.recents.events.activity.IterateRecentsEvent; import com.android.systemui.recents.events.activity.ToggleRecentsEvent; import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; -import com.android.systemui.recents.events.ui.DismissTaskEvent; +import com.android.systemui.recents.events.ui.DismissTaskViewEvent; import com.android.systemui.recents.events.ui.ResizeTaskEvent; import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; import com.android.systemui.recents.events.ui.UserInteractionEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; +import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; +import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent; +import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; import com.android.systemui.recents.misc.Console; +import com.android.systemui.recents.misc.DozeTrigger; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.model.RecentsPackageMonitor; @@ -69,9 +75,11 @@ import java.util.ArrayList; */ public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks { + private final static String TAG = "RecentsActivity"; + private final static boolean DEBUG = false; + public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1; - RecentsConfiguration mConfig; RecentsPackageMonitor mPackageMonitor; long mLastTabKeyEventTime; boolean mFinishedOnStartup; @@ -96,6 +104,14 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // Runnable to be executed after we paused ourselves Runnable mAfterPauseRunnable; + // The trigger to automatically launch the current task + DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() { + @Override + public void run() { + boolean dismissed = dismissRecentsToFocusedTask(false); + } + }); + /** * A common Runnable to finish Recents either by calling finish() (with a custom animation) or * launching Home with some ActivityOptions. Generally we always launch home when we exit @@ -157,7 +173,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } // Start loading tasks according to the load plan - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); if (!plan.hasTasks()) { loader.preloadTasks(plan, launchState.launchedFromHome); } @@ -244,9 +261,27 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView MetricsLogger.histogram(this, "overview_task_count", taskCount); } - /** Dismisses recents if we are already visible and the intent is to toggle the recents view */ + /** + * Dismisses recents if we are already visible and the intent is to toggle the recents view. + */ + boolean dismissRecentsToFocusedTask(boolean checkFilteredStackState) { + SystemServicesProxy ssp = Recents.getSystemServices(); + if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) { + // If we currently have filtered stacks, then unfilter those first + if (checkFilteredStackState && + mRecentsView.unfilterFilteredStacks()) return true; + // If we have a focused Task, launch that Task now + if (mRecentsView.launchFocusedTask()) return true; + } + return false; + } + + /** + * Dismisses recents if we are already visible and the intent is to toggle the recents view. + */ boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) { - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); SystemServicesProxy ssp = Recents.getSystemServices(); if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) { // If we currently have filtered stacks, then unfilter those first @@ -318,7 +353,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView EventBus.getDefault().register(this, EVENT_BUS_PRIORITY); // Initialize the widget host (the host id is static and does not change) - mConfig = RecentsConfiguration.getInstance(); if (!Constants.DebugFlags.App.DisableSearchBar) { mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId); } @@ -365,7 +399,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // If this is a new instance from a configuration change, then we have to manually trigger // the enter animation state, or if recents was relaunched by AM, without going through // the normal mechanisms - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); boolean wasLaunchedByAm = !launchState.launchedFromHome && !launchState.launchedFromAppWithThumbnail; if (launchState.launchedHasConfigurationChanged || wasLaunchedByAm) { @@ -390,6 +425,11 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView mRecentsView.post(mAfterPauseRunnable); mAfterPauseRunnable = null; } + + if (Constants.DebugFlags.App.EnableFastToggleRecents) { + // Stop the fast-toggle dozer + mIterateTrigger.stopDozing(); + } } @Override @@ -403,7 +443,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // Workaround for b/22542869, if the RecentsActivity is started again, but without going // through SystemUI, we need to reset the config launch flags to ensure that we do not // wait on the system to send a signal that was never queued. - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); launchState.launchedFromHome = false; launchState.launchedFromSearchHome = false; launchState.launchedFromAppWithThumbnail = false; @@ -467,22 +508,27 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) { // Focus the next task in the stack final boolean backward = event.isShiftPressed(); - mRecentsView.focusNextTask(!backward); + if (backward) { + EventBus.getDefault().send(new FocusPreviousTaskViewEvent()); + } else { + EventBus.getDefault().send(new FocusNextTaskViewEvent()); + } mLastTabKeyEventTime = SystemClock.elapsedRealtime(); } return true; } case KeyEvent.KEYCODE_DPAD_UP: { - mRecentsView.focusNextTask(true); + EventBus.getDefault().send(new FocusNextTaskViewEvent()); return true; } case KeyEvent.KEYCODE_DPAD_DOWN: { - mRecentsView.focusNextTask(false); + EventBus.getDefault().send(new FocusPreviousTaskViewEvent()); return true; } case KeyEvent.KEYCODE_DEL: case KeyEvent.KEYCODE_FORWARD_DEL: { - mRecentsView.dismissFocusedTask(); + EventBus.getDefault().send(new DismissFocusedTaskViewEvent()); + // Keep track of deletions by keyboard MetricsLogger.histogram(this, "overview_task_dismissed_source", Constants.Metrics.DismissSourceKeyboard); @@ -542,6 +588,16 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView dismissRecentsToFocusedTaskOrHome(true /* checkFilteredStackState */); } + public final void onBusEvent(IterateRecentsEvent event) { + // Focus the next task + EventBus.getDefault().send(new FocusNextTaskViewEvent()); + mIterateTrigger.poke(); + } + + public final void onBusEvent(UserInteractionEvent event) { + mIterateTrigger.stopDozing(); + } + public final void onBusEvent(HideRecentsEvent event) { if (event.triggeredFromAltTab) { // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app @@ -558,7 +614,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // Try and start the enter animation (or restart it on configuration changed) ReferenceCountedTrigger t = new ReferenceCountedTrigger(this, null, null, null); ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t); - mRecentsView.startEnterRecentsAnimation(ctx); + ctx.postAnimationTrigger.increment(); if (mSearchWidgetInfo != null) { ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { @Override @@ -570,6 +626,21 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } }); } + ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { + @Override + public void run() { + // If we are not launching with alt-tab and fast-toggle is enabled, then start + // the dozer now + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); + if (Constants.DebugFlags.App.EnableFastToggleRecents && + !launchState.launchedWithAltTab) { + mIterateTrigger.startDozing(); + } + } + }); + mRecentsView.startEnterRecentsAnimation(ctx); + ctx.postAnimationTrigger.decrement(); } public final void onBusEvent(AppWidgetProviderChangedEvent event) { @@ -589,7 +660,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView MetricsLogger.count(this, "overview_app_info", 1); } - public final void onBusEvent(DismissTaskEvent event) { + public final void onBusEvent(DismissTaskViewEvent event) { // Remove any stored data from the loader RecentsTaskLoader loader = Recents.getTaskLoader(); loader.deleteTaskData(event.task, false); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java index e2e0e918ab51..aca816e7f068 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java @@ -28,8 +28,6 @@ package com.android.systemui.recents; */ public class RecentsActivityLaunchState { - public RecentsConfiguration mConfig; - public boolean launchedWithAltTab; public boolean launchedWithNoRecentTasks; public boolean launchedFromAppWithThumbnail; @@ -41,10 +39,6 @@ public class RecentsActivityLaunchState { public int launchedNumVisibleTasks; public int launchedNumVisibleThumbnails; - RecentsActivityLaunchState(RecentsConfiguration config) { - mConfig = config; - } - /** Called when the configuration has changed, and we want to reset any configuration specific * members. */ public void updateOnConfigurationChange() { @@ -56,7 +50,7 @@ public class RecentsActivityLaunchState { /** Returns whether the status bar scrim should be animated when shown for the first time. */ public boolean shouldAnimateStatusBarScrim() { - return launchedFromHome; + return true; } /** Returns whether the status bar scrim should be visible. */ @@ -72,6 +66,7 @@ public class RecentsActivityLaunchState { /** Returns whether the nav bar scrim should be visible. */ public boolean hasNavBarScrim() { // Only show the scrim if we have recent tasks, and if the nav bar is not transposed - return !launchedWithNoRecentTasks && mConfig.hasTransposedNavBar; + RecentsConfiguration config = Recents.getConfiguration(); + return !launchedWithNoRecentTasks && !config.hasTransposedNavBar; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index d8f40238eaf4..e2f20fdefbb3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -28,7 +28,6 @@ import com.android.systemui.recents.misc.SystemServicesProxy; * tied to the current activity. */ public class RecentsConfiguration { - static RecentsConfiguration sInstance; private static final int LARGE_SCREEN_MIN_DP = 600; private static final int XLARGE_SCREEN_MIN_DP = 720; @@ -53,7 +52,7 @@ public class RecentsConfiguration { public static final int SVELTE_DISABLE_LOADING = 3; // Launch states - public RecentsActivityLaunchState mLaunchState = new RecentsActivityLaunchState(this); + public RecentsActivityLaunchState mLaunchState = new RecentsActivityLaunchState(); // TODO: Values determined by the current context, needs to be refactored into something that is // agnostic of the activity context, but still calculable from the Recents component for @@ -79,10 +78,10 @@ public class RecentsConfiguration { /** Dev options and global settings */ public boolean lockToAppEnabled; - /** Private constructor */ - private RecentsConfiguration(Context context, SystemServicesProxy ssp) { + public RecentsConfiguration(Context context) { // Load only resources that can not change after the first load either through developer // settings or via multi window + SystemServicesProxy ssp = Recents.getSystemServices(); Context appContext = context.getApplicationContext(); Resources res = appContext.getResources(); useHardwareLayers = res.getBoolean(R.bool.config_recents_use_hardware_layers); @@ -121,19 +120,6 @@ public class RecentsConfiguration { hasTransposedSearchBar = isLandscape && isLargeScreen && !isXLargeScreen; } - /** Updates the configuration to the current context */ - public static RecentsConfiguration initialize(Context context, SystemServicesProxy ssp) { - if (sInstance == null) { - sInstance = new RecentsConfiguration(context, ssp); - } - return sInstance; - } - - /** Returns the current recents configuration */ - public static RecentsConfiguration getInstance() { - return sInstance; - } - /** * Returns the activity launch state. * TODO: This will be refactored out of RecentsConfiguration. diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 07c7897242a4..d02e2affaaa7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -16,6 +16,8 @@ package com.android.systemui.recents; +import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID; + import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ITaskStackListener; @@ -31,8 +33,10 @@ import android.os.Handler; import android.os.SystemClock; import android.os.UserHandle; import android.util.MutableBoolean; +import android.view.AppTransitionAnimationSpec; import android.view.LayoutInflater; import android.view.View; + import com.android.internal.logging.MetricsLogger; import com.android.systemui.Prefs; import com.android.systemui.R; @@ -40,6 +44,7 @@ import com.android.systemui.SystemUIApplication; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationStartedEvent; import com.android.systemui.recents.events.activity.HideRecentsEvent; +import com.android.systemui.recents.events.activity.IterateRecentsEvent; import com.android.systemui.recents.events.activity.ToggleRecentsEvent; import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; @@ -134,7 +139,6 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub boolean mCanReuseTaskStackViews = true; // Task launching - RecentsConfiguration mConfig; Rect mSearchBarBounds = new Rect(); Rect mTaskStackBounds = new Rect(); Rect mLastTaskViewBounds = new Rect(); @@ -173,7 +177,6 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub ssp.registerTaskStackListener(mTaskStackListener); // Initialize the static configuration resources - mConfig = RecentsConfiguration.initialize(mContext, ssp); mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height); mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width); @@ -204,7 +207,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub public void onConfigurationChanged() { // Don't reuse task stack views if the configuration changes mCanReuseTaskStackViews = false; - mConfig.updateOnConfigurationChange(); + Recents.getConfiguration().updateOnConfigurationChange(); } /** @@ -265,26 +268,38 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub mTriggeredFromAltTab = false; try { - // If the user has toggled it too quickly, then just eat up the event here (it's better - // than showing a janky screenshot). - // NOTE: Ideally, the screenshot mechanism would take the window transform into account - if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) { - return; - } - - // If Recents is the front most activity, then we should just communicate with it - // directly to launch the first task or dismiss itself SystemServicesProxy ssp = Recents.getSystemServices(); ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); MutableBoolean isTopTaskHome = new MutableBoolean(true); if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) { - // Notify recents to toggle itself - EventBus.getDefault().post(new ToggleRecentsEvent()); - mLastToggleTime = SystemClock.elapsedRealtime(); + if (Constants.DebugFlags.App.EnableFastToggleRecents) { + // Notify recents to move onto the next task + EventBus.getDefault().post(new IterateRecentsEvent()); + } else { + // If the user has toggled it too quickly, then just eat up the event here (it's + // better than showing a janky screenshot). + // NOTE: Ideally, the screenshot mechanism would take the window transform into + // account + if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) { + return; + } + + EventBus.getDefault().post(new ToggleRecentsEvent()); + mLastToggleTime = SystemClock.elapsedRealtime(); + } return; } else { + // If the user has toggled it too quickly, then just eat up the event here (it's + // better than showing a janky screenshot). + // NOTE: Ideally, the screenshot mechanism would take the window transform into + // account + if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) { + return; + } + // Otherwise, start the recents activity startRecentsActivity(topTask, isTopTaskHome.value); + mLastToggleTime = SystemClock.elapsedRealtime(); } } catch (ActivityNotFoundException e) { Console.logRawError("Failed to launch RecentAppsIntent", e); @@ -333,7 +348,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub // Return early if there is no running task (can't determine affiliated tasks in this case) if (runningTask == null) return; // Return early if the running task is in the home stack (optimization) - if (ssp.isInHomeStack(runningTask.id)) return; + if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return; // Find the task in the recents list ArrayList<Task> tasks = focusedStack.getTasks(); @@ -405,6 +420,16 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub showRelativeAffiliatedTask(false); } + public void dockTopTask() { + SystemServicesProxy ssp = Recents.getSystemServices(); + ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask(); + if (topTask != null && !SystemServicesProxy.isHomeStack(topTask.stackId)) { + ssp.startTaskInDockedMode(topTask.id, + ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT); + showRecents(false /* triggeredFromAltTab */); + } + } + /** * Returns the preloaded load plan and invalidates it. */ @@ -422,31 +447,32 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub * is not already bound (can be expensive) */ private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) { + RecentsConfiguration config = Recents.getConfiguration(); SystemServicesProxy ssp = Recents.getSystemServices(); Rect windowRect = ssp.getWindowRect(); // Update the configuration for the current state - mConfig.update(mContext, ssp, ssp.getWindowRect()); + config.update(mContext, ssp, ssp.getWindowRect()); if (!Constants.DebugFlags.App.DisableSearchBar && tryAndBindSearchWidget) { // Try and pre-emptively bind the search widget on startup to ensure that we // have the right thumbnail bounds to animate to. // Note: We have to reload the widget id before we get the task stack bounds below if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) { - mConfig.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds); + config.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds); } } Rect systemInsets = new Rect(0, mStatusBarHeight, - (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), - (mConfig.hasTransposedNavBar ? 0 : mNavBarHeight)); - mConfig.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right, + (config.hasTransposedNavBar ? mNavBarWidth : 0), + (config.hasTransposedNavBar ? 0 : mNavBarHeight)); + config.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right, mSearchBarBounds, mTaskStackBounds); // Rebind the header bar and draw it for the transition TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm(); Rect taskStackBounds = new Rect(mTaskStackBounds); algo.setSystemInsets(systemInsets); - algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds); + algo.computeRects(taskStackBounds); Rect taskViewBounds = algo.getUntransformedTaskViewBounds(); if (!taskViewBounds.equals(mLastTaskViewBounds)) { mLastTaskViewBounds.set(taskViewBounds); @@ -535,12 +561,44 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub */ private ActivityOptions getThumbnailTransitionActivityOptions( ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) { + if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) { + ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>(); + stackView.getScroller().setStackScrollToInitialState(); + ArrayList<Task> tasks = stack.getTasks(); + for (int i = tasks.size() - 1; i >= 0; i--) { + Task task = tasks.get(i); + if (SystemServicesProxy.isFreeformStack(task.key.stackId)) { + mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task, + stackView.getScroller().getStackScroll(), mTmpTransform, null); + Rect toTaskRect = new Rect(); + mTmpTransform.rect.round(toTaskRect); + Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform); + specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect)); + } + } + AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()]; + specs.toArray(specsArray); + return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, + specsArray, mHandler, this); + } else { + // Update the destination rect + Task toTask = new Task(); + TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, + topTask.id, toTask); + RectF toTaskRect = toTransform.rect; + Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform); + if (thumbnail != null) { + return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, + thumbnail, (int) toTaskRect.left, (int) toTaskRect.top, + (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this); + } + // If both the screenshot and thumbnail fails, then just fall back to the default transition + return getUnknownTransitionActivityOptions(); + } + } - // Update the destination rect - Task toTask = new Task(); - TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, - topTask.id, toTask); - RectF toTaskRect = toTransform.rect; + private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask, + TaskViewTransform toTransform) { Bitmap thumbnail; if (mThumbnailTransitionBitmapCacheKey != null && mThumbnailTransitionBitmapCacheKey.key != null @@ -552,14 +610,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub preloadIcon(topTask); thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform); } - if (thumbnail != null) { - return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, - thumbnail, (int) toTaskRect.left, (int) toTaskRect.top, - (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, this); - } - - // If both the screenshot and thumbnail fails, then just fall back to the default transition - return getUnknownTransitionActivityOptions(); + return thumbnail; } /** @@ -695,7 +746,8 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub mStartAnimationTriggered = false; // Update the configuration based on the launch options - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); launchState.launchedFromHome = fromSearchHome || fromHome; launchState.launchedFromSearchHome = fromSearchHome; launchState.launchedFromAppWithThumbnail = fromThumbnail; diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java index 31ee8ad7dd7d..28299d3217fb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsResizeTaskDialog.java @@ -234,14 +234,6 @@ public class RecentsResizeTaskDialog extends DialogFragment { dismissAllowingStateLoss(); mRecentsActivity.dismissRecentsToHomeWithoutTransitionAnimation(); - // In debug mode, we force all task to be resizeable regardless of the - // current app configuration. - for (int i = additionalTasks; i >= 0; --i) { - if (mTasks[i] != null) { - ssp.setTaskResizeable(mTasks[i].key.id); - } - } - // Show tasks as they might not be currently visible - beginning with the oldest so that // the focus ends on the selected one. for (int i = additionalTasks; i >= 0; --i) { @@ -277,8 +269,7 @@ public class RecentsResizeTaskDialog extends DialogFragment { if (mTasks[0].key.stackId != DOCKED_STACK_ID) { int taskId = mTasks[0].key.id; SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.setTaskResizeable(taskId); - ssp.dockTask(taskId, createMode); + ssp.startTaskInDockedMode(taskId, createMode); mRecentsView.launchTask(mTasks[0], null, DOCKED_STACK_ID); } else { Toast.makeText(getContext(), "Already docked", Toast.LENGTH_SHORT); diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 231843ea2690..10075bc6861e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -214,11 +214,8 @@ public class ScreenPinningRequest implements View.OnClickListener { .setVisibility(View.INVISIBLE); } - final int description = mAccessibilityService.isEnabled() - ? R.string.screen_pinning_description_accessible - : R.string.screen_pinning_description; ((TextView) mLayout.findViewById(R.id.screen_pinning_description)) - .setText(description); + .setText(R.string.screen_pinning_description); final int backBgVisibility = mAccessibilityService.isEnabled() ? View.INVISIBLE : View.VISIBLE; mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVisibility); diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java index fec0fc57a766..deae4c8d5ff9 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java @@ -200,7 +200,8 @@ class EventHandlerMethod { */ public class EventBus extends BroadcastReceiver { - public static final String TAG = "EventBus"; + private static final String TAG = "EventBus"; + private static final boolean DEBUG_TRACE_ALL = false; /** * An event super class that allows us to track internal event state across subscriber @@ -277,9 +278,6 @@ public class EventBus extends BroadcastReceiver { // The default priority of all subscribers private static final int DEFAULT_SUBSCRIBER_PRIORITY = 1; - // Used for debugging everything - private static final boolean DEBUG_TRACE_ALL = false; - // Orders the handlers by priority and registration time private static final Comparator<EventHandler> EVENT_HANDLER_COMPARATOR = new Comparator<EventHandler>() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java new file mode 100644 index 000000000000..f7b2706b9c57 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 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.recents.events.activity; + +import com.android.systemui.recents.events.EventBus; + +/** + * This is sent when the user taps on the Overview button to iterate to the next item in the + * Recents list. + */ +public class IterateRecentsEvent extends EventBus.Event { + // Simple event +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java index 12e5d3d61cf7..968890aea2f1 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DismissTaskViewEvent.java @@ -23,12 +23,12 @@ import com.android.systemui.recents.views.TaskView; /** * This is sent when a {@link TaskView} has been dismissed. */ -public class DismissTaskEvent extends EventBus.Event { +public class DismissTaskViewEvent extends EventBus.Event { public final Task task; public final TaskView taskView; - public DismissTaskEvent(Task task, TaskView taskView) { + public DismissTaskViewEvent(Task task, TaskView taskView) { this.task = task; this.taskView = taskView; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java new file mode 100644 index 000000000000..9f3e9d5e3362 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/DismissFocusedTaskViewEvent.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 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.recents.events.ui.focus; + +import com.android.systemui.recents.events.EventBus; + +/** + * Dismisses the currently focused task view. + */ +public class DismissFocusedTaskViewEvent extends EventBus.Event { + // Simple event +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java new file mode 100644 index 000000000000..171ab5e8bcca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 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.recents.events.ui.focus; + +import com.android.systemui.recents.events.EventBus; + +/** + * Focuses the next task view in the stack. + */ +public class FocusNextTaskViewEvent extends EventBus.Event { + // Simple event +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java new file mode 100644 index 000000000000..22469e758e70 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusPreviousTaskViewEvent.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 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.recents.events.ui.focus; + +import com.android.systemui.recents.events.EventBus; + +/** + * Focuses the previous task view in the stack. + */ +public class FocusPreviousTaskViewEvent extends EventBus.Event { + // Simple event +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java index 735f79f4021d..336d2db443f9 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java @@ -19,8 +19,8 @@ package com.android.systemui.recents.misc; import android.os.Handler; /** - * A dozer is a class that fires a trigger after it falls asleep. You can occasionally poke it to - * wake it up, but it will fall asleep if left untouched. + * A dozer is a class that fires a trigger after it falls asleep. + * You can occasionally poke the trigger to wake it up, but it will fall asleep if left untouched. */ public class DozeTrigger { @@ -28,7 +28,7 @@ public class DozeTrigger { boolean mIsDozing; boolean mHasTriggered; - int mDozeDurationSeconds; + int mDozeDurationMilliseconds; Runnable mSleepRunnable; // Sleep-runnable @@ -41,9 +41,9 @@ public class DozeTrigger { } }; - public DozeTrigger(int dozeDurationSeconds, Runnable sleepRunnable) { + public DozeTrigger(int dozeDurationMilliseconds, Runnable sleepRunnable) { mHandler = new Handler(); - mDozeDurationSeconds = dozeDurationSeconds; + mDozeDurationMilliseconds = dozeDurationMilliseconds; mSleepRunnable = sleepRunnable; } @@ -69,7 +69,7 @@ public class DozeTrigger { /** Poke this dozer to wake it up for a little bit. */ void forcePoke() { mHandler.removeCallbacks(mDozeRunnable); - mHandler.postDelayed(mDozeRunnable, mDozeDurationSeconds * 1000); + mHandler.postDelayed(mDozeRunnable, mDozeDurationMilliseconds); mIsDozing = true; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java index 8e85bfc4aca6..ea6821fe1696 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java @@ -48,6 +48,7 @@ public class ParametricCurve { float[] xp; float[] px; + float mLength; CurveFunction mFn; ParametricCurveFunction mScaleFn; @@ -79,6 +80,7 @@ public class ParametricCurve { dx[xStep] = (float) Math.hypot(fx[xStep] - fx[xStep - 1], step); pLength += dx[xStep]; } + mLength = pLength; // Approximate p(x), a function of cumulative progress with x, normalized to 0..1 float p = 0; px[0] = 0f; @@ -260,4 +262,11 @@ public class ParametricCurve { } return 1f - xToP(maxX, bounds); } + + /** + * Returns the length of this curve. + */ + public float getArcLength() { + return mLength; + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index a51e475bc164..141562c86b90 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -247,8 +247,10 @@ public class SystemServicesProxy { return true; } + // Note, this is only valid because we currently only allow the recents and home + // activities in the home stack if (isHomeTopMost != null) { - isHomeTopMost.value = isInHomeStack(topTask.id); + isHomeTopMost.value = SystemServicesProxy.isHomeStack(topTask.stackId); } } return false; @@ -266,17 +268,6 @@ public class SystemServicesProxy { return null; } - /** Allow a task to resize. */ - public void setTaskResizeable(int taskId) { - if (mIam == null) return; - - try { - mIam.setTaskResizeable(taskId, true); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - /** * Resizes the given task to the new bounds. */ @@ -290,12 +281,14 @@ public class SystemServicesProxy { } } - /** Docks a task to the side of the screen. */ - public void dockTask(int taskId, int createMode) { + /** Docks a task to the side of the screen and starts it. */ + public void startTaskInDockedMode(int taskId, int createMode) { if (mIam == null) return; try { - mIam.startActivityFromRecents(taskId, DOCKED_STACK_ID, null); + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setDockCreateMode(createMode); + mIam.startActivityFromRecents(taskId, DOCKED_STACK_ID, options.toBundle()); } catch (RemoteException e) { e.printStackTrace(); } @@ -313,16 +306,18 @@ public class SystemServicesProxy { } } - /** Returns whether the specified task is in the home stack */ - public boolean isInHomeStack(int taskId) { - if (mAm == null) return false; - - // If we are mocking, then just return false - if (Constants.DebugFlags.App.EnableSystemServicesProxy) { - return false; - } + /** + * Returns whether the given stack id is the home stack id. + */ + public static boolean isHomeStack(int stackId) { + return stackId == ActivityManager.HOME_STACK_ID; + } - return mAm.isInHomeStack(taskId); + /** + * Returns whether the given stack id is the freeform workspace stack id. + */ + public static boolean isFreeformStack(int stackId) { + return stackId == ActivityManager.FREEFORM_WORKSPACE_STACK_ID; } /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java index 8de8e15f9427..6fc4e20a9810 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -41,6 +41,7 @@ import java.util.List; * options specified, such that we can transition into the Recents activity seamlessly */ public class RecentsTaskLoadPlan { + private static String TAG = "RecentsTaskLoadPlan"; private static boolean DEBUG = false; @@ -58,39 +59,50 @@ public class RecentsTaskLoadPlan { } Context mContext; - RecentsConfiguration mConfig; List<ActivityManager.RecentTaskInfo> mRawTasks; TaskStack mStack; /** Package level ctor */ - RecentsTaskLoadPlan(Context context, RecentsConfiguration config) { + RecentsTaskLoadPlan(Context context) { mContext = context; - mConfig = config; } /** - * An optimization to preload the raw list of tasks. + * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent + * to most-recent order. */ public synchronized void preloadRawTasks(boolean isTopTaskHome) { SystemServicesProxy ssp = Recents.getSystemServices(); mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(), UserHandle.CURRENT.getIdentifier(), isTopTaskHome); + // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it Collections.reverse(mRawTasks); - if (DEBUG) Log.d(TAG, "preloadRawTasks, tasks: " + mRawTasks.size()); + if (DEBUG) { + Log.d(TAG, "preloadRawTasks, tasks: " + mRawTasks.size()); + for (ActivityManager.RecentTaskInfo info : mRawTasks) { + Log.d(TAG, " " + info.baseIntent + ", " + info.lastActiveTime); + } + } } /** * Preloads the list of recent tasks from the system. After this call, the TaskStack will * have a list of all the recent tasks with their metadata, not including icons or * thumbnails which were not cached and have to be loaded. + * + * The tasks will be ordered by: + * - least-recent to most-recent stack tasks + * - least-recent to most-recent freeform tasks */ public synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) { if (DEBUG) Log.d(TAG, "preloadPlan"); + RecentsConfiguration config = Recents.getConfiguration(); SystemServicesProxy ssp = Recents.getSystemServices(); Resources res = mContext.getResources(); + ArrayList<Task> freeformTasks = new ArrayList<>(); ArrayList<Task> stackTasks = new ArrayList<>(); if (mRawTasks == null) { preloadRawTasks(isTopTaskHome); @@ -113,16 +125,14 @@ public class RecentsTaskLoadPlan { int activityColor = loader.getActivityPrimaryColor(t.taskDescription); Bitmap icon = t.taskDescription != null - ? t.taskDescription.getInMemoryIcon() - : null; + ? t.taskDescription.getInMemoryIcon() : null; String iconFilename = t.taskDescription != null - ? t.taskDescription.getIconFilename() - : null; + ? t.taskDescription.getIconFilename() : null; // Add the task to the stack Task task = new Task(taskKey, (t.id != INVALID_TASK_ID), t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, contentDescription, activityIcon, - activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled, icon, + activityColor, (i == (taskCount - 1)), config.lockToAppEnabled, icon, iconFilename); task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false); if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail); @@ -145,6 +155,7 @@ public class RecentsTaskLoadPlan { ", # thumbnails: " + opts.numVisibleTaskThumbnails + ", running task id: " + opts.runningTaskId); + RecentsConfiguration config = Recents.getConfiguration(); SystemServicesProxy ssp = Recents.getSystemServices(); Resources res = mContext.getResources(); @@ -175,9 +186,9 @@ public class RecentsTaskLoadPlan { if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) { if (task.thumbnail == null || isRunningTask) { if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey); - if (mConfig.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) { + if (config.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) { task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, true); - } else if (mConfig.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) { + } else if (config.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) { loadQueue.addTask(task); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java index ea97b712dedb..71e39573537a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java @@ -155,7 +155,7 @@ class BackgroundTaskLoader implements Runnable { } } } else { - RecentsConfiguration config = RecentsConfiguration.getInstance(); + RecentsConfiguration config = Recents.getConfiguration(); SystemServicesProxy ssp = Recents.getSystemServices(); // If we've stopped the loader, then fall through to the above logic to wait on // the load thread @@ -320,8 +320,7 @@ public class RecentsTaskLoader { /** Creates a new plan for loading the recent tasks. */ public RecentsTaskLoadPlan createLoadPlan(Context context) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); - RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config); + RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context); return plan; } @@ -383,7 +382,7 @@ public class RecentsTaskLoader { * out of memory. */ public void onTrimMemory(int level) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); + RecentsConfiguration config = Recents.getConfiguration(); switch (level) { case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: // Stop the loader immediately when the UI is no longer visible @@ -528,7 +527,7 @@ public class RecentsTaskLoader { } if (loadIfNotCached) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); + RecentsConfiguration config = Recents.getConfiguration(); if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) { // Load the thumbnail from the system thumbnail = ssp.getTaskThumbnail(taskKey.id); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index 0793180549a9..60051b83ad43 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -170,6 +170,12 @@ public class Task { } } + public boolean isFreeformTask() { + // Temporarily disable: + return false; + // return SystemServicesProxy.isFreeformStack(key.stackId); + } + /** Notifies the callback listeners that this task has been loaded */ public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) { this.applicationIcon = applicationIcon; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java index fb05c01e5de9..21b6bd88bcbf 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java @@ -20,13 +20,12 @@ import android.graphics.Outline; import android.graphics.Rect; import android.view.View; import android.view.ViewOutlineProvider; +import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsConfiguration; /* An outline provider that has a clip and outline that can be animated. */ public class AnimateableViewBounds extends ViewOutlineProvider { - RecentsConfiguration mConfig; - TaskView mSourceView; Rect mClipRect = new Rect(); Rect mClipBounds = new Rect(); @@ -35,7 +34,6 @@ public class AnimateableViewBounds extends ViewOutlineProvider { final float mMinAlpha = 0.25f; public AnimateableViewBounds(TaskView source, int cornerRadius) { - mConfig = RecentsConfiguration.getInstance(); mSourceView = source; mCornerRadius = cornerRadius; setClipBottom(getClipBottom()); @@ -64,7 +62,9 @@ public class AnimateableViewBounds extends ViewOutlineProvider { mClipRect.bottom = bottom; mSourceView.invalidateOutline(); updateClipBounds(); - if (!mConfig.useHardwareLayers) { + + RecentsConfiguration config = Recents.getConfiguration(); + if (!config.useHardwareLayers) { mSourceView.mThumbnailView.updateThumbnailVisibility( bottom - mSourceView.getPaddingBottom()); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 7f618e3c250a..a8a8259e79e5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -49,7 +49,7 @@ import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; -import com.android.systemui.recents.events.ui.DismissTaskEvent; +import com.android.systemui.recents.events.ui.DismissTaskViewEvent; import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; @@ -69,6 +69,7 @@ import static android.app.ActivityManager.INVALID_STACK_ID; public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks { private static final String TAG = "RecentsView"; + private static final boolean DEBUG = false; private static final boolean ADD_HEADER_BITMAP = true; @@ -80,7 +81,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV public void runAfterPause(Runnable r); } - RecentsConfiguration mConfig; LayoutInflater mInflater; ArrayList<TaskStack> mStacks; @@ -116,7 +116,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); setWillNotDraw(false); - mConfig = RecentsConfiguration.getInstance(); mInflater = LayoutInflater.from(context); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.fast_out_slow_in); @@ -130,7 +129,8 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV /** Set/get the bsp root node */ public void setTaskStack(TaskStack stack) { - if (mConfig.getLaunchState().launchedReuseTaskStackViews) { + RecentsConfiguration config = Recents.getConfiguration(); + if (config.getLaunchState().launchedReuseTaskStackViews) { if (mTaskStackView != null) { // If onRecentsHidden is not triggered, we need to the stack view again here mTaskStackView.reset(); @@ -310,13 +310,14 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + RecentsConfiguration config = Recents.getConfiguration(); int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); // Get the search bar bounds and measure the search bar layout Rect searchBarSpaceBounds = new Rect(); if (mSearchBar != null) { - mConfig.getSearchBarBounds(new Rect(0, 0, width, height), mSystemInsets.top, + config.getSearchBarBounds(new Rect(0, 0, width, height), mSystemInsets.top, searchBarSpaceBounds); mSearchBar.measure( MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY), @@ -324,7 +325,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } Rect taskStackBounds = new Rect(); - mConfig.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top, + config.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top, mSystemInsets.right, searchBarSpaceBounds, taskStackBounds); if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) { mTaskStackView.setTaskStackBounds(taskStackBounds, mSystemInsets); @@ -345,11 +346,13 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + RecentsConfiguration config = Recents.getConfiguration(); + // Get the search bar bounds so that we lay it out Rect measuredRect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight()); Rect searchBarSpaceBounds = new Rect(); if (mSearchBar != null) { - mConfig.getSearchBarBounds(measuredRect, + config.getSearchBarBounds(measuredRect, mSystemInsets.top, searchBarSpaceBounds); mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top, searchBarSpaceBounds.right, searchBarSpaceBounds.bottom); @@ -404,22 +407,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV return super.verifyDrawable(who); } - /** Focuses the next task in the first stack view */ - public void focusNextTask(boolean forward) { - // Get the first stack view - if (mTaskStackView != null) { - mTaskStackView.focusNextTask(forward, true); - } - } - - /** Dismisses the focused task. */ - public void dismissFocusedTask() { - // Get the first stack view - if (mTaskStackView != null) { - mTaskStackView.dismissFocusedTask(); - } - } - /** Unfilters any filtered stacks */ public boolean unfilterFilteredStacks() { if (mStacks != null) { @@ -562,7 +549,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // Disable any focused state before we draw the header // Upfront the processing of the thumbnail if (tv.isFocusedTask()) { - tv.unsetFocusedTask(); + tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */); } TaskViewTransform transform = new TaskViewTransform(); transform = stackView.getStackAlgorithm().getStackTransform(tv.mTask, stackScroll, @@ -682,7 +669,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } else { // Dismiss the task and return the user to home if we fail to // launch the task - EventBus.getDefault().send(new DismissTaskEvent(task, tv)); + EventBus.getDefault().send(new DismissTaskViewEvent(task, tv)); if (mCb != null) { mCb.onTaskLaunchFailed(); } @@ -800,8 +787,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // Dock the new task if we are hovering over a valid dock state if (event.dockState != TaskStack.DockState.NONE) { SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.setTaskResizeable(event.task.key.id); - ssp.dockTask(event.task.key.id, event.dockState.createMode); + ssp.startTaskInDockedMode(event.task.key.id, event.dockState.createMode); launchTask(event.task, null, INVALID_STACK_ID); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java index cf4c9cb108e3..f3a43905a823 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java @@ -19,6 +19,7 @@ package com.android.systemui.recents.views; import android.content.res.Configuration; import android.graphics.Point; import android.view.MotionEvent; +import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent; @@ -34,14 +35,16 @@ import com.android.systemui.recents.model.TaskStack; */ class DockRegion { public static TaskStack.DockState[] PHONE_LANDSCAPE = { + // We only allow docking to the left for now on small devices TaskStack.DockState.LEFT }; public static TaskStack.DockState[] PHONE_PORTRAIT = { - // We only allow docking to the top for now + // We only allow docking to the top for now on small devices TaskStack.DockState.TOP }; public static TaskStack.DockState[] TABLET_LANDSCAPE = { - TaskStack.DockState.LEFT + TaskStack.DockState.LEFT, + TaskStack.DockState.RIGHT }; public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT; } @@ -71,7 +74,7 @@ class RecentsViewTouchHandler { public TaskStack.DockState[] getDockStatesForCurrentOrientation() { boolean isLandscape = mRv.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; - RecentsConfiguration config = RecentsConfiguration.getInstance(); + RecentsConfiguration config = Recents.getConfiguration(); TaskStack.DockState[] dockStates = isLandscape ? (config.isLargeScreen ? DockRegion.TABLET_LANDSCAPE : DockRegion.PHONE_LANDSCAPE) : (config.isLargeScreen ? DockRegion.TABLET_PORTRAIT : DockRegion.PHONE_PORTRAIT); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java index e04699c10fdd..b7c1de36279f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java @@ -68,6 +68,7 @@ public class SwipeHelper { private float mInitialTouchPos; private boolean mDragging; + private float mSnapBackTranslationX; private View mCurrView; private boolean mCanCurrViewBeDimissed; @@ -92,6 +93,10 @@ public class SwipeHelper { mDensityScale = densityScale; } + public void setSnapBackTranslationX(float translationX) { + mSnapBackTranslationX = translationX; + } + public void setPagingTouchSlop(float pagingTouchSlop) { mPagingTouchSlop = pagingTouchSlop; } @@ -267,7 +272,7 @@ public class SwipeHelper { private void snapChild(final View view, float velocity) { final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view); - ValueAnimator anim = createTranslationAnimation(view, 0); + ValueAnimator anim = createTranslationAnimation(view, mSnapBackTranslationX); int duration = SNAP_ANIM_LEN; anim.setDuration(duration); anim.setInterpolator(mLinearOutSlowInInterpolator); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java index 5fbea00b00d5..c4e2d8a92c42 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java @@ -22,6 +22,7 @@ import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import com.android.systemui.R; +import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivityLaunchState; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; @@ -31,7 +32,6 @@ import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationS public class SystemBarScrimViews { Context mContext; - RecentsConfiguration mConfig; View mStatusBarScrimView; View mNavBarScrimView; @@ -48,7 +48,6 @@ public class SystemBarScrimViews { public SystemBarScrimViews(Activity activity) { mContext = activity; - mConfig = RecentsConfiguration.getInstance(); mStatusBarScrimView = activity.findViewById(R.id.status_bar_scrim); mNavBarScrimView = activity.findViewById(R.id.nav_bar_scrim); mNavBarScrimEnterDuration = activity.getResources().getInteger( @@ -64,7 +63,8 @@ public class SystemBarScrimViews { * the first draw. */ public void prepareEnterRecentsAnimation() { - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); mHasNavBarScrim = launchState.hasNavBarScrim(); mShouldAnimateNavBarScrim = launchState.shouldAnimateNavBarScrim(); mHasStatusBarScrim = launchState.hasStatusBarScrim(); @@ -82,7 +82,8 @@ public class SystemBarScrimViews { * Starts animating the scrim views when entering Recents. */ public final void onBusEvent(EnterRecentsWindowAnimationStartedEvent event) { - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); int transitionEnterFromAppDelay = mContext.getResources().getInteger( R.integer.recents_enter_from_app_transition_duration); int transitionEnterFromHomeDelay = mContext.getResources().getInteger( diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 592885412009..dfa36d81affb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -23,13 +23,16 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.RectF; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import com.android.systemui.R; +import com.android.systemui.recents.Constants; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivity; import com.android.systemui.recents.RecentsActivityLaunchState; @@ -37,8 +40,11 @@ import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.PackagesChangedEvent; import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; -import com.android.systemui.recents.events.ui.DismissTaskEvent; +import com.android.systemui.recents.events.ui.DismissTaskViewEvent; import com.android.systemui.recents.events.ui.UserInteractionEvent; +import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; +import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent; +import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; import com.android.systemui.recents.misc.DozeTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; @@ -60,6 +66,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task> { + private final static String TAG = "TaskStackView"; + private final static boolean DEBUG = false; + /** The TaskView callbacks */ interface TaskStackViewCallbacks { public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t, @@ -68,7 +77,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void onTaskStackFilterTriggered(); public void onTaskStackUnfilterTriggered(); } - RecentsConfiguration mConfig; TaskStack mStack; TaskStackViewLayoutAlgorithm mLayoutAlgorithm; @@ -80,14 +88,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>(); DozeTrigger mUIDozeTrigger; int mFocusedTaskIndex = -1; - int mPrevAccessibilityFocusedIndex = -1; // Optimizations int mStackViewsAnimationDuration; boolean mStackViewsDirty = true; boolean mStackViewsClipDirty = true; boolean mAwaitingFirstLayout = true; boolean mStartEnterAnimationRequestedAfterLayout; - boolean mStartEnterAnimationCompleted; ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext; Rect mTaskStackBounds = new Rect(); @@ -114,10 +120,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal super(context); // Set the stack first setStack(stack); - mConfig = RecentsConfiguration.getInstance(); mViewPool = new ViewPool<>(context, this); mInflater = LayoutInflater.from(context); - mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(context, mConfig); + mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(context); mFilterAlgorithm = new TaskStackViewFilterAlgorithm(this, mViewPool); mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm); mStackScroller.setCallbacks(this); @@ -219,7 +224,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStackViewsDirty = true; mStackViewsClipDirty = true; mAwaitingFirstLayout = true; - mPrevAccessibilityFocusedIndex = -1; if (mUIDozeTrigger != null) { mUIDozeTrigger.stopDozing(); mUIDozeTrigger.resetTrigger(); @@ -332,8 +336,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** Synchronizes the views with the model */ boolean synchronizeStackViewsWithModel() { if (mStackViewsDirty) { - SystemServicesProxy ssp = Recents.getSystemServices(); - // Get all the task transforms ArrayList<Task> tasks = mStack.getTasks(); float stackScroll = mStackScroller.getStackScroll(); @@ -344,8 +346,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Return all the invisible children to the pool mTmpTaskViewMap.clear(); List<TaskView> taskViews = getTaskViews(); + boolean wasLastFocusedTaskAnimated = false; + int lastFocusedTaskIndex = -1; int taskViewCount = taskViews.size(); - boolean reaquireAccessibilityFocus = false; for (int i = taskViewCount - 1; i >= 0; i--) { TaskView tv = taskViews.get(i); Task task = tv.getTask(); @@ -353,8 +356,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) { mTmpTaskViewMap.put(task, tv); } else { + if (tv.isFocusedTask()) { + wasLastFocusedTaskAnimated = tv.isFocusAnimated(); + lastFocusedTaskIndex = taskIndex; + resetFocusedTask(); + } mViewPool.returnViewToPool(tv); - reaquireAccessibilityFocus |= (i == mPrevAccessibilityFocusedIndex); } } @@ -385,21 +392,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Animate the task into place tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex), mStackViewsAnimationDuration, mRequestUpdateClippingListener); + } - // Request accessibility focus on the next view if we removed the task - // that previously held accessibility focus - if (reaquireAccessibilityFocus) { - taskViews = getTaskViews(); - taskViewCount = taskViews.size(); - if (taskViewCount > 0 && ssp.isTouchExplorationEnabled() && - mPrevAccessibilityFocusedIndex != -1) { - TaskView atv = taskViews.get(taskViewCount - 1); - int indexOfTask = mStack.indexOfTask(atv.getTask()); - if (mPrevAccessibilityFocusedIndex != indexOfTask) { - tv.requestAccessibilityFocus(); - mPrevAccessibilityFocusedIndex = indexOfTask; - } - } + // Update the focus if the previous focused task was returned to the view pool + if (lastFocusedTaskIndex != -1) { + if (lastFocusedTaskIndex < visibleRange[1]) { + setFocusedTask(visibleRange[1], false, wasLastFocusedTaskAnimated); + } else { + setFocusedTask(visibleRange[0], false, wasLastFocusedTaskAnimated); } } @@ -473,118 +473,80 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal return mStackScroller; } - /** Focuses the task at the specified index in the stack */ - void focusTask(int taskIndex, boolean scrollToNewPosition, final boolean animateFocusedState) { - // Return early if the task is already focused - if (taskIndex == mFocusedTaskIndex) return; + /** + * Sets the focused task to the provided (bounded taskIndex). + */ + private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated) { + setFocusedTask(taskIndex, scrollToTask, animated, true); + } - if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) { - mFocusedTaskIndex = taskIndex; - mPrevAccessibilityFocusedIndex = taskIndex; + /** + * Sets the focused task to the provided (bounded taskIndex). + */ + private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated, + final boolean requestViewFocus) { + // Find the next task to focus + int newFocusedTaskIndex = mStack.getTaskCount() > 0 ? + Math.max(0, Math.min(mStack.getTaskCount() - 1, taskIndex)) : -1; + final Task newFocusedTask = (newFocusedTaskIndex != -1) ? + mStack.getTasks().get(newFocusedTaskIndex) : null; + + // Reset the last focused task state if changed + if (mFocusedTaskIndex != -1) { + Task focusedTask = mStack.getTasks().get(mFocusedTaskIndex); + if (focusedTask != newFocusedTask) { + resetFocusedTask(); + } + } - // Focus the view if possible, otherwise, focus the view after we scroll into position - final Task t = mStack.getTasks().get(mFocusedTaskIndex); - Runnable postScrollRunnable = new Runnable() { + mFocusedTaskIndex = newFocusedTaskIndex; + if (mFocusedTaskIndex != -1) { + Runnable focusTaskRunnable = new Runnable() { @Override public void run() { - TaskView tv = getChildViewForTask(t); + TaskView tv = getChildViewForTask(newFocusedTask); if (tv != null) { - tv.setFocusedTask(animateFocusedState); - tv.requestAccessibilityFocus(); + tv.setFocusedState(true, animated, requestViewFocus); } } }; - // Scroll the view into position (just center it in the curve) - if (scrollToNewPosition) { - float newScroll = mLayoutAlgorithm.getStackScrollForTask(t) - 0.5f; + if (scrollToTask) { + // TODO: Center the newly focused task view + float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask) - 0.5f; newScroll = mStackScroller.getBoundedStackScroll(newScroll); - mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, postScrollRunnable); + mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, + focusTaskRunnable); } else { - if (postScrollRunnable != null) { - postScrollRunnable.run(); - } + focusTaskRunnable.run(); } - } } /** - * Ensures that there is a task focused, if nothing is focused, then we will use the task - * at the center of the visible stack. + * Sets the focused task relative to the currently focused task. + * + * @param animated determines whether to actually draw the highlight along with the change in + * focus. */ - public boolean ensureFocusedTask(boolean findClosestToCenter) { - if (mFocusedTaskIndex < 0) { - List<TaskView> taskViews = getTaskViews(); - int taskViewCount = taskViews.size(); - if (findClosestToCenter) { - // If there is no task focused, then find the task that is closes to the center - // of the screen and use that as the currently focused task - int x = mLayoutAlgorithm.mStackRect.centerX(); - int y = mLayoutAlgorithm.mStackRect.centerY(); - for (int i = taskViewCount - 1; i >= 0; i--) { - TaskView tv = taskViews.get(i); - tv.getHitRect(mTmpRect); - if (mTmpRect.contains(x, y)) { - mFocusedTaskIndex = mStack.indexOfTask(tv.getTask()); - mPrevAccessibilityFocusedIndex = mFocusedTaskIndex; - break; - } - } - } - // If we can't find the center task, then use the front most index - if (mFocusedTaskIndex < 0 && taskViewCount > 0) { - TaskView tv = taskViews.get(taskViewCount - 1); - mFocusedTaskIndex = mStack.indexOfTask(tv.getTask()); - mPrevAccessibilityFocusedIndex = mFocusedTaskIndex; - } - } - return mFocusedTaskIndex >= 0; + public void setRelativeFocusedTask(boolean forward, boolean animated) { + // Find the next index to focus + int newIndex = mFocusedTaskIndex + (forward ? -1 : 1); + setFocusedTask(newIndex, true, animated); } /** - * Focuses the next task in the stack. - * @param animateFocusedState determines whether to actually draw the highlight along with - * the change in focus, as well as whether to scroll to fit the - * task into view. + * Resets the focused task. */ - public void focusNextTask(boolean forward, boolean animateFocusedState) { - // Find the next index to focus - int numTasks = mStack.getTaskCount(); - if (numTasks == 0) return; - - int direction = (forward ? -1 : 1); - int newIndex = mFocusedTaskIndex + direction; - if (newIndex >= 0 && newIndex <= (numTasks - 1)) { - newIndex = Math.max(0, Math.min(numTasks - 1, newIndex)); - focusTask(newIndex, true, animateFocusedState); - } - } - - /** Dismisses the focused task. */ - public void dismissFocusedTask() { - // Return early if the focused task index is invalid - if (mFocusedTaskIndex < 0 || mFocusedTaskIndex >= mStack.getTaskCount()) { - mFocusedTaskIndex = -1; - return; - } - - Task t = mStack.getTasks().get(mFocusedTaskIndex); - TaskView tv = getChildViewForTask(t); - tv.dismissTask(); - } - - /** Resets the focused task. */ void resetFocusedTask() { - if ((0 <= mFocusedTaskIndex) && (mFocusedTaskIndex < mStack.getTaskCount())) { + if (mFocusedTaskIndex != -1) { Task t = mStack.getTasks().get(mFocusedTaskIndex); TaskView tv = getChildViewForTask(t); if (tv != null) { - tv.unsetFocusedTask(); + tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */); } } mFocusedTaskIndex = -1; - mPrevAccessibilityFocusedIndex = -1; } @Override @@ -609,12 +571,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal super.onInitializeAccessibilityNodeInfo(info); List<TaskView> taskViews = getTaskViews(); int taskViewCount = taskViews.size(); - if (taskViewCount > 1 && mPrevAccessibilityFocusedIndex != -1) { + if (taskViewCount > 1 && mFocusedTaskIndex != -1) { info.setScrollable(true); - if (mPrevAccessibilityFocusedIndex > 0) { + if (mFocusedTaskIndex > 0) { info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); } - if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) { + if (mFocusedTaskIndex < mStack.getTaskCount() - 1) { info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); } } @@ -630,22 +592,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (super.performAccessibilityAction(action, arguments)) { return true; } - if (ensureFocusedTask(false)) { - switch (action) { - case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { - if (mPrevAccessibilityFocusedIndex > 0) { - focusNextTask(true, false); - return true; - } - } - break; - case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { - if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) { - focusNextTask(false, false); - return true; - } - } - break; + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + setRelativeFocusedTask(true, false /* animated */); + return true; + } + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + setRelativeFocusedTask(false, false /* animated */); + return true; } } return false; @@ -677,10 +631,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** Computes the stack and task rects */ - public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds, - boolean launchedWithAltTab, boolean launchedFromHome) { + public void computeRects(Rect taskStackBounds) { // Compute the rects in the stack algorithm - mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds); + mLayoutAlgorithm.computeRects(taskStackBounds); // Update the scroll bounds updateMinMaxScroll(false); @@ -718,9 +671,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int height = MeasureSpec.getSize(heightMeasureSpec); // Compute our stack/task rects - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); - computeRects(width, height, mTaskStackBounds, launchState.launchedWithAltTab, - launchState.launchedFromHome); + computeRects(mTaskStackBounds); // If this is the first layout, then scroll to the front of the stack and synchronize the // stack views immediately to load all the views @@ -741,12 +692,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mTmpRect.setEmpty(); } tv.measure( - MeasureSpec.makeMeasureSpec( - mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right, - MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec( - mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom, - MeasureSpec.EXACTLY)); + MeasureSpec.makeMeasureSpec( + mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right, + MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec( + mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom, + MeasureSpec.EXACTLY)); } setMeasuredDimension(width, height); @@ -815,17 +766,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStartEnterAnimationContext = null; } - // When Alt-Tabbing, focus the previous task (but leave the animation until we finish the - // enter animation). - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); - if (launchState.launchedWithAltTab) { - if (launchState.launchedFromAppWithThumbnail) { - focusTask(Math.max(0, mStack.getTaskCount() - 2), false, - launchState.launchedHasConfigurationChanged); - } else { - focusTask(Math.max(0, mStack.getTaskCount() - 1), false, - launchState.launchedHasConfigurationChanged); - } + // Set the task focused state without requesting view focus, and leave the focus animations + // until after the enter-animation + if (!Constants.DebugFlags.App.EnableFastToggleRecents && launchTargetTask != null) { + setFocusedTask(mStack.indexOfTask(launchTargetTask), false /* scrollToTask */, + false /* animated */, false /* requestViewFocus */); + } else { + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); + int taskOffset = launchState.launchedFromHome ? -1 : -2; + setFocusedTask(mStack.getTaskCount() + taskOffset, false /* scrollToTask */, + false /* animated */, false /* requestViewFocus */); } // Start dozing @@ -874,32 +825,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { @Override public void run() { - mStartEnterAnimationCompleted = true; // Poke the dozer to restart the trigger after the animation completes mUIDozeTrigger.poke(); - SystemServicesProxy ssp = Recents.getSystemServices(); - List<TaskView> taskViews = getTaskViews(); - int taskViewCount = taskViews.size(); - if (taskViewCount > 0) { - // Focus the first view if accessibility is enabled - if (ssp.isTouchExplorationEnabled()) { - TaskView tv = taskViews.get(taskViewCount - 1); - tv.requestAccessibilityFocus(); - mPrevAccessibilityFocusedIndex = mStack.indexOfTask(tv.getTask()); - } - } - - // Start the focus animation when alt-tabbing - ArrayList<Task> tasks = mStack.getTasks(); - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); - if (launchState.launchedWithAltTab && - !launchState.launchedHasConfigurationChanged && - 0 <= mFocusedTaskIndex && mFocusedTaskIndex < tasks.size()) { - TaskView tv = getChildViewForTask(tasks.get(mFocusedTaskIndex)); - if (tv != null) { - tv.setFocusedTask(true); - } + // Update the focused state here -- since we only set the focused task without + // requesting view focus in onFirstLayout(), actually request view focus and + // animate the focused state if we are alt-tabbing now, after the window enter + // animation is completed + if (mFocusedTaskIndex != -1) { + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); + setFocusedTask(mFocusedTaskIndex, false /* scrollToTask */, + launchState.launchedWithAltTab); } } }); @@ -1132,11 +1069,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void prepareViewToEnterPool(TaskView tv) { Task task = tv.getTask(); - // Clear the accessibility focus for that view - if (tv.isAccessibilityFocused()) { - tv.clearAccessibilityFocus(); - } - // Report that this tasks's data is no longer being used Recents.getTaskLoader().unloadTaskData(task); @@ -1167,11 +1099,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // If the doze trigger has already fired, then update the state for this task view tv.setNoUserInteractionState(); - // If we've finished the start animation, then ensure we always enable the focus animations - if (mStartEnterAnimationCompleted) { - tv.enableFocusAnimations(); - } - // Find the index where this task should be placed in the stack int insertIndex = -1; int taskIndex = mStack.indexOfTask(task); @@ -1231,13 +1158,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } - @Override - public void onTaskViewFocusChanged(TaskView tv, boolean focused) { - if (focused) { - mFocusedTaskIndex = mStack.indexOfTask(tv.getTask()); - } - } - /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ @Override @@ -1259,13 +1179,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal for (int i = tasks.size() - 1; i >= 0; i--) { final Task t = tasks.get(i); if (removedComponents.contains(t.key.getComponent())) { - TaskView tv = getChildViewForTask(t); + final TaskView tv = getChildViewForTask(t); if (tv != null) { // For visible children, defer removing the task until after the animation tv.startDeleteTaskAnimation(new Runnable() { @Override public void run() { - mStack.removeTask(t); + removeTaskViewFromStack(tv); } }, 0); } else { @@ -1276,33 +1196,23 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } - public final void onBusEvent(DismissTaskEvent event) { - TaskView tv = event.taskView; - Task task = tv.getTask(); - int taskIndex = mStack.indexOfTask(task); - boolean taskWasFocused = tv.isFocusedTask(); + public final void onBusEvent(DismissTaskViewEvent event) { + removeTaskViewFromStack(event.taskView); + } - // Announce for accessibility - tv.announceForAccessibility(getContext().getString(R.string.accessibility_recents_item_dismissed, - tv.getTask().activityLabel)); + public final void onBusEvent(FocusNextTaskViewEvent event) { + setRelativeFocusedTask(true, true); + } - // Remove the task from the view - mStack.removeTask(task); + public final void onBusEvent(FocusPreviousTaskViewEvent event) { + setRelativeFocusedTask(false, true); + } - // If the dismissed task was focused, then we should focus the new task in the same index - if (taskWasFocused) { - ArrayList<Task> tasks = mStack.getTasks(); - int nextTaskIndex = Math.min(tasks.size() - 1, taskIndex - 1); - if (nextTaskIndex >= 0) { - Task nextTask = tasks.get(nextTaskIndex); - TaskView nextTv = getChildViewForTask(nextTask); - if (nextTv != null) { - // Focus the next task, and only animate the visible state if we are launched - // from Alt-Tab - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); - nextTv.setFocusedTask(launchState.launchedWithAltTab); - } - } + public final void onBusEvent(DismissFocusedTaskViewEvent event) { + if (mFocusedTaskIndex != -1) { + Task t = mStack.getTasks().get(mFocusedTaskIndex); + TaskView tv = getChildViewForTask(t); + tv.dismissTask(); } } @@ -1316,4 +1226,33 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal reset(); } } + + /** + * Removes the task from the stack, and updates the focus to the next task in the stack if the + * removed TaskView was focused. + */ + private void removeTaskViewFromStack(TaskView tv) { + SystemServicesProxy ssp = Recents.getSystemServices(); + Task task = tv.getTask(); + int taskIndex = mStack.indexOfTask(task); + boolean taskWasFocused = tv.isFocusedTask(); + + // Reset the previously focused task before it is removed from the stack + resetFocusedTask(); + + // Announce for accessibility + tv.announceForAccessibility(getContext().getString( + R.string.accessibility_recents_item_dismissed, tv.getTask().activityLabel)); + + // Remove the task from the stack + mStack.removeTask(task); + + if (taskWasFocused || ssp.isTouchExplorationEnabled()) { + // If the dismissed task was focused or if we are in touch exploration mode, then focus + // the next task + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); + setFocusedTask(taskIndex - 1, true /* scrollToTask */, launchState.launchedWithAltTab); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java index 9a5d9bdf7902..c74c65456bdb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java @@ -21,6 +21,7 @@ import android.content.res.Resources; import android.graphics.Rect; import android.util.Log; import com.android.systemui.R; +import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.ParametricCurve; import com.android.systemui.recents.misc.Utilities; @@ -32,14 +33,15 @@ import java.util.HashMap; /** * The layout logic for a TaskStackView. + * */ public class TaskStackViewLayoutAlgorithm { - private static final boolean DEBUG = false; private static final String TAG = "TaskStackViewLayoutAlgorithm"; + private static final boolean DEBUG = false; // The min scale of the last task at the top of the curve - private static final float STACK_PEEK_MIN_SCALE = 0.75f; + private static final float STACK_PEEK_MIN_SCALE = 0.85f; // The scale of the last task private static final float SINGLE_TASK_SCALE = 0.95f; // The percentage of height of task to show between tasks @@ -58,14 +60,17 @@ public class TaskStackViewLayoutAlgorithm { } Context mContext; - RecentsConfiguration mConfig; // This is the view bounds inset exactly by the search bar, but without the bottom inset // see RecentsConfiguration.getTaskStackBounds() public Rect mStackRect = new Rect(); + // This is the task view bounds for layout (untransformed), the rect is top-aligned to the top // of the stack rect public Rect mTaskRect = new Rect(); + + // The bounds of the freeform workspace, the rect is top-aligned to the top of the stack rect + public Rect mFreeformRect = new Rect(); // This is the current system insets public Rect mSystemInsets = new Rect(); @@ -74,8 +79,13 @@ public class TaskStackViewLayoutAlgorithm { // The largest scroll progress, at this value, the front most task will be visible above the // navigation bar float mMaxScrollP; + // The scroll progress at which the stack scroll ends and the overscroll begins. This serves + // as the point at which we can show the freeform space. + float mMaxStackScrollP; // The initial progress that the scroller is set float mInitialScrollP; + // The task progress for the front-most task in the stack + float mFrontMostTaskP; // The relative progress to ensure that the height between affiliated tasks is respected float mWithinAffiliationPOffset; @@ -89,11 +99,21 @@ public class TaskStackViewLayoutAlgorithm { // The relative progress to ensure that the offset from the bottom of the stack to the bottom // of the task is respected float mTaskBottomPOffset; + // The relative progress to ensure that the freeform workspace height is respected + float mFreeformWorkspacePOffset; // The front-most task bottom offset int mTaskBottomOffset; - // The last computed task count - int mNumTasks; + // The number of cells in the freeform workspace + int mFreeformCellXCount; + int mFreeformCellYCount; + // The width and height of the cells in the freeform workspace + int mFreeformCellWidth; + int mFreeformCellHeight; + + // The last computed task counts + int mNumStackTasks; + int mNumFreeformTasks; // The min/max z translations int mMinTranslationZ; int mMaxTranslationZ; @@ -104,12 +124,11 @@ public class TaskStackViewLayoutAlgorithm { // Log function static ParametricCurve sCurve; - public TaskStackViewLayoutAlgorithm(Context context, RecentsConfiguration config) { + public TaskStackViewLayoutAlgorithm(Context context) { Resources res = context.getResources(); mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min); mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max); mContext = context; - mConfig = config; if (sCurve == null) { sCurve = new ParametricCurve(new ParametricCurve.CurveFunction() { // The large the XScale, the longer the flat area of the curve @@ -153,23 +172,25 @@ public class TaskStackViewLayoutAlgorithm { } /** - * Computes the stack and task rects + * Computes the stack and task rects. */ - public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) { - int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * taskStackBounds.width()); + public void computeRects(Rect taskStackBounds) { + RecentsConfiguration config = Recents.getConfiguration(); + int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width()); int heightPadding = mContext.getResources().getDimensionPixelSize( R.dimen.recents_stack_top_padding); // Compute the stack rect, inset from the given task stack bounds mStackRect.set(taskStackBounds.left + widthPadding, taskStackBounds.top + heightPadding, - taskStackBounds.right - widthPadding, windowHeight); + taskStackBounds.right - widthPadding, taskStackBounds.bottom); mTaskBottomOffset = mSystemInsets.bottom + heightPadding; // Compute the task rect, align it to the top-center square in the stack rect - int size = Math.min(mStackRect.width(), taskStackBounds.height() - mTaskBottomOffset); + int size = Math.min(mStackRect.width(), mStackRect.height() - mTaskBottomOffset); int xOffset = (mStackRect.width() - size) / 2; mTaskRect.set(mStackRect.left + xOffset, mStackRect.top, mStackRect.right - xOffset, mStackRect.top + size); + mFreeformRect.set(mTaskRect); // Compute the progress offsets int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize( @@ -184,9 +205,12 @@ public class TaskStackViewLayoutAlgorithm { mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2, mStackRect); mTaskBottomPOffset = sCurve.computePOffsetForHeight(mTaskBottomOffset, mStackRect); + mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformRect.height(), + mStackRect); if (DEBUG) { Log.d(TAG, "computeRects"); + Log.d(TAG, "\tarclength: " + sCurve.getArcLength()); Log.d(TAG, "\tmStackRect: " + mStackRect); Log.d(TAG, "\tmTaskRect: " + mTaskRect); Log.d(TAG, "\tmSystemInsets: " + mSystemInsets); @@ -222,29 +246,38 @@ public class TaskStackViewLayoutAlgorithm { // Clear the progress map mTaskProgressMap.clear(); - mNumTasks = tasks.size(); // Return early if we have no tasks if (tasks.isEmpty()) { - mMinScrollP = mMaxScrollP = 0; + mMinScrollP = mMaxScrollP = mMaxStackScrollP = 0; + mNumStackTasks = mNumFreeformTasks = 0; return; } - // We calculate the progress by taking the progress of the element from the bottom of the - // screen - if (mNumTasks == 1) { - // Just center the task in the visible stack rect - mMinScrollP = mMaxScrollP = mInitialScrollP = 0f; - mTaskProgressMap.put(tasks.get(0).key, 0f); - } else { - // Update the tasks from back to front with the new progresses. We set the initial - // progress to the progress at which the top of the last task is near the center of the - // visible stack rect. + // Filter the set of freeform and stack tasks + ArrayList<Task> freeformTasks = new ArrayList<>(); + ArrayList<Task> stackTasks = new ArrayList<>(); + for (int i = 0; i < tasks.size(); i++) { + Task task = tasks.get(i); + if (task.isFreeformTask()) { + freeformTasks.add(task); + } else { + stackTasks.add(task); + } + } + mNumStackTasks = stackTasks.size(); + mNumFreeformTasks = freeformTasks.size(); + + // TODO: In the case where there is only freeform tasks, then the scrolls should be set to + // zero + + if (!stackTasks.isEmpty()) { + // Update the for each task from back to front. float pAtBackMostTaskTop = 0; float pAtFrontMostTaskTop = pAtBackMostTaskTop; - int taskCount = tasks.size(); + int taskCount = stackTasks.size(); for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); + Task task = stackTasks.get(i); mTaskProgressMap.put(task.key, pAtFrontMostTaskTop); if (i < (taskCount - 1)) { @@ -255,15 +288,47 @@ public class TaskStackViewLayoutAlgorithm { } } - // Set the max scroll progress to the point at which the bottom of the front-most task - // is aligned to the bottom of the stack (including nav bar and stack padding) - mMaxScrollP = pAtFrontMostTaskTop - 1f + mTaskBottomPOffset + mTaskHeightPOffset; + mFrontMostTaskP = pAtFrontMostTaskTop; + // Set the max scroll progress to the point at which the top of the front-most task + // is aligned to the bottom of the stack (offset by nav bar, padding, and task height) + mMaxStackScrollP = getBottomAlignedScrollProgress(pAtFrontMostTaskTop, + mTaskBottomPOffset + mTaskHeightPOffset); // Basically align the back-most task such that its progress is the same as the top of - // the front most task at the max scroll - mMinScrollP = pAtBackMostTaskTop - 1f + mTaskBottomPOffset + mTaskHeightPOffset; - // The offset the inital scroll position to the front of the stack, with half the front - // task height visible - mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset); + // the front most task at the max stack scroll + mMinScrollP = getBottomAlignedScrollProgress(pAtBackMostTaskTop, + mTaskBottomPOffset + mTaskHeightPOffset); + } + + if (!freeformTasks.isEmpty()) { + // Calculate the cell width/height depending on the number of freeform tasks + mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(mNumFreeformTasks))); + mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) mNumFreeformTasks / mFreeformCellXCount)); + mFreeformCellWidth = mFreeformRect.width() / mFreeformCellXCount; + mFreeformCellHeight = mFreeformRect.height() / mFreeformCellYCount; + + // Put each of the tasks in the progress map at a fixed index (does not need to actually + // map to a scroll position, just by index) + int taskCount = freeformTasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = freeformTasks.get(i); + mTaskProgressMap.put(task.key, (mFrontMostTaskP + 1) + i); + } + + // The max scroll includes the freeform workspace offset. As the scroll progress exceeds + // mMaxStackScrollP up to mMaxScrollP, the stack will translate upwards and the freeform + // workspace will be visible + mMaxScrollP = mMaxStackScrollP + mFreeformWorkspacePOffset; + mInitialScrollP = mMaxScrollP; + } else { + mMaxScrollP = mMaxStackScrollP; + mInitialScrollP = Math.max(mMinScrollP, mMaxStackScrollP - mTaskHalfHeightPOffset); + } + if (DEBUG) { + Log.d(TAG, "mNumStackTasks: " + mNumStackTasks); + Log.d(TAG, "mNumFreeformTasks: " + mNumFreeformTasks); + Log.d(TAG, "mMinScrollP: " + mMinScrollP); + Log.d(TAG, "mMaxStackScrollP: " + mMaxStackScrollP); + Log.d(TAG, "mMaxScrollP: " + mMaxScrollP); } } @@ -272,11 +337,24 @@ public class TaskStackViewLayoutAlgorithm { * computeMinMaxScroll() is called first. */ public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) { + // Ensure minimum visibility count if (tasks.size() <= 1) { return new VisibilityReport(1, 1); } - // Walk backwards in the task stack and count the number of tasks and visible thumbnails + // If there are freeform tasks, then they will be the only ones visible + int freeformTaskCount = 0; + for (Task t : tasks) { + if (t.isFreeformTask()) { + freeformTaskCount++; + } + } + if (freeformTaskCount > 0) { + return new VisibilityReport(freeformTaskCount, freeformTaskCount); + } + + // Otherwise, walk backwards in the stack and count the number of tasks and visible + // thumbnails int taskHeight = mTaskRect.height(); int taskBarHeight = mContext.getResources().getDimensionPixelSize( R.dimen.recents_task_bar_height); @@ -338,18 +416,38 @@ public class TaskStackViewLayoutAlgorithm { /** Update/get the transform */ public TaskViewTransform getStackTransform(float taskProgress, float stackScroll, TaskViewTransform transformOut, TaskViewTransform prevTransform) { - if (DEBUG) { - Log.d(TAG, "getStackTransform: " + stackScroll); + float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset; + int overscrollYOffset = 0; + if (mNumFreeformTasks > 0) { + overscrollYOffset = (int) (Math.max(0, stackOverscroll) * mFreeformRect.height()); } - if (mNumTasks == 1) { + if ((mNumFreeformTasks > 0) && (stackScroll > mMaxStackScrollP) && + (taskProgress > mFrontMostTaskP)) { + // This is a freeform task, so lay it out in the freeform workspace + int taskIndex = Math.round(taskProgress - (mFrontMostTaskP + 1)); + int x = taskIndex % mFreeformCellXCount; + int y = taskIndex / mFreeformCellXCount; + int frontTaskBottom = mStackRect.height() - mTaskBottomOffset; + float scale = (float) mFreeformCellWidth / mTaskRect.width(); + int scaleXOffset = (int) (((1f - scale) * mTaskRect.width()) / 2); + int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2); + transformOut.scale = scale; + transformOut.translationX = x * mFreeformCellWidth - scaleXOffset; + transformOut.translationY = frontTaskBottom - overscrollYOffset + + (y * mFreeformCellHeight) - scaleYOffset; + transformOut.visible = true; + return transformOut; + + } else if (mNumStackTasks == 1) { // Center the task in the stack, changing the scale will not follow the curve, but just // modulate some values directly - float pTaskRelative = -stackScroll; + float pTaskRelative = mMinScrollP - stackScroll; float scale = SINGLE_TASK_SCALE; int topOffset = (mStackRect.height() - mTaskBottomOffset - mTaskRect.height()) / 2; transformOut.scale = scale; - transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height())); + transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height())) - + overscrollYOffset; transformOut.translationZ = mMaxTranslationZ; transformOut.rect.set(mTaskRect); transformOut.rect.offset(0, transformOut.translationY); @@ -357,8 +455,12 @@ public class TaskStackViewLayoutAlgorithm { transformOut.visible = true; transformOut.p = pTaskRelative; return transformOut; + } else { float pTaskRelative = taskProgress - stackScroll; + if (mNumFreeformTasks > 0) { + pTaskRelative = Math.min(mMaxStackScrollP, pTaskRelative); + } float pBounded = Math.max(0, Math.min(pTaskRelative, 1f)); // If the task top is outside of the bounds below the screen, then immediately reset it if (pTaskRelative > 1f) { @@ -379,7 +481,7 @@ public class TaskStackViewLayoutAlgorithm { int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2); transformOut.scale = scale; transformOut.translationY = sCurve.pToX(pBounded, mStackRect) - mStackRect.top - - scaleYOffset; + scaleYOffset - overscrollYOffset; transformOut.translationZ = Math.max(mMinTranslationZ, mMinTranslationZ + (pBounded * (mMaxTranslationZ - mMinTranslationZ))); transformOut.rect.set(mTaskRect); @@ -392,6 +494,27 @@ public class TaskStackViewLayoutAlgorithm { } /** + * Update/get the transform + */ + public TaskViewTransform getFreeformWorkspaceBounds(float stackScroll, + TaskViewTransform transformOut) { + transformOut.reset(); + if (mNumFreeformTasks == 0) { + return transformOut; + } + + if (stackScroll > mMaxStackScrollP) { + float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset; + int overscrollYOffset = (int) (stackOverscroll * mFreeformRect.height()); + int frontTaskBottom = mStackRect.height() - mTaskBottomOffset; + transformOut.visible = true; + transformOut.alpha = + transformOut.translationY = frontTaskBottom - overscrollYOffset; + } + return transformOut; + } + + /** * Returns the untransformed task view bounds. */ public Rect getUntransformedTaskViewBounds() { @@ -406,4 +529,29 @@ public class TaskStackViewLayoutAlgorithm { if (!mTaskProgressMap.containsKey(t.key)) return 0f; return mTaskProgressMap.get(t.key); } + + /** + * Maps a movement in screen y, relative to {@param downY}, to a movement in along the arc + * length of the curve. We know the curve is mostly flat, so we just map the length of the + * screen along the arc-length proportionally (1/arclength). + */ + public float getDeltaPForY(int downY, int y) { + float deltaP = (float) (y - downY) / mStackRect.height() * (1f / sCurve.getArcLength()); + return -deltaP; + } + + /** + * This is the inverse of {@link #getDeltaPForY}. Given a movement along the arc length + * of the curve, map back to the screen y. + */ + public int getYForDeltaP(float downScrollP, float p) { + int y = (int) ((p - downScrollP) * mStackRect.height() * sCurve.getArcLength()); + return -y; + } + + private float getBottomAlignedScrollProgress(float p, float pOffsetFromBottom) { + // At scroll progress == p, then p is at the top of the stack + // At scroll progress == p + 1, then p is at the bottom of the stack + return p - (1 - pOffsetFromBottom); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java index 3d3b13dd3290..15fcab4c67cd 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; +import android.util.Log; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.OverScroller; @@ -29,8 +30,12 @@ import com.android.systemui.recents.misc.Utilities; /* The scrolling logic for a TaskStackView */ public class TaskStackViewScroller { + + private static final String TAG = "TaskStackViewScroller"; + private static final boolean DEBUG = false; + public interface TaskStackViewScrollerCallbacks { - public void onScrollChanged(float p); + void onScrollChanged(float p); } Context mContext; @@ -38,6 +43,8 @@ public class TaskStackViewScroller { TaskStackViewScrollerCallbacks mCb; float mStackScrollP; + float mFlingDownScrollP; + int mFlingDownY; OverScroller mScroller; ObjectAnimator mScrollAnimator; @@ -77,11 +84,6 @@ public class TaskStackViewScroller { } } - /** Sets the current stack scroll without calling the callback. */ - void setStackScrollRaw(float s) { - mStackScrollP = s; - } - /** * Sets the current stack scroll to the initial state when you first enter recents. * @return whether the stack progress changed. @@ -92,6 +94,20 @@ public class TaskStackViewScroller { return Float.compare(prevStackScrollP, mStackScrollP) != 0; } + /** + * Starts a fling that is coordinated with the {@link TaskStackViewTouchHandler}. + */ + public void fling(float downScrollP, int downY, int y, int velY, int minY, int maxY, + int overscroll) { + if (DEBUG) { + Log.d(TAG, "fling: " + downScrollP + ", downY: " + downY + ", y: " + y + + ", velY: " + velY + ", minY: " + minY + ", maxY: " + maxY); + } + mFlingDownScrollP = downScrollP; + mFlingDownY = downY; + mScroller.fling(0, y, 0, velY, 0, 0, minY, maxY, 0, overscroll); + } + /** Bounds the current scroll if necessary */ public boolean boundScroll() { float curScroll = getStackScroll(); @@ -174,21 +190,20 @@ public class TaskStackViewScroller { /**** OverScroller ****/ + // TODO: Remove + @Deprecated int progressToScrollRange(float p) { return (int) (p * mLayoutAlgorithm.mStackRect.height()); } - float scrollRangeToProgress(int s) { - return (float) s / mLayoutAlgorithm.mStackRect.height(); - } - /** Called from the view draw, computes the next scroll. */ boolean computeScroll() { if (mScroller.computeScrollOffset()) { - float scroll = scrollRangeToProgress(mScroller.getCurrY()); - setStackScrollRaw(scroll); - if (mCb != null) { - mCb.onScrollChanged(scroll); + float deltaP = mLayoutAlgorithm.getDeltaPForY(mFlingDownY, mScroller.getCurrY()); + float scroll = mFlingDownScrollP + deltaP; + setStackScroll(scroll); + if (DEBUG) { + Log.d(TAG, "computeScroll: " + scroll); } return true; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 1274318562ea..08889c53161c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -17,6 +17,7 @@ package com.android.systemui.recents.views; import android.content.Context; +import android.content.res.Resources; import android.view.InputDevice; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -28,13 +29,17 @@ import com.android.systemui.R; import com.android.systemui.recents.Constants; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.HideRecentsEvent; -import com.android.systemui.recents.events.ui.DismissTaskEvent; +import com.android.systemui.recents.events.ui.DismissTaskViewEvent; import java.util.List; /* Handles touch events for a TaskStackView. */ class TaskStackViewTouchHandler implements SwipeHelper.Callback { - static int INACTIVE_POINTER_ID = -1; + + private static final String TAG = "TaskStackViewTouchHandler"; + private static final boolean DEBUG = true; + + private static int INACTIVE_POINTER_ID = -1; Context mContext; TaskStackView mSv; @@ -42,21 +47,16 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { VelocityTracker mVelocityTracker; boolean mIsScrolling; - - float mInitialP; - float mLastP; - float mTotalPMotion; - int mInitialMotionX, mInitialMotionY; - int mLastMotionX, mLastMotionY; + float mDownScrollP; + int mDownX, mDownY; int mActivePointerId = INACTIVE_POINTER_ID; + int mOverscrollSize; TaskView mActiveTaskView = null; int mMinimumVelocity; int mMaximumVelocity; // The scroll touch slop is used to calculate when we start scrolling int mScrollTouchSlop; - // The page touch slop is used to calculate when we start swiping - float mPagingTouchSlop; // Used to calculate when a tap is outside a task view rectangle. final int mWindowTouchSlop; @@ -65,18 +65,20 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { public TaskStackViewTouchHandler(Context context, TaskStackView sv, TaskStackViewScroller scroller) { - mContext = context; + Resources res = context.getResources(); ViewConfiguration configuration = ViewConfiguration.get(context); + mContext = context; mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mScrollTouchSlop = configuration.getScaledTouchSlop(); - mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); mWindowTouchSlop = configuration.getScaledWindowTouchSlop(); mSv = sv; mScroller = scroller; - float densityScale = context.getResources().getDisplayMetrics().density; - mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this, densityScale, mPagingTouchSlop); + float densityScale = res.getDisplayMetrics().density; + mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_stack_overscroll); + mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this, densityScale, + configuration.getScaledPagingTouchSlop()); mSwipeHelper.setMinAlpha(1f); } @@ -88,11 +90,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { mVelocityTracker.clear(); } } - void initVelocityTrackerIfNotExists() { - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - } void recycleVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); @@ -115,175 +112,70 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { return null; } - /** Constructs a simulated motion event for the current stack scroll. */ - MotionEvent createMotionEventForStackScroll(MotionEvent ev) { - MotionEvent pev = MotionEvent.obtainNoHistory(ev); - pev.setLocation(0, mScroller.progressToScrollRange(mScroller.getStackScroll())); - return pev; - } - /** Touch preprocessing for handling below */ public boolean onInterceptTouchEvent(MotionEvent ev) { - // Return early if we have no children - boolean hasTaskViews = (mSv.getTaskViews().size() > 0); - if (!hasTaskViews) { - return false; - } - // Pass through to swipe helper if we are swiping mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev); if (mInterceptedBySwipeHelper) { return true; } - TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm; - boolean wasScrolling = mScroller.isScrolling() || - (mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning()); - int action = ev.getAction(); - switch (action & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: { - // Save the touch down info - mInitialMotionX = mLastMotionX = (int) ev.getX(); - mInitialMotionY = mLastMotionY = (int) ev.getY(); - mInitialP = mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, - layoutAlgorithm.mStackRect); - mActivePointerId = ev.getPointerId(0); - mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); - // Stop the current scroll if it is still flinging - mScroller.stopScroller(); - mScroller.stopBoundScrollAnimation(); - // Initialize the velocity tracker - initOrResetVelocityTracker(); - mVelocityTracker.addMovement(createMotionEventForStackScroll(ev)); - break; - } - case MotionEvent.ACTION_POINTER_DOWN: { - final int index = ev.getActionIndex(); - mActivePointerId = ev.getPointerId(index); - mLastMotionX = (int) ev.getX(index); - mLastMotionY = (int) ev.getY(index); - mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect); - break; - } - case MotionEvent.ACTION_MOVE: { - if (mActivePointerId == INACTIVE_POINTER_ID) break; - - // Initialize the velocity tracker if necessary - initVelocityTrackerIfNotExists(); - mVelocityTracker.addMovement(createMotionEventForStackScroll(ev)); - - int activePointerIndex = ev.findPointerIndex(mActivePointerId); - int y = (int) ev.getY(activePointerIndex); - int x = (int) ev.getX(activePointerIndex); - if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) { - // Save the touch move info - mIsScrolling = true; - // Disallow parents from intercepting touch events - final ViewParent parent = mSv.getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } - } - - mLastMotionX = x; - mLastMotionY = y; - mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect); - break; - } - case MotionEvent.ACTION_POINTER_UP: { - int pointerIndex = ev.getActionIndex(); - int pointerId = ev.getPointerId(pointerIndex); - if (pointerId == mActivePointerId) { - // Select a new active pointer id and reset the motion state - final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; - mActivePointerId = ev.getPointerId(newPointerIndex); - mLastMotionX = (int) ev.getX(newPointerIndex); - mLastMotionY = (int) ev.getY(newPointerIndex); - mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect); - mVelocityTracker.clear(); - } - break; - } - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - // Animate the scroll back if we've cancelled - mScroller.animateBoundScroll(); - // Reset the drag state and the velocity tracker - mIsScrolling = false; - mActivePointerId = INACTIVE_POINTER_ID; - mActiveTaskView = null; - mTotalPMotion = 0; - recycleVelocityTracker(); - break; - } - } - - return wasScrolling || mIsScrolling; + return handleTouchEvent(ev); } /** Handles touch events once we have intercepted them */ public boolean onTouchEvent(MotionEvent ev) { - // Short circuit if we have no children - boolean hasTaskViews = (mSv.getTaskViews().size() > 0); - if (!hasTaskViews) { - return false; - } - // Pass through to swipe helper if we are swiping if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) { return true; } - // Update the velocity tracker - initVelocityTrackerIfNotExists(); + handleTouchEvent(ev); + return true; + } + + private boolean handleTouchEvent(MotionEvent ev) { + // Short circuit if we have no children + if (mSv.getTaskViews().size() == 0) { + return false; + } TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm; int action = ev.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { // Save the touch down info - mInitialMotionX = mLastMotionX = (int) ev.getX(); - mInitialMotionY = mLastMotionY = (int) ev.getY(); - mInitialP = mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, - layoutAlgorithm.mStackRect); + mDownX = (int) ev.getX(); + mDownY = (int) ev.getY(); + mDownScrollP = mScroller.getStackScroll(); mActivePointerId = ev.getPointerId(0); - mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); + mActiveTaskView = findViewAtPoint(mDownX, mDownY); + // Stop the current scroll if it is still flinging mScroller.stopScroller(); mScroller.stopBoundScrollAnimation(); + // Initialize the velocity tracker initOrResetVelocityTracker(); - mVelocityTracker.addMovement(createMotionEventForStackScroll(ev)); - // Disallow parents from intercepting touch events - final ViewParent parent = mSv.getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } + mVelocityTracker.addMovement(ev); break; } case MotionEvent.ACTION_POINTER_DOWN: { final int index = ev.getActionIndex(); + mDownX = (int) ev.getX(); + mDownY = (int) ev.getY(); + mDownScrollP = mScroller.getStackScroll(); mActivePointerId = ev.getPointerId(index); - mLastMotionX = (int) ev.getX(index); - mLastMotionY = (int) ev.getY(index); - mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, - layoutAlgorithm.mStackRect); + mVelocityTracker.addMovement(ev); break; } case MotionEvent.ACTION_MOVE: { - if (mActivePointerId == INACTIVE_POINTER_ID) break; - - mVelocityTracker.addMovement(createMotionEventForStackScroll(ev)); - int activePointerIndex = ev.findPointerIndex(mActivePointerId); - int x = (int) ev.getX(activePointerIndex); int y = (int) ev.getY(activePointerIndex); - int yTotal = Math.abs(y - mInitialMotionY); - float curP = layoutAlgorithm.sCurve.xToP(y, layoutAlgorithm.mStackRect); - float deltaP = mLastP - curP; if (!mIsScrolling) { - if (yTotal > mScrollTouchSlop) { + if (Math.abs(y - mDownY) > mScrollTouchSlop) { mIsScrolling = true; + // Disallow parents from intercepting touch events final ViewParent parent = mSv.getParent(); if (parent != null) { @@ -292,45 +184,50 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { } } if (mIsScrolling) { - float curStackScroll = mScroller.getStackScroll(); - float overScrollAmount = mScroller.getScrollAmountOutOfBounds(curStackScroll + deltaP); - if (Float.compare(overScrollAmount, 0f) != 0) { - // Bound the overscroll to a fixed amount, and inversely scale the y-movement - // relative to how close we are to the max overscroll - float maxOverScroll = mContext.getResources().getFloat( - R.dimen.recents_stack_overscroll_percentage); - deltaP *= (1f - (Math.min(maxOverScroll, overScrollAmount) - / maxOverScroll)); - } - mScroller.setStackScroll(curStackScroll + deltaP); + // If we just move linearly on the screen, then that would map to 1/arclength + // of the curve, so just move the scroll proportional to that + float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y); + float curScrollP = mDownScrollP + deltaP; + mScroller.setStackScroll(curScrollP); + } + + mVelocityTracker.addMovement(ev); + break; + } + case MotionEvent.ACTION_POINTER_UP: { + int pointerIndex = ev.getActionIndex(); + int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + // Select a new active pointer id and reset the motion state + final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; + mActivePointerId = ev.getPointerId(newPointerIndex); } - mLastMotionX = x; - mLastMotionY = y; - mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect); - mTotalPMotion += Math.abs(deltaP); + mVelocityTracker.addMovement(ev); break; } case MotionEvent.ACTION_UP: { + mVelocityTracker.addMovement(ev); mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int activePointerIndex = ev.findPointerIndex(mActivePointerId); + int y = (int) ev.getY(activePointerIndex); int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); - if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) { - float overscrollRangePct = Math.abs((float) velocity / mMaximumVelocity); - int overscrollRange = (int) (Math.min(1f, overscrollRangePct) * - (Constants.Values.TaskStackView.TaskStackMaxOverscrollRange - - Constants.Values.TaskStackView.TaskStackMinOverscrollRange)); - mScroller.mScroller.fling(0, - mScroller.progressToScrollRange(mScroller.getStackScroll()), - 0, velocity, - 0, 0, - mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMinScrollP), - mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMaxScrollP), - 0, Constants.Values.TaskStackView.TaskStackMinOverscrollRange + - overscrollRange); - // Invalidate to kick off computeScroll - mSv.invalidate(); - } else if (mIsScrolling && mScroller.isScrollOutOfBounds()) { - // Animate the scroll back into bounds - mScroller.animateBoundScroll(); + if (mIsScrolling) { + if (mScroller.isScrollOutOfBounds()) { + // Animate the scroll back into bounds + mScroller.animateBoundScroll(); + } else if (Math.abs(velocity) > mMinimumVelocity) { + float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y); + float curScrollP = mDownScrollP + deltaP; + float downToCurY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP, + curScrollP); + float downToMinY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP, + layoutAlgorithm.mMaxScrollP); + float downToMaxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP, + layoutAlgorithm.mMinScrollP); + mScroller.fling(mDownScrollP, mDownY, (int) downToCurY, velocity, + (int) downToMinY, (int) downToMaxY, mOverscrollSize); + mSv.invalidate(); + } } else if (mActiveTaskView == null) { // This tap didn't start on a task. maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY()); @@ -338,24 +235,9 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { mActivePointerId = INACTIVE_POINTER_ID; mIsScrolling = false; - mTotalPMotion = 0; recycleVelocityTracker(); break; } - case MotionEvent.ACTION_POINTER_UP: { - int pointerIndex = ev.getActionIndex(); - int pointerId = ev.getPointerId(pointerIndex); - if (pointerId == mActivePointerId) { - // Select a new active pointer id and reset the motion state - final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; - mActivePointerId = ev.getPointerId(newPointerIndex); - mLastMotionX = (int) ev.getX(newPointerIndex); - mLastMotionY = (int) ev.getY(newPointerIndex); - mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect); - mVelocityTracker.clear(); - } - break; - } case MotionEvent.ACTION_CANCEL: { if (mScroller.isScrollOutOfBounds()) { // Animate the scroll back into bounds @@ -363,20 +245,19 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { } mActivePointerId = INACTIVE_POINTER_ID; mIsScrolling = false; - mTotalPMotion = 0; recycleVelocityTracker(); break; } } - return true; + return mIsScrolling; } /** Hides recents if the up event at (x, y) is a tap on the background area. */ void maybeHideRecentsFromBackgroundTap(int x, int y) { // Ignore the up event if it's too far from its start position. The user might have been // trying to scroll or swipe. - int dx = Math.abs(mInitialMotionX - x); - int dy = Math.abs(mInitialMotionY - y); + int dx = Math.abs(mDownX - x); + int dy = Math.abs(mDownY - y); if (dx > mScrollTouchSlop || dy > mScrollTouchSlop) { return; } @@ -408,13 +289,9 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { // Find the front most task and scroll the next task to the front float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL); if (vScroll > 0) { - if (mSv.ensureFocusedTask(true)) { - mSv.focusNextTask(true, false); - } + mSv.setRelativeFocusedTask(true, false /* animated */); } else { - if (mSv.ensureFocusedTask(true)) { - mSv.focusNextTask(false, false); - } + mSv.setRelativeFocusedTask(false, false /* animated */); } return true; } @@ -431,12 +308,16 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { @Override public boolean canChildBeDismissed(View v) { + if (v instanceof TaskView) { + return !((TaskView) v).getTask().isFreeformTask(); + } return true; } @Override public void onBeginDrag(View v) { TaskView tv = (TaskView) v; + mSwipeHelper.setSnapBackTranslationX(tv.getTranslationX()); // Disable clipping with the stack while we are swiping tv.setClipViewInStack(false); // Disallow touch events from this task view @@ -461,7 +342,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { // Re-enable touch events from this task view tv.setTouchEnabled(true); // Remove the task view from the stack - EventBus.getDefault().send(new DismissTaskEvent(tv.getTask(), tv)); + EventBus.getDefault().send(new DismissTaskViewEvent(tv.getTask(), tv)); // Keep track of deletions by keyboard MetricsLogger.histogram(tv.getContext(), "overview_task_dismissed_source", Constants.Metrics.DismissSourceSwipeGesture); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index bab4da75f691..aaacc6cf99b5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -29,8 +29,8 @@ import android.graphics.Paint; import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; -import android.graphics.Rect; import android.util.AttributeSet; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewOutlineProvider; @@ -40,13 +40,15 @@ import android.view.animation.Interpolator; import android.widget.FrameLayout; import com.android.systemui.R; import com.android.systemui.recents.Constants; +import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivity; import com.android.systemui.recents.RecentsActivityLaunchState; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.ui.DismissTaskEvent; +import com.android.systemui.recents.events.ui.DismissTaskViewEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; +import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.Task; import com.android.systemui.statusbar.phone.PhoneStatusBar; @@ -55,15 +57,15 @@ import com.android.systemui.statusbar.phone.PhoneStatusBar; public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.OnClickListener, View.OnLongClickListener { + private final static String TAG = "TaskView"; + private final static boolean DEBUG = false; + /** The TaskView callbacks */ interface TaskViewCallbacks { public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask); public void onTaskViewClipStateChanged(TaskView tv); - public void onTaskViewFocusChanged(TaskView tv, boolean focused); } - RecentsConfiguration mConfig; - float mTaskProgress; ObjectAnimator mTaskProgressAnimator; float mMaxDimScale; @@ -76,6 +78,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, Task mTask; boolean mTaskDataLoaded; boolean mIsFocused; + boolean mIsFocusAnimated; boolean mFocusAnimationsEnabled; boolean mClipViewInStack; AnimateableViewBounds mViewBounds; @@ -116,8 +119,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + RecentsConfiguration config = Recents.getConfiguration(); Resources res = context.getResources(); - mConfig = RecentsConfiguration.getInstance(); mMaxDimScale = res.getInteger(R.integer.recents_max_task_stack_view_dim) / 255f; mClipViewInStack = true; mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize( @@ -130,8 +133,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, com.android.internal.R.interpolator.decelerate_quint); setTaskProgress(getTaskProgress()); setDim(getDim()); - if (mConfig.fakeShadows) { - setBackground(new FakeShadowDrawable(res, mConfig)); + if (config.fakeShadows) { + setBackground(new FakeShadowDrawable(res, config)); } setOutlineProvider(mViewBounds); } @@ -219,9 +222,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration, ValueAnimator.AnimatorUpdateListener updateCallback) { + RecentsConfiguration config = Recents.getConfiguration(); + // Apply the transform toTransform.applyToTaskView(this, duration, mFastOutSlowInInterpolator, false, - !mConfig.fakeShadows, updateCallback); + !config.fakeShadows, updateCallback); // Update the task progress Utilities.cancelAnimationWithoutCallbacks(mTaskProgressAnimator); @@ -272,7 +277,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, * first layout because the actual animation into recents may take a long time. */ void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, boolean occludesLaunchTarget, int offscreenY) { - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); int initialDim = getDim(); if (launchState.launchedHasConfigurationChanged) { // Just load the views as-is @@ -302,7 +308,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, /** Animates this task view as it enters recents */ void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) { - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); + RecentsConfiguration config = Recents.getConfiguration(); + RecentsActivityLaunchState launchState = config.getLaunchState(); Resources res = mContext.getResources(); final TaskViewTransform transform = ctx.currentTaskTransform; final int transitionEnterFromAppDelay = res.getInteger( @@ -317,7 +324,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, R.integer.recents_task_enter_from_home_stagger_delay); final int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize( R.dimen.recents_task_view_affiliate_group_enter_offset); - int startDelay = 0; if (launchState.launchedFromAppWithThumbnail) { if (mTask.isLaunchTarget) { @@ -366,7 +372,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, ctx.postAnimationTrigger.increment(); } } - startDelay = transitionEnterFromAppDelay; } else if (launchState.launchedFromHome) { // Animate the tasks up @@ -376,7 +381,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, setScaleX(transform.scale); setScaleY(transform.scale); - if (!mConfig.fakeShadows) { + if (!config.fakeShadows) { animate().translationZ(transform.translationZ); } animate() @@ -395,17 +400,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, }) .start(); ctx.postAnimationTrigger.increment(); - startDelay = delay; } - - // Enable the focus animations from this point onwards so that they aren't affected by the - // window transitions - postDelayed(new Runnable() { - @Override - public void run() { - enableFocusAnimations(); - } - }, startDelay); } public void fadeInActionButton(int delay, int duration) { @@ -547,7 +542,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, startDeleteTaskAnimation(new Runnable() { @Override public void run() { - EventBus.getDefault().send(new DismissTaskEvent(mTask, tv)); + EventBus.getDefault().send(new DismissTaskViewEvent(mTask, tv)); } }, 0); } @@ -584,8 +579,10 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, /** Returns the current dim. */ public void setDim(int dim) { + RecentsConfiguration config = Recents.getConfiguration(); + mDimAlpha = dim; - if (mConfig.useHardwareLayers) { + if (config.useHardwareLayers) { // Defer setting hardware layers if we have not yet measured, or there is no dim to draw if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) { mDimColorFilter.setColor(Color.argb(mDimAlpha, 0, 0, 0)); @@ -620,6 +617,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, anim.addListener(postAnimRunnable); } anim.start(); + } else { + postAnimRunnable.onAnimationEnd(null); } } @@ -641,58 +640,32 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, /**** View focus state ****/ /** - * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen - * if the view is not currently visible, or we are in touch state (where we still want to keep - * track of focus). - */ - public void setFocusedTask(boolean animateFocusedState) { - mIsFocused = true; - if (mFocusAnimationsEnabled) { - // Focus the header bar - mHeaderView.onTaskViewFocusChanged(true, animateFocusedState); - } - // Update the thumbnail alpha with the focus - mThumbnailView.onFocusChanged(true); - // Call the callback - if (mCb != null) { - mCb.onTaskViewFocusChanged(this, true); - } - // Workaround, we don't always want it focusable in touch mode, but we want the first task - // to be focused after the enter-recents animation, which can be triggered from either touch - // or keyboard - setFocusableInTouchMode(true); - requestFocus(); - setFocusableInTouchMode(false); - invalidate(); - } - - /** - * Unsets the focused task explicitly. + * Explicitly sets the focused state of this task. */ - void unsetFocusedTask() { - mIsFocused = false; - if (mFocusAnimationsEnabled) { - // Un-focus the header bar - mHeaderView.onTaskViewFocusChanged(false, true); - } - - // Update the thumbnail alpha with the focus - mThumbnailView.onFocusChanged(false); - // Call the callback - if (mCb != null) { - mCb.onTaskViewFocusChanged(this, false); + public void setFocusedState(boolean isFocused, boolean animated, boolean requestViewFocus) { + if (DEBUG) { + Log.d(TAG, "setFocusedState: " + mTask.activityLabel + " focused: " + isFocused + + " mIsFocused: " + mIsFocused + " animated: " + animated + + " requestViewFocus: " + requestViewFocus + " isFocused(): " + isFocused() + + " isAccessibilityFocused(): " + isAccessibilityFocused()); } - invalidate(); - } - /** - * Updates the explicitly focused state when the view focus changes. - */ - @Override - protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { - super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); - if (!gainFocus) { - unsetFocusedTask(); + SystemServicesProxy ssp = Recents.getSystemServices(); + mIsFocused = isFocused; + mIsFocusAnimated = animated; + mHeaderView.onTaskViewFocusChanged(isFocused, animated); + mThumbnailView.onFocusChanged(isFocused); + if (isFocused) { + if (requestViewFocus && !isFocused()) { + requestFocus(); + } + if (requestViewFocus && !isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) { + requestAccessibilityFocus(); + } + } else { + if (isAccessibilityFocused() && ssp.isTouchExplorationEnabled()) { + clearAccessibilityFocus(); + } } } @@ -700,17 +673,14 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, * Returns whether we have explicitly been focused. */ public boolean isFocusedTask() { - return mIsFocused || isFocused(); + return mIsFocused; } - /** Enables all focus animations. */ - void enableFocusAnimations() { - boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled; - mFocusAnimationsEnabled = true; - if (mIsFocused && !wasFocusAnimationsEnabled) { - // Re-notify the header if we were focused and animations were not previously enabled - mHeaderView.onTaskViewFocusChanged(true, true); - } + /** + * Returns whether this focused task is animated. + */ + public boolean isFocusAnimated() { + return mIsFocusAnimated; } public void disableLayersForOneFrame() { @@ -734,13 +704,14 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, @Override public void onTaskDataLoaded() { + RecentsConfiguration config = Recents.getConfiguration(); if (mThumbnailView != null && mHeaderView != null) { // Bind each of the views to the new task data mThumbnailView.rebindToTask(mTask); mHeaderView.rebindToTask(mTask); // Rebind any listeners mActionButtonView.setOnClickListener(this); - setOnLongClickListener(mConfig.hasDockedTasks ? null : this); + setOnLongClickListener(config.hasDockedTasks ? null : this); } mTaskDataLoaded = true; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index e1e07efdd953..f6353f83da52 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -39,7 +39,6 @@ import android.graphics.drawable.RippleDrawable; import android.util.AttributeSet; import android.view.View; import android.view.ViewOutlineProvider; -import android.view.accessibility.AccessibilityManager; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.FrameLayout; @@ -244,9 +243,8 @@ public class TaskViewHeader extends FrameLayout mMoveTaskButton.setOnClickListener(this); // In accessibility, a single click on the focused app info button will show it - AccessibilityManager am = (AccessibilityManager) getContext(). - getSystemService(Context.ACCESSIBILITY_SERVICE); - if (am != null && am.isEnabled()) { + SystemServicesProxy ssp = Recents.getSystemServices(); + if (ssp.isTouchExplorationEnabled()) { mApplicationIcon.setOnClickListener(this); } } @@ -369,9 +367,6 @@ public class TaskViewHeader extends FrameLayout /** Notifies the associated TaskView has been focused. */ void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) { - // If we are not animating the visible state, just return - if (!animateFocusedState) return; - boolean isRunning = false; if (mFocusAnimator != null) { isRunning = mFocusAnimator.isRunning(); @@ -379,6 +374,9 @@ public class TaskViewHeader extends FrameLayout } if (focused) { + // If we are not animating the visible state, just return + if (!animateFocusedState) return; + int currentColor = mBackgroundColor; int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark); int[][] states = new int[][] { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java index ec50eb61b558..174ff33e9681 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java @@ -26,6 +26,7 @@ import android.view.animation.Interpolator; /* The transform state for a task view */ public class TaskViewTransform { public int startDelay = 0; + public int translationX = 0; public int translationY = 0; public float translationZ = 0; public float scale = 1f; @@ -43,6 +44,7 @@ public class TaskViewTransform { public TaskViewTransform(TaskViewTransform o) { startDelay = o.startDelay; + translationX = o.translationX; translationY = o.translationY; translationZ = o.translationZ; scale = o.scale; @@ -52,9 +54,12 @@ public class TaskViewTransform { p = o.p; } - /** Resets the current transform */ + /** + * Resets the current transform. + */ public void reset() { startDelay = 0; + translationX = 0; translationY = 0; translationZ = 0; scale = 1f; @@ -71,6 +76,9 @@ public class TaskViewTransform { public boolean hasScaleChangedFrom(float v) { return (Float.compare(scale, v) != 0); } + public boolean hasTranslationXChangedFrom(float v) { + return (Float.compare(translationX, v) != 0); + } public boolean hasTranslationYChangedFrom(float v) { return (Float.compare(translationY, v) != 0); } @@ -87,6 +95,9 @@ public class TaskViewTransform { boolean requiresLayers = false; // Animate to the final state + if (hasTranslationXChangedFrom(v.getTranslationX())) { + anim.translationX(translationX); + } if (hasTranslationYChangedFrom(v.getTranslationY())) { anim.translationY(translationY); } @@ -117,6 +128,9 @@ public class TaskViewTransform { .start(); } else { // Set the changed properties + if (hasTranslationXChangedFrom(v.getTranslationX())) { + v.setTranslationX(translationX); + } if (hasTranslationYChangedFrom(v.getTranslationY())) { v.setTranslationY(translationY); } @@ -147,7 +161,8 @@ public class TaskViewTransform { @Override public String toString() { - return "TaskViewTransform delay: " + startDelay + " y: " + translationY + " z: " + translationZ + + return "TaskViewTransform delay: " + startDelay + + " x: " + translationX + " y: " + translationY + " z: " + translationZ + " scale: " + scale + " alpha: " + alpha + " visible: " + visible + " rect: " + rect + " p: " + p; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 893866925474..2c16f81a269e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -217,7 +217,7 @@ public abstract class BaseStatusBar extends SystemUI implements private boolean mDeviceProvisioned = false; - private RecentsComponent mRecents; + protected RecentsComponent mRecents; protected int mZenMode; @@ -1275,20 +1275,22 @@ public abstract class BaseStatusBar extends SystemUI implements int maxHeight = mRowMaxHeight; final StatusBarNotification sbn = entry.notification; - RemoteViews contentView = sbn.getNotification().contentView; - RemoteViews bigContentView = sbn.getNotification().bigContentView; - RemoteViews headsUpContentView = sbn.getNotification().headsUpContentView; + entry.cacheContentViews(mContext, null); + + final RemoteViews contentView = entry.cachedContentView; + final RemoteViews bigContentView = entry.cachedBigContentView; + final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView; + final RemoteViews publicContentView = entry.cachedPublicContentView; if (contentView == null) { + Log.v(TAG, "no contentView for: " + sbn.getNotification()); return false; } if (DEBUG) { - Log.v(TAG, "publicNotification: " + sbn.getNotification().publicVersion); + Log.v(TAG, "publicContentView: " + publicContentView); } - Notification publicNotification = sbn.getNotification().publicVersion; - ExpandableNotificationRow row; // Stash away previous user expansion state so we can restore it at @@ -1377,9 +1379,9 @@ public abstract class BaseStatusBar extends SystemUI implements // now the public version View publicViewLocal = null; - if (publicNotification != null) { + if (publicContentView != null) { try { - publicViewLocal = publicNotification.contentView.apply( + publicViewLocal = publicContentView.apply( sbn.getPackageContext(mContext), contentContainerPublic, mOnClickHandler); @@ -1537,30 +1539,9 @@ public abstract class BaseStatusBar extends SystemUI implements } if (viableAction != null) { - Notification stripped = n.clone(); - Notification.Builder.stripForDelivery(stripped); - stripped.extras.putBoolean("android.rebuild", true); - stripped.actions = new Notification.Action[] { viableAction }; - stripped.extras.putBoolean("android.rebuild.contentView", true); - stripped.contentView = null; - stripped.extras.putBoolean("android.rebuild.bigView", true); - stripped.bigContentView = null; - stripped.extras.putBoolean("android.rebuild.hudView", true); - stripped.headsUpContentView = null; - - stripped.extras.putParcelable(Notification.EXTRA_LARGE_ICON, - stripped.getLargeIcon()); - if (SystemProperties.getBoolean("debug.strip_third_line", false)) { - stripped.extras.putCharSequence(Notification.EXTRA_INFO_TEXT, null); - stripped.extras.putCharSequence(Notification.EXTRA_SUMMARY_TEXT, null); - } - - Notification rebuilt = Notification.Builder.rebuild(mContext, stripped); - - n.actions = rebuilt.actions; - n.bigContentView = rebuilt.bigContentView; - n.headsUpContentView = rebuilt.headsUpContentView; - n.publicVersion = rebuilt.publicVersion; + Notification.Builder rebuilder = Notification.Builder.recoverBuilder(mContext, n); + rebuilder.setActions(viableAction); + rebuilder.build(); // will rewrite n } } } @@ -2034,12 +2015,15 @@ public abstract class BaseStatusBar extends SystemUI implements } Notification n = notification.getNotification(); - if (DEBUG) { - logUpdate(entry, n); - } - boolean applyInPlace = shouldApplyInPlace(entry, n); + + boolean applyInPlace = !entry.cacheContentViews(mContext, notification.getNotification()); boolean shouldInterrupt = shouldInterrupt(entry, notification); boolean alertAgain = alertAgain(entry, n); + if (DEBUG) { + Log.d(TAG, "applyInPlace=" + applyInPlace + + " shouldInterrupt=" + shouldInterrupt + + " alertAgain=" + alertAgain); + } entry.notification = notification; mGroupManager.onEntryUpdated(entry, entry.notification); @@ -2104,101 +2088,32 @@ public abstract class BaseStatusBar extends SystemUI implements protected abstract void updateHeadsUp(String key, Entry entry, boolean shouldInterrupt, boolean alertAgain); - private void logUpdate(Entry oldEntry, Notification n) { - StatusBarNotification oldNotification = oldEntry.notification; - Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when - + " ongoing=" + oldNotification.isOngoing() - + " expanded=" + oldEntry.getContentView() - + " contentView=" + oldNotification.getNotification().contentView - + " bigContentView=" + oldNotification.getNotification().bigContentView - + " publicView=" + oldNotification.getNotification().publicVersion - + " rowParent=" + oldEntry.row.getParent()); - Log.d(TAG, "new notification: when=" + n.when - + " ongoing=" + oldNotification.isOngoing() - + " contentView=" + n.contentView - + " bigContentView=" + n.bigContentView - + " publicView=" + n.publicVersion); - } - - /** - * @return whether we can just reapply the RemoteViews from a notification in-place when it is - * updated - */ - private boolean shouldApplyInPlace(Entry entry, Notification n) { - StatusBarNotification oldNotification = entry.notification; - // XXX: modify when we do something more intelligent with the two content views - final RemoteViews oldContentView = oldNotification.getNotification().contentView; - final RemoteViews contentView = n.contentView; - final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView; - final RemoteViews bigContentView = n.bigContentView; - final RemoteViews oldHeadsUpContentView - = oldNotification.getNotification().headsUpContentView; - final RemoteViews headsUpContentView = n.headsUpContentView; - final Notification oldPublicNotification = oldNotification.getNotification().publicVersion; - final RemoteViews oldPublicContentView = oldPublicNotification != null - ? oldPublicNotification.contentView : null; - final Notification publicNotification = n.publicVersion; - final RemoteViews publicContentView = publicNotification != null - ? publicNotification.contentView : null; - boolean contentsUnchanged = entry.getContentView() != null - && contentView.getPackage() != null - && oldContentView.getPackage() != null - && oldContentView.getPackage().equals(contentView.getPackage()) - && oldContentView.getLayoutId() == contentView.getLayoutId(); - // large view may be null - boolean bigContentsUnchanged = - (entry.getExpandedContentView() == null && bigContentView == null) - || ((entry.getExpandedContentView() != null && bigContentView != null) - && bigContentView.getPackage() != null - && oldBigContentView.getPackage() != null - && oldBigContentView.getPackage().equals(bigContentView.getPackage()) - && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); - boolean headsUpContentsUnchanged = - (oldHeadsUpContentView == null && headsUpContentView == null) - || ((oldHeadsUpContentView != null && headsUpContentView != null) - && headsUpContentView.getPackage() != null - && oldHeadsUpContentView.getPackage() != null - && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage()) - && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId()); - boolean publicUnchanged = - (oldPublicContentView == null && publicContentView == null) - || ((oldPublicContentView != null && publicContentView != null) - && publicContentView.getPackage() != null - && oldPublicContentView.getPackage() != null - && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) - && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); - return contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged - && publicUnchanged; - } - - private void updateNotificationViews(Entry entry, StatusBarNotification notification) { - final RemoteViews contentView = notification.getNotification().contentView; - final RemoteViews bigContentView = notification.getNotification().bigContentView; - final RemoteViews headsUpContentView = notification.getNotification().headsUpContentView; - final Notification publicVersion = notification.getNotification().publicVersion; - final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView - : null; + private void updateNotificationViews(Entry entry, StatusBarNotification sbn) { + final RemoteViews contentView = entry.cachedContentView; + final RemoteViews bigContentView = entry.cachedBigContentView; + final RemoteViews headsUpContentView = entry.cachedHeadsUpContentView; + final RemoteViews publicContentView = entry.cachedPublicContentView; // Reapply the RemoteViews contentView.reapply(mContext, entry.getContentView(), mOnClickHandler); if (bigContentView != null && entry.getExpandedContentView() != null) { - bigContentView.reapply(notification.getPackageContext(mContext), + bigContentView.reapply(sbn.getPackageContext(mContext), entry.getExpandedContentView(), mOnClickHandler); } View headsUpChild = entry.getHeadsUpContentView(); if (headsUpContentView != null && headsUpChild != null) { - headsUpContentView.reapply(notification.getPackageContext(mContext), + headsUpContentView.reapply(sbn.getPackageContext(mContext), headsUpChild, mOnClickHandler); } if (publicContentView != null && entry.getPublicContentView() != null) { - publicContentView.reapply(notification.getPackageContext(mContext), + publicContentView.reapply(sbn.getPackageContext(mContext), entry.getPublicContentView(), mOnClickHandler); } // update the contentIntent - mNotificationClicker.register(entry.row, notification); + mNotificationClicker.register(entry.row, sbn); - entry.row.setStatusBarNotification(notification); + entry.row.setStatusBarNotification(sbn); entry.row.notifyContentUpdated(); entry.row.resetHeight(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index aedae521a7d4..6a90d8e2e147 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar; import android.app.Notification; +import android.content.Context; import android.os.SystemClock; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; @@ -24,6 +25,7 @@ import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.view.View; +import android.widget.RemoteViews; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -53,6 +55,10 @@ public class NotificationData { public boolean legacy; // whether the notification has a legacy, dark background public int targetSdk; private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET; + public RemoteViews cachedContentView; + public RemoteViews cachedBigContentView; + public RemoteViews cachedHeadsUpContentView; + public RemoteViews cachedPublicContentView; public Entry(StatusBarNotification n, StatusBarIconView ic) { this.key = n.getKey(); @@ -98,6 +104,67 @@ public class NotificationData { return row.getPublicLayout().getContractedChild(); } + public boolean cacheContentViews(Context ctx, Notification updatedNotification) { + boolean cached = false; + if (updatedNotification != null) { + final Notification.Builder updatedNotificationBuilder + = Notification.Builder.recoverBuilder(ctx, updatedNotification); + final RemoteViews newContentView = updatedNotificationBuilder.makeContentView(); + if (!compareRemoteViews(cachedContentView, newContentView)) { + cachedContentView = newContentView; + cached |= true; + } + final RemoteViews newBigContentView = + updatedNotificationBuilder.makeBigContentView(); + if (!compareRemoteViews(cachedBigContentView, newBigContentView)) { + cachedBigContentView = newBigContentView; + cached |= true; + } + final RemoteViews newHeadsUpContentView = + updatedNotificationBuilder.makeHeadsUpContentView(); + if (!compareRemoteViews(cachedHeadsUpContentView, newBigContentView)) { + cachedHeadsUpContentView = newHeadsUpContentView; + cached |= true; + } + final Notification updatedPublicNotification = updatedNotification.publicVersion; + final RemoteViews newPubContentView = (updatedPublicNotification != null) + ? Notification.Builder.recoverBuilder( + ctx, updatedPublicNotification).makeContentView() + : null; + if (!compareRemoteViews(cachedPublicContentView, newPubContentView)) { + cachedPublicContentView = newPubContentView; + cached |= true; + } + } else { + final Notification.Builder builder + = Notification.Builder.recoverBuilder(ctx, notification.getNotification()); + + cachedContentView = builder.makeContentView(); + cachedBigContentView = builder.makeBigContentView(); + cachedHeadsUpContentView = builder.makeHeadsUpContentView(); + + final Notification publicNotification = + notification.getNotification().publicVersion; + if (publicNotification != null) { + final Notification.Builder publicBuilder + = Notification.Builder.recoverBuilder(ctx, publicNotification); + cachedPublicContentView = publicBuilder.makeContentView(); + } + cached = true; + } + return cached; + } + + // Returns true if the RemoteViews are the same. + private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) { + return (a == null && b == null) || + (a != null && b != null + && b.getPackage() != null + && a.getPackage() != null + && a.getPackage().equals(b.getPackage()) + && a.getLayoutId() == b.getLayoutId()); + } + public void notifyFullScreenIntentLaunched() { lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index cfde7913613f..f3658eb3c1c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -33,6 +33,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; @@ -88,7 +89,6 @@ import android.view.ViewGroup.LayoutParams; import android.view.ViewStub; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.view.accessibility.AccessibilityEvent; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; import android.view.animation.Interpolator; @@ -146,7 +146,6 @@ import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.FullscreenUserSwitcher; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.HotspotControllerImpl; -import com.android.systemui.statusbar.policy.KeyButtonView; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.LocationControllerImpl; @@ -248,15 +247,25 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, * Prudently disable QS and notifications. */ private static final boolean ONLY_CORE_APPS; + /* If true, the device supports freeform window management. + * This affects the status bar UI. */ + private static final boolean FREEFORM_WINDOW_MANAGEMENT; + static { boolean onlyCoreApps; + boolean freeformWindowManagement; try { - onlyCoreApps = IPackageManager.Stub.asInterface(ServiceManager.getService("package")) - .isOnlyCoreApps(); + IPackageManager packageManager = + IPackageManager.Stub.asInterface(ServiceManager.getService("package")); + onlyCoreApps = packageManager.isOnlyCoreApps(); + freeformWindowManagement = packageManager.hasSystemFeature( + PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT); } catch (RemoteException e) { onlyCoreApps = false; + freeformWindowManagement = false; } ONLY_CORE_APPS = onlyCoreApps; + FREEFORM_WINDOW_MANAGEMENT = freeformWindowManagement; } PhoneStatusBarPolicy mIconPolicy; @@ -984,8 +993,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (shelfOverride != DEFAULT) { return shelfOverride != 0; } - // Otherwise default to the build setting. - return mContext.getResources().getBoolean(R.bool.config_enableAppShelf); + // Otherwise default to the platform feature. + return FREEFORM_WINDOW_MANAGEMENT; } private void clearAllNotifications() { @@ -1115,13 +1124,22 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } }; - private long mLastLockToAppLongPress; - private View.OnLongClickListener mLongPressBackRecentsListener = - new View.OnLongClickListener() { + private View.OnLongClickListener mLongPressBackListener = new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { - handleLongPressBackRecents(v); - return true; + return handleLongPressBack(); + } + }; + + private View.OnLongClickListener mRecentsLongClickListener = new View.OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + if (mRecents != null) { + mRecents.dockTopTask(); + return true; + } + return false; } }; @@ -1170,9 +1188,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener); mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener); mNavigationBarView.getRecentsButton().setLongClickable(true); - mNavigationBarView.getRecentsButton().setOnLongClickListener(mLongPressBackRecentsListener); + mNavigationBarView.getRecentsButton().setOnLongClickListener(mRecentsLongClickListener); mNavigationBarView.getBackButton().setLongClickable(true); - mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackRecentsListener); + mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackListener); mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener); mNavigationBarView.getHomeButton().setOnLongClickListener(mLongPressHomeListener); mAssistManager.onConfigurationChanged(); @@ -4048,7 +4066,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private void vibrateForCameraGesture() { // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep. - mVibrator.vibrate(new long[] { 0, 750L }, -1 /* repeat */); + mVibrator.vibrate(new long[]{0, 750L}, -1 /* repeat */); } public void onScreenTurnedOn() { @@ -4057,58 +4075,22 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } /** - * This handles long-press of both back and recents. They are - * handled together to capture them both being long-pressed - * at the same time to exit screen pinning (lock task). - * - * When accessibility mode is on, only a long-press from recents - * is required to exit. - * - * In all other circumstances we try to pass through long-press events - * for Back, so that apps can still use it. Which can be from two things. - * 1) Not currently in screen pinning (lock task). - * 2) Back is long-pressed without recents. + * Handles long press for back button. This exits screen pinning. */ - private void handleLongPressBackRecents(View v) { + private boolean handleLongPressBack() { try { - boolean sendBackLongPress = false; IActivityManager activityManager = ActivityManagerNative.getDefault(); - boolean isAccessiblityEnabled = mAccessibilityManager.isEnabled(); - if (activityManager.isInLockTaskMode() && !isAccessiblityEnabled) { - long time = System.currentTimeMillis(); - // If we recently long-pressed the other button then they were - // long-pressed 'together' - if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) { - activityManager.stopLockTaskModeOnCurrent(); - // When exiting refresh disabled flags. - mNavigationBarView.setDisabledFlags(mDisabled1, true); - } else if ((v.getId() == R.id.back) - && !mNavigationBarView.getRecentsButton().isPressed()) { - // If we aren't pressing recents right now then they presses - // won't be together, so send the standard long-press action. - sendBackLongPress = true; - } - mLastLockToAppLongPress = time; - } else { - // If this is back still need to handle sending the long-press event. - if (v.getId() == R.id.back) { - sendBackLongPress = true; - } else if (isAccessiblityEnabled && activityManager.isInLockTaskMode()) { - // When in accessibility mode a long press that is recents (not back) - // should stop lock task. - activityManager.stopLockTaskModeOnCurrent(); - // When exiting refresh disabled flags. - mNavigationBarView.setDisabledFlags(mDisabled1, true); - } - } - if (sendBackLongPress) { - KeyButtonView keyButtonView = (KeyButtonView) v; - keyButtonView.sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS); - keyButtonView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); + if (activityManager.isInLockTaskMode()) { + activityManager.stopLockTaskModeOnCurrent(); + + // When exiting refresh disabled flags. + mNavigationBarView.setDisabledFlags(mDisabled1, true); + return true; } } catch (RemoteException e) { Log.d(TAG, "Unable to reach activity manager", e); } + return false; } // Recents diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java index 4ce0933e36ec..05e3fd50a9f5 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java @@ -46,6 +46,7 @@ import com.android.systemui.qs.QSPanel; import com.android.systemui.qs.QSTile; import com.android.systemui.qs.QSTile.Host.Callback; import com.android.systemui.qs.QSTile.ResourceIcon; +import com.android.systemui.qs.QSTileBaseView; import com.android.systemui.qs.QSTileView; import com.android.systemui.qs.tiles.IntentTile; import com.android.systemui.statusbar.phone.QSTileHost; @@ -381,7 +382,7 @@ public class QsTuner extends Fragment implements Callback { private static class DraggableTile extends QSTile<QSTile.State> implements DropListener { private String mSpec; - private QSTileView mView; + private QSTileBaseView mView; protected DraggableTile(QSTile.Host host, String tileSpec) { super(host); @@ -390,7 +391,7 @@ public class QsTuner extends Fragment implements Callback { } @Override - public QSTileView createTileView(Context context) { + public QSTileBaseView createTileView(Context context) { mView = super.createTileView(context); return mView; } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 08f0952e2a0a..ed6fc0057c09 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -95,6 +95,7 @@ class AlarmManagerService extends SystemService { static final boolean DEBUG_VALIDATE = localLOGV || false; static final boolean DEBUG_ALARM_CLOCK = localLOGV || false; static final boolean RECORD_ALARMS_IN_HISTORY = true; + static final boolean RECORD_DEVICE_IDLE_ALARMS = false; static final int ALARM_EVENT = 1; static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; @@ -144,6 +145,16 @@ class AlarmManagerService extends SystemService { */ final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray(); + final static class IdleDispatchEntry { + int uid; + String pkg; + String tag; + String op; + long elapsedRealtime; + long argRealtime; + } + final ArrayList<IdleDispatchEntry> mAllowWhileIdleDispatches = new ArrayList(); + /** * Broadcast options to use for FLAG_ALLOW_WHILE_IDLE. */ @@ -178,7 +189,7 @@ class AlarmManagerService extends SystemService { private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; private static final long DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME = DEFAULT_MIN_FUTURITY; - private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 15*60*1000; + private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9*60*1000; private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10*1000; // Minimum futurity of a new alarm @@ -700,6 +711,14 @@ class AlarmManagerService extends SystemService { } void restorePendingWhileIdleAlarmsLocked() { + if (RECORD_DEVICE_IDLE_ALARMS) { + IdleDispatchEntry ent = new IdleDispatchEntry(); + ent.uid = 0; + ent.pkg = "FINISH IDLE"; + ent.elapsedRealtime = SystemClock.elapsedRealtime(); + mAllowWhileIdleDispatches.add(ent); + } + // Bring pending alarms back into the main list. if (mPendingWhileIdleAlarms.size() > 0) { ArrayList<Alarm> alarms = mPendingWhileIdleAlarms; @@ -1006,6 +1025,19 @@ class AlarmManagerService extends SystemService { } } + if (RECORD_DEVICE_IDLE_ALARMS) { + if ((a.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) { + IdleDispatchEntry ent = new IdleDispatchEntry(); + ent.uid = a.uid; + ent.pkg = a.operation.getCreatorPackage(); + ent.tag = a.operation.getTag(""); + ent.op = "SET"; + ent.elapsedRealtime = SystemClock.elapsedRealtime(); + ent.argRealtime = a.whenElapsed; + mAllowWhileIdleDispatches.add(ent); + } + } + int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0) ? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed); if (whichBatch < 0) { @@ -1028,6 +1060,15 @@ class AlarmManagerService extends SystemService { boolean needRebatch = false; if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) { + if (RECORD_DEVICE_IDLE_ALARMS) { + if (mPendingIdleUntil == null) { + IdleDispatchEntry ent = new IdleDispatchEntry(); + ent.uid = 0; + ent.pkg = "START IDLE"; + ent.elapsedRealtime = SystemClock.elapsedRealtime(); + mAllowWhileIdleDispatches.add(ent); + } + } mPendingIdleUntil = a; mConstants.updateAllowWhileIdleMinTimeLocked(); needRebatch = true; @@ -1400,6 +1441,32 @@ class AlarmManagerService extends SystemService { } } + if (RECORD_DEVICE_IDLE_ALARMS) { + pw.println(); + pw.println(" Allow while idle dispatches:"); + for (int i = 0; i < mAllowWhileIdleDispatches.size(); i++) { + IdleDispatchEntry ent = mAllowWhileIdleDispatches.get(i); + pw.print(" "); + TimeUtils.formatDuration(ent.elapsedRealtime, nowELAPSED, pw); + pw.print(": "); + UserHandle.formatUid(pw, ent.uid); + pw.print(":"); + pw.println(ent.pkg); + if (ent.op != null) { + pw.print(" "); + pw.print(ent.op); + pw.print(" / "); + pw.print(ent.tag); + if (ent.argRealtime != 0) { + pw.print(" ("); + TimeUtils.formatDuration(ent.argRealtime, nowELAPSED, pw); + pw.print(")"); + } + pw.println(); + } + } + } + if (WAKEUP_STATS) { pw.println(); pw.println(" Recent Wakeup History:"); @@ -1862,6 +1929,16 @@ class AlarmManagerService extends SystemService { if (alarm.maxWhenElapsed < minTime) { alarm.maxWhenElapsed = minTime; } + if (RECORD_DEVICE_IDLE_ALARMS) { + IdleDispatchEntry ent = new IdleDispatchEntry(); + ent.uid = alarm.uid; + ent.pkg = alarm.operation.getCreatorPackage(); + ent.tag = alarm.operation.getTag(""); + ent.op = "RESCHEDULE"; + ent.elapsedRealtime = nowELAPSED; + ent.argRealtime = lastTime; + mAllowWhileIdleDispatches.add(ent); + } setImplLocked(alarm, true, false); continue; } @@ -2130,6 +2207,15 @@ class AlarmManagerService extends SystemService { if (allowWhileIdle) { // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm. mLastAllowWhileIdleDispatch.put(alarm.uid, nowELAPSED); + if (RECORD_DEVICE_IDLE_ALARMS) { + IdleDispatchEntry ent = new IdleDispatchEntry(); + ent.uid = alarm.uid; + ent.pkg = alarm.operation.getCreatorPackage(); + ent.tag = alarm.operation.getTag(""); + ent.op = "DELIVER"; + ent.elapsedRealtime = nowELAPSED; + mAllowWhileIdleDispatches.add(ent); + } } final BroadcastStats bs = inflight.mBroadcastStats; diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 2eeaec96b581..61fe62fb88a4 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -19,6 +19,8 @@ package com.android.server; import android.database.ContentObserver; import android.os.BatteryStats; +import android.os.ResultReceiver; +import android.os.ShellCommand; import com.android.internal.app.IBatteryStats; import com.android.server.am.BatteryStatsService; import com.android.server.lights.Light; @@ -96,7 +98,6 @@ public final class BatteryService extends SystemService { // discharge stats before the device dies. private int mCriticalBatteryLevel; - private static final int DUMP_MAX_LENGTH = 24 * 1024; private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "--unplugged" }; private static final String DUMPSYS_DATA_PATH = "/data/system/"; @@ -106,6 +107,7 @@ public final class BatteryService extends SystemService { private final Context mContext; private final IBatteryStats mBatteryStats; + BinderService mBinderService; private final Handler mHandler; private final Object mLock = new Object(); @@ -162,7 +164,18 @@ public final class BatteryService extends SystemService { // watch for invalid charger messages if the invalid_charger switch exists if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) { - mInvalidChargerObserver.startObserving( + UEventObserver invalidChargerObserver = new UEventObserver() { + @Override + public void onUEvent(UEvent event) { + final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0; + synchronized (mLock) { + if (mInvalidCharger != invalidCharger) { + mInvalidCharger = invalidCharger; + } + } + } + }; + invalidChargerObserver.startObserving( "DEVPATH=/devices/virtual/switch/invalid_charger"); } } @@ -178,7 +191,8 @@ public final class BatteryService extends SystemService { // Should never happen. } - publishBinderService("battery", new BinderService()); + mBinderService = new BinderService(); + publishBinderService("battery", mBinderService); publishLocalService(BatteryManagerInternal.class, new LocalService()); } @@ -593,7 +607,6 @@ public final class BatteryService extends SystemService { } catch (NumberFormatException e) { Slog.e(TAG, "Invalid DischargeThresholds GService string: " + durationThresholdString + " or " + dischargeThresholdString); - return; } } } @@ -616,27 +629,40 @@ public final class BatteryService extends SystemService { } } - private void dumpInternal(PrintWriter pw, String[] args) { - synchronized (mLock) { - if (args == null || args.length == 0 || "-a".equals(args[0])) { - pw.println("Current Battery Service state:"); - if (mUpdatesStopped) { - pw.println(" (UPDATES STOPPED -- use 'reset' to restart)"); - } - pw.println(" AC powered: " + mBatteryProps.chargerAcOnline); - pw.println(" USB powered: " + mBatteryProps.chargerUsbOnline); - pw.println(" Wireless powered: " + mBatteryProps.chargerWirelessOnline); - pw.println(" Max charging current: " + mBatteryProps.maxChargingCurrent); - pw.println(" status: " + mBatteryProps.batteryStatus); - pw.println(" health: " + mBatteryProps.batteryHealth); - pw.println(" present: " + mBatteryProps.batteryPresent); - pw.println(" level: " + mBatteryProps.batteryLevel); - pw.println(" scale: " + BATTERY_SCALE); - pw.println(" voltage: " + mBatteryProps.batteryVoltage); - pw.println(" temperature: " + mBatteryProps.batteryTemperature); - pw.println(" technology: " + mBatteryProps.batteryTechnology); + class Shell extends ShellCommand { + @Override + public int onCommand(String cmd) { + return onShellCommand(this, cmd); + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + dumpHelp(pw); + } + } + + static void dumpHelp(PrintWriter pw) { + pw.println("Battery service (battery) commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" set [ac|usb|wireless|status|level|invalid] <value>"); + pw.println(" Force a battery property value, freezing battery state."); + pw.println(" unplug"); + pw.println(" Force battery unplugged, freezing battery state."); + pw.println(" reset"); + pw.println(" Unfreeze battery state, returning to current hardware values."); + } - } else if ("unplug".equals(args[0])) { + int onShellCommand(Shell shell, String cmd) { + if (cmd == null) { + return shell.handleDefaultCommands(cmd); + } + PrintWriter pw = shell.getOutPrintWriter(); + switch (cmd) { + case "unplug": { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, null); if (!mUpdatesStopped) { mLastBatteryProps.set(mBatteryProps); } @@ -650,30 +676,50 @@ public final class BatteryService extends SystemService { } finally { Binder.restoreCallingIdentity(ident); } + } break; + case "set": { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, null); + final String key = shell.getNextArg(); + if (key == null) { + pw.println("No property specified"); + return -1; - } else if (args.length == 3 && "set".equals(args[0])) { - String key = args[1]; - String value = args[2]; + } + final String value = shell.getNextArg(); + if (value == null) { + pw.println("No value specified"); + return -1; + + } try { if (!mUpdatesStopped) { mLastBatteryProps.set(mBatteryProps); } boolean update = true; - if ("ac".equals(key)) { - mBatteryProps.chargerAcOnline = Integer.parseInt(value) != 0; - } else if ("usb".equals(key)) { - mBatteryProps.chargerUsbOnline = Integer.parseInt(value) != 0; - } else if ("wireless".equals(key)) { - mBatteryProps.chargerWirelessOnline = Integer.parseInt(value) != 0; - } else if ("status".equals(key)) { - mBatteryProps.batteryStatus = Integer.parseInt(value); - } else if ("level".equals(key)) { - mBatteryProps.batteryLevel = Integer.parseInt(value); - } else if ("invalid".equals(key)) { - mInvalidCharger = Integer.parseInt(value); - } else { - pw.println("Unknown set option: " + key); - update = false; + switch (key) { + case "ac": + mBatteryProps.chargerAcOnline = Integer.parseInt(value) != 0; + break; + case "usb": + mBatteryProps.chargerUsbOnline = Integer.parseInt(value) != 0; + break; + case "wireless": + mBatteryProps.chargerWirelessOnline = Integer.parseInt(value) != 0; + break; + case "status": + mBatteryProps.batteryStatus = Integer.parseInt(value); + break; + case "level": + mBatteryProps.batteryLevel = Integer.parseInt(value); + break; + case "invalid": + mInvalidCharger = Integer.parseInt(value); + break; + default: + pw.println("Unknown set option: " + key); + update = false; + break; } if (update) { long ident = Binder.clearCallingIdentity(); @@ -686,9 +732,12 @@ public final class BatteryService extends SystemService { } } catch (NumberFormatException ex) { pw.println("Bad value: " + value); + return -1; } - - } else if (args.length == 1 && "reset".equals(args[0])) { + } break; + case "reset": { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, null); long ident = Binder.clearCallingIdentity(); try { if (mUpdatesStopped) { @@ -699,26 +748,38 @@ public final class BatteryService extends SystemService { } finally { Binder.restoreCallingIdentity(ident); } - } else { - pw.println("Dump current battery state, or:"); - pw.println(" set [ac|usb|wireless|status|level|invalid] <value>"); - pw.println(" unplug"); - pw.println(" reset"); - } + } break; + default: + return shell.handleDefaultCommands(cmd); } + return 0; } - private final UEventObserver mInvalidChargerObserver = new UEventObserver() { - @Override - public void onUEvent(UEventObserver.UEvent event) { - final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0; - synchronized (mLock) { - if (mInvalidCharger != invalidCharger) { - mInvalidCharger = invalidCharger; + private void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) { + synchronized (mLock) { + if (args == null || args.length == 0 || "-a".equals(args[0])) { + pw.println("Current Battery Service state:"); + if (mUpdatesStopped) { + pw.println(" (UPDATES STOPPED -- use 'reset' to restart)"); } + pw.println(" AC powered: " + mBatteryProps.chargerAcOnline); + pw.println(" USB powered: " + mBatteryProps.chargerUsbOnline); + pw.println(" Wireless powered: " + mBatteryProps.chargerWirelessOnline); + pw.println(" Max charging current: " + mBatteryProps.maxChargingCurrent); + pw.println(" status: " + mBatteryProps.batteryStatus); + pw.println(" health: " + mBatteryProps.batteryHealth); + pw.println(" present: " + mBatteryProps.batteryPresent); + pw.println(" level: " + mBatteryProps.batteryLevel); + pw.println(" scale: " + BATTERY_SCALE); + pw.println(" voltage: " + mBatteryProps.batteryVoltage); + pw.println(" temperature: " + mBatteryProps.batteryTemperature); + pw.println(" technology: " + mBatteryProps.batteryTechnology); + } else { + Shell shell = new Shell(); + shell.exec(mBinderService, null, fd, null, args, new ResultReceiver(null)); } } - }; + } private final class Led { private final Light mBatteryLight; @@ -776,8 +837,7 @@ public final class BatteryService extends SystemService { } private final class BatteryListener extends IBatteryPropertiesListener.Stub { - @Override - public void batteryPropertiesChanged(BatteryProperties props) { + @Override public void batteryPropertiesChanged(BatteryProperties props) { final long identity = Binder.clearCallingIdentity(); try { BatteryService.this.update(props); @@ -788,8 +848,7 @@ public final class BatteryService extends SystemService { } private final class BinderService extends Binder { - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -799,7 +858,12 @@ public final class BatteryService extends SystemService { return; } - dumpInternal(pw, args); + dumpInternal(fd, pw, args); + } + + @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, + FileDescriptor err, String[] args, ResultReceiver resultReceiver) { + (new Shell()).exec(this, in, out, err, args, resultReceiver); } } diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 496e4dfda6e0..927b995e5cb0 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -1500,7 +1500,7 @@ public class DeviceIdleController extends SystemService mState = STATE_SENSING; if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING."); EventLogTags.writeDeviceIdle(mState, "step"); - cancelAlarmLocked(); + scheduleAlarmLocked(mConstants.SENSING_TIMEOUT, false); cancelLocatingLocked(); mAnyMotionDetector.checkForAnyMotion(); mNotMoving = false; diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 6b346123e94a..1ff13b293add 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -3824,7 +3824,6 @@ public class AccountManagerService boolean isPermitted = isPermitted(opPackageName, callingUid, Manifest.permission.GET_ACCOUNTS, Manifest.permission.GET_ACCOUNTS_PRIVILEGED); - Log.i(TAG, String.format("getTypesVisibleToCaller: isPermitted? %s", isPermitted)); return getTypesForCaller(callingUid, userId, isPermitted); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 983475734a25..d5ee5e846277 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -69,12 +69,14 @@ import android.graphics.Rect; import android.os.BatteryStats; import android.os.PersistableBundle; import android.os.PowerManager; +import android.os.ResultReceiver; import android.os.Trace; import android.os.TransactionTooLargeException; import android.os.WorkSource; import android.os.storage.IMountService; import android.os.storage.MountServiceInternal; import android.os.storage.StorageManager; +import android.provider.Settings.Global; import android.service.voice.IVoiceInteractionSession; import android.service.voice.VoiceInteractionSession; import android.util.ArrayMap; @@ -1189,6 +1191,7 @@ public final class ActivityManagerService extends ActivityManagerNative String mOrigDebugApp = null; boolean mOrigWaitForDebugger = false; boolean mAlwaysFinishActivities = false; + boolean mForceResizableActivites; IActivityController mController = null; String mProfileApp = null; ProcessRecord mProfileProc = null; @@ -3842,8 +3845,8 @@ public final class ActivityManagerService extends ActivityManagerNative Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle options) { return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, - resultWho, requestCode, startFlags, profilerInfo, options, - UserHandle.getCallingUserId()); + resultWho, requestCode, startFlags, profilerInfo, options, + UserHandle.getCallingUserId()); } @Override @@ -4150,7 +4153,12 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } - return startActivityFromRecentsInner(taskId, launchStackId, options); + final long origId = Binder.clearCallingIdentity(); + try { + return startActivityFromRecentsInner(taskId, launchStackId, options); + } finally { + Binder.restoreCallingIdentity(origId); + } } final int startActivityFromRecentsInner(int taskId, int launchStackId, Bundle options) { @@ -4172,8 +4180,12 @@ public final class ActivityManagerService extends ActivityManagerNative } if (launchStackId != INVALID_STACK_ID && task.stack.mStackId != launchStackId) { - mStackSupervisor.moveTaskToStackUncheckedLocked( - task, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents"); + if (launchStackId == DOCKED_STACK_ID && options != null) { + ActivityOptions activityOptions = new ActivityOptions(options); + mWindowManager.setDockedStackCreateMode(activityOptions.getDockCreateMode()); + } + mStackSupervisor.moveTaskToStackLocked( + taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents"); } if (task.getRootActivity() != null) { @@ -9027,7 +9039,8 @@ public final class ActivityManagerService extends ActivityManagerNative } if (DEBUG_STACK) Slog.d(TAG_STACK, "moveActivityToStack: moving r=" + r + " to stackId=" + stackId); - mStackSupervisor.moveTaskToStackLocked(r.task.taskId, stackId, ON_TOP, !FORCE_FOCUS); + mStackSupervisor.moveTaskToStackLocked(r.task.taskId, stackId, ON_TOP, !FORCE_FOCUS, + "moveActivityToStack"); } finally { Binder.restoreCallingIdentity(ident); } @@ -9047,7 +9060,8 @@ public final class ActivityManagerService extends ActivityManagerNative try { if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId + " to stackId=" + stackId + " toTop=" + toTop); - mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop, !FORCE_FOCUS); + mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop, !FORCE_FOCUS, + "moveTaskToStack"); } finally { Binder.restoreCallingIdentity(ident); } @@ -9076,7 +9090,7 @@ public final class ActivityManagerService extends ActivityManagerNative + " to createMode=" + createMode + " toTop=" + toTop); mWindowManager.setDockedStackCreateMode(createMode); mStackSupervisor.moveTaskToStackLocked( - taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS); + taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS, "moveTaskToDockedStack"); } finally { Binder.restoreCallingIdentity(ident); } @@ -11638,14 +11652,17 @@ public final class ActivityManagerService extends ActivityManagerNative private void retrieveSettings() { final ContentResolver resolver = mContext.getContentResolver(); - String debugApp = Settings.Global.getString( - resolver, Settings.Global.DEBUG_APP); + String debugApp = Settings.Global.getString(resolver, Settings.Global.DEBUG_APP); boolean waitForDebugger = Settings.Global.getInt( resolver, Settings.Global.WAIT_FOR_DEBUGGER, 0) != 0; boolean alwaysFinishActivities = Settings.Global.getInt( resolver, Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0; boolean forceRtl = Settings.Global.getInt( resolver, Settings.Global.DEVELOPMENT_FORCE_RTL, 0) != 0; + int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0; + boolean forceResizable = Settings.Global.getInt( + resolver, Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, + defaultForceResizable) != 0; // Transfer any global setting for forcing RTL layout, into a System Property SystemProperties.set(Settings.Global.DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0"); @@ -11660,6 +11677,7 @@ public final class ActivityManagerService extends ActivityManagerNative mDebugApp = mOrigDebugApp = debugApp; mWaitForDebugger = mOrigWaitForDebugger = waitForDebugger; mAlwaysFinishActivities = alwaysFinishActivities; + mForceResizableActivites = forceResizable; // This happens before any activities are started, so we can // change mConfiguration in-place. updateConfigurationLocked(configuration, null, true); @@ -12089,7 +12107,8 @@ public final class ActivityManagerService extends ActivityManagerNative AppsQueryHelper queryHelper = new AppsQueryHelper(mContext); Set<String> enableApps = new HashSet<>(); enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_NON_LAUNCHABLE_APPS - | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM, + | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM + | AppsQueryHelper.GET_DEFAULT_IMES, /* systemAppsOnly */ true, UserHandle.SYSTEM)); ArraySet<String> wlApps = SystemConfig.getInstance().getSystemUserWhitelistedApps(); enableApps.addAll(wlApps); @@ -13065,6 +13084,13 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, + FileDescriptor err, String[] args, ResultReceiver resultReceiver) { + (new ActivityManagerShellCommand(this, false)).exec( + this, in, out, err, args, resultReceiver); + } + + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (checkCallingPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -13101,34 +13127,7 @@ public final class ActivityManagerService extends ActivityManagerNative } dumpClient = true; } else if ("-h".equals(opt)) { - pw.println("Activity manager dump options:"); - pw.println(" [-a] [-c] [-p package] [-h] [cmd] ..."); - pw.println(" cmd may be one of:"); - pw.println(" a[ctivities]: activity stack state"); - pw.println(" r[recents]: recent activities state"); - pw.println(" b[roadcasts] [PACKAGE_NAME] [history [-s]]: broadcast state"); - pw.println(" i[ntents] [PACKAGE_NAME]: pending intent state"); - pw.println(" p[rocesses] [PACKAGE_NAME]: process state"); - pw.println(" o[om]: out of memory management"); - pw.println(" perm[issions]: URI permission grant state"); - pw.println(" prov[iders] [COMP_SPEC ...]: content provider state"); - pw.println(" provider [COMP_SPEC]: provider client-side state"); - pw.println(" s[ervices] [COMP_SPEC ...]: service state"); - pw.println(" as[sociations]: tracked app associations"); - pw.println(" service [COMP_SPEC]: service client-side state"); - pw.println(" package [PACKAGE_NAME]: all state related to given package"); - pw.println(" all: dump all activities"); - pw.println(" top: dump the top activity"); - pw.println(" write: write all pending state to storage"); - pw.println(" track-associations: enable association tracking"); - pw.println(" untrack-associations: disable and clear association tracking"); - pw.println(" cmd may also be a COMP_SPEC to dump activities."); - pw.println(" COMP_SPEC may be a component name (com.foo/.myApp),"); - pw.println(" a partial substring in a component name, a"); - pw.println(" hex object identifier."); - pw.println(" -a: include all available server state."); - pw.println(" -c: include client state."); - pw.println(" -p: limit output to given package."); + ActivityManagerShellCommand.dumpHelp(pw, true); return; } else { pw.println("Unknown argument: " + opt + "; use -h for help"); @@ -13265,36 +13264,15 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { mServices.dumpServicesLocked(fd, pw, args, opti, true, dumpClient, dumpPackage); } - } else if ("write".equals(cmd)) { - mTaskPersister.flush(); - pw.println("All tasks persisted."); - return; - } else if ("track-associations".equals(cmd)) { - synchronized (this) { - if (!mTrackingAssociations) { - mTrackingAssociations = true; - pw.println("Association tracking started."); - } else { - pw.println("Association tracking already enabled."); - } - } - return; - } else if ("untrack-associations".equals(cmd)) { - synchronized (this) { - if (mTrackingAssociations) { - mTrackingAssociations = false; - mAssociations.clear(); - pw.println("Association tracking stopped."); - } else { - pw.println("Association tracking not running."); - } - } - return; } else { // Dumping a single activity? if (!dumpActivity(fd, pw, cmd, args, opti, dumpAll)) { - pw.println("Bad activity command, or no activities match: " + cmd); - pw.println("Use -h for help."); + ActivityManagerShellCommand shell = new ActivityManagerShellCommand(this, true); + int res = shell.exec(this, null, fd, null, args, new ResultReceiver(null)); + if (res < 0) { + pw.println("Bad activity command, or no activities match: " + cmd); + pw.println("Use -h for help."); + } } } if (!more) { @@ -13545,17 +13523,34 @@ public final class ActivityManagerService extends ActivityManagerNative } if (mActiveUids.size() > 0) { - if (needSep) { - pw.println(); + boolean printed = false; + int whichAppId = -1; + if (dumpPackage != null) { + try { + ApplicationInfo info = mContext.getPackageManager().getApplicationInfo( + dumpPackage, 0); + whichAppId = UserHandle.getAppId(info.uid); + } catch (NameNotFoundException e) { + e.printStackTrace(); + } } - pw.println(" UID states:"); for (int i=0; i<mActiveUids.size(); i++) { UidRecord uidRec = mActiveUids.valueAt(i); + if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) { + continue; + } + if (!printed) { + printed = true; + if (needSep) { + pw.println(); + } + pw.println(" UID states:"); + needSep = true; + printedAnything = true; + } pw.print(" UID "); UserHandle.formatUid(pw, uidRec.uid); pw.print(": "); pw.println(uidRec); } - needSep = true; - printedAnything = true; } if (mLruProcesses.size() > 0) { diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java new file mode 100644 index 000000000000..d1e7e85b6d51 --- /dev/null +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2015 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.server.am; + +import android.app.IActivityManager; +import android.os.RemoteException; +import android.os.ShellCommand; +import android.os.UserHandle; + +import java.io.PrintWriter; + +class ActivityManagerShellCommand extends ShellCommand { + // IPC interface to activity manager -- don't need to do additional security checks. + final IActivityManager mInterface; + + // Internal service impl -- must perform security checks before touching. + final ActivityManagerService mInternal; + + final boolean mDumping; + + ActivityManagerShellCommand(ActivityManagerService service, boolean dumping) { + mInterface = service; + mInternal = service; + mDumping = dumping; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + PrintWriter pw = getOutPrintWriter(); + try { + switch (cmd) { + case "force-stop": + return runForceStop(pw); + case "kill": + return runKill(pw); + case "kill-all": + return runKillAll(pw); + case "write": + return runWrite(pw); + case "track-associations": + return runTrackAssociations(pw); + case "untrack-associations": + return runUntrackAssociations(pw); + default: + return handleDefaultCommands(cmd); + } + } catch (RemoteException e) { + pw.println("Remote exception: " + e); + } + return -1; + } + + int runForceStop(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_ALL; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = parseUserArg(getNextArgRequired()); + } else { + pw.println("Error: Unknown option: " + opt); + return -1; + } + } + mInterface.forceStopPackage(getNextArgRequired(), userId); + return 0; + } + + int runKill(PrintWriter pw) throws RemoteException { + int userId = UserHandle.USER_ALL; + + String opt; + while ((opt=getNextOption()) != null) { + if (opt.equals("--user")) { + userId = parseUserArg(getNextArgRequired()); + } else { + pw.println("Error: Unknown option: " + opt); + return -1; + } + } + mInterface.killBackgroundProcesses(getNextArgRequired(), userId); + return 0; + } + + int runKillAll(PrintWriter pw) throws RemoteException { + mInterface.killAllBackgroundProcesses(); + return 0; + } + + int runWrite(PrintWriter pw) { + mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER, + "registerUidObserver()"); + mInternal.mTaskPersister.flush(); + pw.println("All tasks persisted."); + return 0; + } + + int runTrackAssociations(PrintWriter pw) { + mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER, + "registerUidObserver()"); + synchronized (mInternal) { + if (!mInternal.mTrackingAssociations) { + mInternal.mTrackingAssociations = true; + pw.println("Association tracking started."); + } else { + pw.println("Association tracking already enabled."); + } + } + return 0; + } + + int runUntrackAssociations(PrintWriter pw) { + mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER, + "registerUidObserver()"); + synchronized (mInternal) { + if (mInternal.mTrackingAssociations) { + mInternal.mTrackingAssociations = false; + mInternal.mAssociations.clear(); + pw.println("Association tracking stopped."); + } else { + pw.println("Association tracking not running."); + } + } + return 0; + } + + int parseUserArg(String arg) { + int userId; + if ("all".equals(arg)) { + userId = UserHandle.USER_ALL; + } else if ("current".equals(arg) || "cur".equals(arg)) { + userId = UserHandle.USER_CURRENT; + } else { + try { + userId = Integer.parseInt(arg); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Bad user number: " + arg); + } + } + return userId; + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + dumpHelp(pw, mDumping); + } + + static void dumpHelp(PrintWriter pw, boolean dumping) { + if (dumping) { + pw.println("Activity manager dump options:"); + pw.println(" [-a] [-c] [-p PACKAGE] [-h] [WHAT] ..."); + pw.println(" WHAT may be one of:"); + pw.println(" a[ctivities]: activity stack state"); + pw.println(" r[recents]: recent activities state"); + pw.println(" b[roadcasts] [PACKAGE_NAME] [history [-s]]: broadcast state"); + pw.println(" i[ntents] [PACKAGE_NAME]: pending intent state"); + pw.println(" p[rocesses] [PACKAGE_NAME]: process state"); + pw.println(" o[om]: out of memory management"); + pw.println(" perm[issions]: URI permission grant state"); + pw.println(" prov[iders] [COMP_SPEC ...]: content provider state"); + pw.println(" provider [COMP_SPEC]: provider client-side state"); + pw.println(" s[ervices] [COMP_SPEC ...]: service state"); + pw.println(" as[sociations]: tracked app associations"); + pw.println(" service [COMP_SPEC]: service client-side state"); + pw.println(" package [PACKAGE_NAME]: all state related to given package"); + pw.println(" all: dump all activities"); + pw.println(" top: dump the top activity"); + pw.println(" WHAT may also be a COMP_SPEC to dump activities."); + pw.println(" COMP_SPEC may be a component name (com.foo/.myApp),"); + pw.println(" a partial substring in a component name, a"); + pw.println(" hex object identifier."); + pw.println(" -a: include all available server state."); + pw.println(" -c: include client state."); + pw.println(" -p: limit output to given package."); + } else { + pw.println("Activity manager (activity) commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" force-stop [--user <USER_ID> | all | current] <PACKAGE>"); + pw.println(" Complete stop the given application package."); + pw.println(" kill [--user <USER_ID> | all | current] <PACKAGE>"); + pw.println(" Kill all processes associated with the given application."); + pw.println(" kill-all"); + pw.println(" Kill all processes that are safe to kill (cached, etc)"); + pw.println(" write"); + pw.println(" Write all pending state to storage."); + pw.println(" track-associations"); + pw.println(" Enable association tracking."); + pw.println(" untrack-associations"); + pw.println(" Disable and clear association tracking."); + } + } +} diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 4671cb0a94e3..8061a9271cb4 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -53,6 +53,7 @@ import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.util.TimeUtils; +import android.view.AppTransitionAnimationSpec; import android.view.IApplicationToken; import android.view.WindowManager; @@ -863,17 +864,24 @@ final class ActivityRecord { break; case ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP: case ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_DOWN: - service.mWindowManager.overridePendingAppTransitionAspectScaledThumb( - pendingOptions.getThumbnail(), - pendingOptions.getStartX(), pendingOptions.getStartY(), - pendingOptions.getWidth(), pendingOptions.getHeight(), - pendingOptions.getOnAnimationStartListener(), - (animationType == ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP)); - if (intent.getSourceBounds() == null) { - intent.setSourceBounds(new Rect(pendingOptions.getStartX(), - pendingOptions.getStartY(), - pendingOptions.getStartX() + pendingOptions.getWidth(), - pendingOptions.getStartY() + pendingOptions.getHeight())); + final AppTransitionAnimationSpec[] specs = pendingOptions.getAnimSpecs(); + if (animationType == ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_DOWN + && specs != null) { + service.mWindowManager.overridePendingAppTransitionMultiThumb( + specs, pendingOptions.getOnAnimationStartListener(), false); + } else { + service.mWindowManager.overridePendingAppTransitionAspectScaledThumb( + pendingOptions.getThumbnail(), + pendingOptions.getStartX(), pendingOptions.getStartY(), + pendingOptions.getWidth(), pendingOptions.getHeight(), + pendingOptions.getOnAnimationStartListener(), + (animationType == ActivityOptions.ANIM_THUMBNAIL_ASPECT_SCALE_UP)); + if (intent.getSourceBounds() == null) { + intent.setSourceBounds(new Rect(pendingOptions.getStartX(), + pendingOptions.getStartY(), + pendingOptions.getStartX() + pendingOptions.getWidth(), + pendingOptions.getStartY() + pendingOptions.getHeight())); + } } break; default: diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 99539e4647f9..2104830c6666 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -21,6 +21,7 @@ import static android.app.ActivityManager.FIRST_STATIC_STACK_ID; import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.HOME_STACK_ID; +import static android.app.ActivityManager.INVALID_STACK_ID; import static android.app.ActivityManager.LAST_STATIC_STACK_ID; import static android.app.ActivityManager.PINNED_STACK_ID; import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; @@ -529,26 +530,36 @@ final class ActivityStack { * @param task If non-null, the task will be moved to the top of the stack. * */ void moveToFront(String reason, TaskRecord task) { - if (isAttached()) { - final ActivityStack lastFocusStack = mStacks.get(mStacks.size() - 1); - // Need to move this stack to the front before calling - // {@link ActivityStackSupervisor#setFocusStack} below. - mStacks.remove(this); - mStacks.add(this); - - // TODO(multi-display): Needs to also work if focus is moving to the non-home display. - if (isOnHomeDisplay()) { - mStackSupervisor.setFocusStack(reason, lastFocusStack); - } - if (task != null) { - insertTaskAtTop(task, null); - } else { - task = topTask(); - } - if (task != null) { - mWindowManager.moveTaskToTop(task.taskId); + if (!isAttached()) { + return; + } + + mStacks.remove(this); + int addIndex = mStacks.size(); + + if (addIndex > 0) { + final ActivityStack topStack = mStacks.get(addIndex - 1); + if (topStack.mStackId == PINNED_STACK_ID && topStack != this) { + // The pinned stack is always the top most stack (always-on-top). + // So, stack is moved just below the pinned stack. + addIndex--; } } + + mStacks.add(addIndex, this); + + // TODO(multi-display): Needs to also work if focus is moving to the non-home display. + if (isOnHomeDisplay()) { + mStackSupervisor.setFocusStack(reason, this); + } + if (task != null) { + insertTaskAtTop(task, null); + } else { + task = topTask(); + } + if (task != null) { + mWindowManager.moveTaskToTop(task.taskId); + } } final boolean isAttached() { @@ -1303,12 +1314,7 @@ final class ActivityStack { return false; } - if (mStackSupervisor.isFrontStack(this)) { - return true; - } - - if (mStackId == PINNED_STACK_ID) { - // Pinned stack is always visible if it exist. + if (mStackSupervisor.isFrontStack(this) || mStackSupervisor.isFocusedStack(this)) { return true; } @@ -2040,7 +2046,7 @@ final class ActivityStack { // Have the window manager re-evaluate the orientation of // the screen based on the new activity order. boolean notUpdated = true; - if (mStackSupervisor.isFrontStack(this)) { + if (mStackSupervisor.isFocusedStack(this)) { Configuration config = mWindowManager.updateOrientationFromAppTokens( mService.mConfiguration, next.mayFreezeScreenLocked(next.app) ? next.appToken : null); @@ -2787,7 +2793,7 @@ final class ActivityStack { } private void adjustFocusedActivityLocked(ActivityRecord r, String reason) { - if (mStackSupervisor.isFrontStack(this) && mService.mFocusedActivity == r) { + if (mStackSupervisor.isFocusedStack(this) && mService.mFocusedActivity == r) { ActivityRecord next = topRunningActivityLocked(); final String myReason = reason + " adjustFocus"; if (next != r) { @@ -3394,7 +3400,7 @@ final class ActivityStack { if (task != null && task.removeActivity(r)) { if (DEBUG_STACK) Slog.i(TAG_STACK, "removeActivityFromHistoryLocked: last activity removed from " + this); - if (mStackSupervisor.isFrontStack(this) && task == topTask() && + if (mStackSupervisor.isFocusedStack(this) && task == topTask() && task.isOverHomeStack()) { mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo(), reason); } @@ -4375,6 +4381,7 @@ final class ActivityStack { RunningTaskInfo ci = new RunningTaskInfo(); ci.id = task.taskId; + ci.stackId = mStackId; ci.baseActivity = r.intent.getComponent(); ci.topActivity = top.intent.getComponent(); ci.lastActiveTime = task.lastActiveTime; @@ -4567,7 +4574,7 @@ final class ActivityStack { if (mTaskHistory.isEmpty()) { if (DEBUG_STACK) Slog.i(TAG_STACK, "removeTask: removing stack=" + this); // We only need to adjust focused stack if this stack is in focus. - if (isOnHomeDisplay() && mStackSupervisor.isFrontStack(this)) { + if (isOnHomeDisplay() && mStackSupervisor.isFocusedStack(this)) { String myReason = reason + " leftTaskHistoryEmpty"; if (mFullscreen || !adjustFocusToNextVisibleStackLocked(null, myReason)) { mStackSupervisor.moveHomeStackToFront(myReason); @@ -4675,7 +4682,7 @@ final class ActivityStack { return; } - final boolean wasFocused = mStackSupervisor.isFrontStack(prevStack) + final boolean wasFocused = mStackSupervisor.isFocusedStack(prevStack) && (mStackSupervisor.topRunningActivityLocked() == r); final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 98cfc7cbbae6..1cd71a1cb61a 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -458,10 +458,7 @@ public final class ActivityStackSupervisor implements DisplayListener { return mLastFocusedStack; } - /** Top of all visible stacks is/should always be equal to the focused stack. - * Use {@link ActivityStack#isStackVisibleLocked} to determine if a specific - * stack is visible or not. */ - boolean isFrontStack(ActivityStack stack) { + boolean isFocusedStack(ActivityStack stack) { if (stack == null) { return false; } @@ -473,18 +470,22 @@ public final class ActivityStackSupervisor implements DisplayListener { return stack == mFocusedStack; } - void setFocusStack(String reason, ActivityStack lastFocusedStack) { - ArrayList<ActivityStack> stacks = mHomeStack.mStacks; - final int topNdx = stacks.size() - 1; - if (topNdx <= 0) { - return; + /** The top most stack. */ + boolean isFrontStack(ActivityStack stack) { + if (stack == null) { + return false; } - final ActivityStack topStack = stacks.get(topNdx); - mFocusedStack = topStack; - if (lastFocusedStack != null) { - mLastFocusedStack = lastFocusedStack; + final ActivityRecord parent = stack.mActivityContainer.mParentActivity; + if (parent != null) { + stack = parent.task.stack; } + return stack == mHomeStack.mStacks.get((mHomeStack.mStacks.size() - 1)); + } + + void setFocusStack(String reason, ActivityStack focusedStack) { + mLastFocusedStack = mFocusedStack; + mFocusedStack = focusedStack; EventLogTags.writeAmFocusedStack( mCurrentUser, mFocusedStack == null ? -1 : mFocusedStack.getStackId(), @@ -642,7 +643,7 @@ public final class ActivityStackSupervisor implements DisplayListener { ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); - if (!isFrontStack(stack)) { + if (!isFocusedStack(stack)) { continue; } ActivityRecord hr = stack.topRunningActivityLocked(); @@ -673,7 +674,7 @@ public final class ActivityStackSupervisor implements DisplayListener { ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); - if (!isFrontStack(stack) || stack.numActivities() == 0) { + if (!isFocusedStack(stack) || stack.numActivities() == 0) { continue; } final ActivityRecord resumedActivity = stack.mResumedActivity; @@ -692,7 +693,7 @@ public final class ActivityStackSupervisor implements DisplayListener { ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); - if (isFrontStack(stack)) { + if (isFocusedStack(stack)) { final ActivityRecord r = stack.mResumedActivity; if (r != null && r.state != RESUMED) { return false; @@ -737,7 +738,7 @@ public final class ActivityStackSupervisor implements DisplayListener { ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); - if (!isFrontStack(stack) && stack.mResumedActivity != null) { + if (!isFocusedStack(stack) && stack.mResumedActivity != null) { if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack + " mResumedActivity=" + stack.mResumedActivity); someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming, @@ -1374,7 +1375,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // launching the initial activity (that is, home), so that it can have // a chance to initialize itself while in the background, making the // switch back to it faster and look better. - if (isFrontStack(stack)) { + if (isFocusedStack(stack)) { mService.startSetupActivityLocked(); } @@ -2621,7 +2622,7 @@ public final class ActivityStackSupervisor implements DisplayListener { r.idle = true; //Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout); - if (isFrontStack(r.task.stack) || fromTimeout) { + if (isFocusedStack(r.task.stack) || fromTimeout) { booting = checkFinishBootingLocked(); } } @@ -2749,8 +2750,7 @@ public final class ActivityStackSupervisor implements DisplayListener { boolean didSomething = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; - final int numStacks = stacks.size(); - for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); if (stack.finishDisabledPackageActivitiesLocked( packageName, filterByClasses, doit, evenPersistent, userId)) { @@ -2774,7 +2774,7 @@ public final class ActivityStackSupervisor implements DisplayListener { final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); - if (isFrontStack(stack)) { + if (isFocusedStack(stack)) { if (stack.mResumedActivity != null) { fgApp = stack.mResumedActivity.app; } else if (stack.mPausingActivity != null) { @@ -2806,7 +2806,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } // Do targetStack first. boolean result = false; - if (isFrontStack(targetStack)) { + if (isFocusedStack(targetStack)) { result = targetStack.resumeTopActivityLocked(target, targetOptions); } @@ -2818,7 +2818,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // Already started above. continue; } - if (isFrontStack(stack)) { + if (isFocusedStack(stack)) { stack.resumeTopActivityLocked(null); } } @@ -2876,13 +2876,16 @@ public final class ActivityStackSupervisor implements DisplayListener { } if (stackId != task.stack.mStackId) { - moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, FORCE_FOCUS, reason); - } else { - task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options, - task.getTopActivity() == null ? null : task.getTopActivity().appTimeTracker, - reason); + moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, reason); + + // moveTaskToStackUncheckedLocked() should already placed the task on top, + // still need moveTaskToFrontLocked() below for any transition settings. } + final ActivityRecord r = task.getTopActivity(); + task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options, + r == null ? null : r.appTimeTracker, reason); + if (DEBUG_STACK) Slog.d(TAG_STACK, "findTaskToMoveToFront: moved to front of stack=" + task.stack); } @@ -2989,84 +2992,87 @@ public final class ActivityStackSupervisor implements DisplayListener { } Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId); - - ActivityRecord r = stack.topRunningActivityLocked(); - - mTmpBounds.clear(); - mTmpConfigs.clear(); - ArrayList<TaskRecord> tasks = stack.getAllTasks(); - for (int i = tasks.size() - 1; i >= 0; i--) { - TaskRecord task = tasks.get(i); - if (task.mResizeable) { - if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) { - // For freeform stack we don't adjust the size of the tasks to match that - // of the stack, but we do try to make sure the tasks are still contained - // with the bounds of the stack. - tempRect2.set(task.mBounds); - fitWithinBounds(tempRect2, bounds); - task.updateOverrideConfiguration(tempRect2); - } else { - task.updateOverrideConfiguration(bounds); + mWindowManager.deferSurfaceLayout(); + try { + ActivityRecord r = stack.topRunningActivityLocked(); + + mTmpBounds.clear(); + mTmpConfigs.clear(); + ArrayList<TaskRecord> tasks = stack.getAllTasks(); + for (int i = tasks.size() - 1; i >= 0; i--) { + TaskRecord task = tasks.get(i); + if (task.mResizeable) { + if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) { + // For freeform stack we don't adjust the size of the tasks to match that + // of the stack, but we do try to make sure the tasks are still contained + // with the bounds of the stack. + tempRect2.set(task.mBounds); + fitWithinBounds(tempRect2, bounds); + task.updateOverrideConfiguration(tempRect2); + } else { + task.updateOverrideConfiguration(bounds); + } } - } - mTmpConfigs.put(task.taskId, task.mOverrideConfig); - mTmpBounds.put(task.taskId, task.mBounds); - } - stack.mFullscreen = mWindowManager.resizeStack(stackId, bounds, mTmpConfigs, mTmpBounds); - if (stack.mStackId == DOCKED_STACK_ID) { - // Dock stack funness...Yay! - if (stack.mFullscreen) { - // The dock stack went fullscreen which is kinda like dismissing it. - // In this case we make all other static stacks fullscreen and move all - // docked stack tasks to the fullscreen stack. - for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) { - if (i != DOCKED_STACK_ID && getStack(i) != null) { - resizeStackLocked(i, null, preserveWindows, true); + mTmpConfigs.put(task.taskId, task.mOverrideConfig); + mTmpBounds.put(task.taskId, task.mBounds); + } + stack.mFullscreen = mWindowManager.resizeStack(stackId, bounds, mTmpConfigs, mTmpBounds); + if (stack.mStackId == DOCKED_STACK_ID) { + // Dock stack funness...Yay! + if (stack.mFullscreen) { + // The dock stack went fullscreen which is kinda like dismissing it. + // In this case we make all other static stacks fullscreen and move all + // docked stack tasks to the fullscreen stack. + for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) { + if (i != DOCKED_STACK_ID && getStack(i) != null) { + resizeStackLocked(i, null, preserveWindows, true); + } } - } - final int count = tasks.size(); - for (int i = 0; i < count; i++) { - moveTaskToStackLocked(tasks.get(i).taskId, - FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS); - } + final int count = tasks.size(); + for (int i = 0; i < count; i++) { + moveTaskToStackLocked(tasks.get(i).taskId, + FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS, "resizeStack"); + } - // stack shouldn't contain anymore activities, so nothing to resume. - r = null; - } else { - // Docked stacks occupy a dedicated region on screen so the size of all other - // static stacks need to be adjusted so they don't overlap with the docked stack. - // We get the bounds to use from window manager which has been adjusted for any - // screen controls and is also the same for all stacks. - mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect); - - for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) { - if (i != DOCKED_STACK_ID) { - ActivityStack otherStack = getStack(i); - if (otherStack != null) { - resizeStackLocked(i, tempRect, PRESERVE_WINDOWS, true); + // stack shouldn't contain anymore activities, so nothing to resume. + r = null; + } else { + // Docked stacks occupy a dedicated region on screen so the size of all other + // static stacks need to be adjusted so they don't overlap with the docked stack. + // We get the bounds to use from window manager which has been adjusted for any + // screen controls and is also the same for all stacks. + mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect); + + for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) { + if (i != DOCKED_STACK_ID) { + ActivityStack otherStack = getStack(i); + if (otherStack != null) { + resizeStackLocked(i, tempRect, PRESERVE_WINDOWS, true); + } } } } + // Since we are resizing the stack, all other operations should strive to preserve + // windows. + preserveWindows = true; } - // Since we are resizing the stack, all other operations should strive to preserve - // windows. - preserveWindows = true; - } - stack.setBounds(bounds); + stack.setBounds(bounds); - if (r != null) { - final boolean updated = stack.ensureActivityConfigurationLocked(r, 0, preserveWindows); - // And we need to make sure at this point that all other activities - // are made visible with the correct configuration. - ensureActivitiesVisibleLocked(r, 0, preserveWindows); - if (!updated) { - resumeTopActivitiesLocked(stack, null, null); + if (r != null) { + final boolean updated = stack.ensureActivityConfigurationLocked(r, 0, preserveWindows); + // And we need to make sure at this point that all other activities + // are made visible with the correct configuration. + ensureActivitiesVisibleLocked(r, 0, preserveWindows); + if (!updated) { + resumeTopActivitiesLocked(stack, null, null); + } } + } finally { + mWindowManager.continueSurfaceLayout(); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } - - Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } void resizeTaskLocked(TaskRecord task, Rect bounds, int resizeMode, boolean preserveWindow) { @@ -3238,7 +3244,7 @@ public final class ActivityStackSupervisor implements DisplayListener { ActivityStack moveTaskToStackUncheckedLocked( TaskRecord task, int stackId, boolean toTop, boolean forceFocus, String reason) { final ActivityRecord r = task.getTopActivity(); - final boolean wasFocused = isFrontStack(task.stack) && (topRunningActivityLocked() == r); + final boolean wasFocused = isFocusedStack(task.stack) && (topRunningActivityLocked() == r); final boolean wasResumed = wasFocused && (task.stack.mResumedActivity == r); final boolean resizeable = task.mResizeable; @@ -3262,13 +3268,13 @@ public final class ActivityStackSupervisor implements DisplayListener { return stack; } - void moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus) { + void moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus, + String reason) { final TaskRecord task = anyTaskForIdLocked(taskId); if (task == null) { Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId); return; } - final String reason = "moveTaskToStack"; if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID || stackId == FULLSCREEN_WORKSPACE_STACK_ID) { // We are about to relaunch the activity because its configuration changed due to @@ -3281,7 +3287,8 @@ public final class ActivityStackSupervisor implements DisplayListener { mWindowManager.setReplacingWindow(r.appToken, true /* animate */); } final ActivityStack stack = - moveTaskToStackUncheckedLocked(task, stackId, toTop, forceFocus, reason); + moveTaskToStackUncheckedLocked(task, stackId, toTop, forceFocus, + "moveTaskToStack:" + reason); // Make sure the task has the appropriate bounds/size for the stack it is in. if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) { @@ -3327,7 +3334,8 @@ public final class ActivityStackSupervisor implements DisplayListener { if (task.mActivities.size() == 1) { // There is only one activity in the task. So, we can just move the task over to the // pinned stack without re-parenting the activity in a different task. - moveTaskToStackLocked(task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS); + moveTaskToStackLocked(task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS, + "moveTopActivityToPinnedStack"); } else { final ActivityStack pinnedStack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP); pinnedStack.moveActivityToStack(r); @@ -3462,7 +3470,7 @@ public final class ActivityStackSupervisor implements DisplayListener { for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); stack.awakeFromSleepingLocked(); - if (isFrontStack(stack)) { + if (isFocusedStack(stack)) { resumeTopActivitiesLocked(); } } @@ -3529,7 +3537,7 @@ public final class ActivityStackSupervisor implements DisplayListener { boolean reportResumedActivityLocked(ActivityRecord r) { final ActivityStack stack = r.task.stack; - if (isFrontStack(stack)) { + if (isFocusedStack(stack)) { mService.updateUsageStats(r, true); } if (allResumedActivitiesComplete()) { @@ -3822,7 +3830,7 @@ public final class ActivityStackSupervisor implements DisplayListener { final ActivityStack stack = stacks.get(stackNdx); final ActivityRecord r = stack.topRunningActivityLocked(); final ActivityState state = r == null ? DESTROYED : r.state; - if (isFrontStack(stack)) { + if (isFocusedStack(stack)) { if (r == null) Slog.e(TAG, "validateTop...: null top activity, stack=" + stack); else { diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 090a34247f04..120b40c73c13 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -318,7 +318,7 @@ final class TaskRecord { mNextAffiliateTaskId = nextTaskId; mCallingUid = callingUid; mCallingPackage = callingPackage; - mResizeable = resizeable; + mResizeable = resizeable || mService.mForceResizableActivites; mPrivileged = privileged; ActivityInfo info = mActivities.get(0).info; mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1; @@ -420,7 +420,7 @@ final class TaskRecord { } else { autoRemoveRecents = false; } - mResizeable = info.resizeable; + mResizeable = info.resizeable || mService.mForceResizableActivites; mLockTaskMode = info.lockTaskLaunchMode; mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0; setLockTaskAuth(); @@ -626,6 +626,9 @@ final class TaskRecord { // Only set this based on the first activity if (mActivities.isEmpty()) { taskType = r.mActivityType; + if (taskType == HOME_ACTIVITY_TYPE && mService.mForceResizableActivites) { + mResizeable = r.info.resizeable; + } isPersistable = r.isPersistable(); mCallingUid = r.launchedFromUid; mCallingPackage = r.launchedFromPackage; diff --git a/services/core/java/com/android/server/input/InputWindowHandle.java b/services/core/java/com/android/server/input/InputWindowHandle.java index 9149fcc63b0d..207c05d72bd1 100644 --- a/services/core/java/com/android/server/input/InputWindowHandle.java +++ b/services/core/java/com/android/server/input/InputWindowHandle.java @@ -100,6 +100,17 @@ public final class InputWindowHandle { } @Override + public String toString() { + return new StringBuilder(name) + .append(", layer=").append(layer) + .append(", frame=[").append(frameLeft).append(",").append(frameTop).append(",") + .append(frameRight).append(",").append(frameBottom).append("]") + .append(", touchableRegion=").append(touchableRegion) + .toString(); + + } + + @Override protected void finalize() throws Throwable { try { nativeDispose(); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e8e46efd55df..361bbf906d7e 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2146,13 +2146,6 @@ public class NotificationManagerService extends SystemService { + " id=" + id + " notification=" + notification); } - if (notification.getSmallIcon() != null) { - if (!notification.isValid()) { - throw new IllegalArgumentException("Invalid notification (): pkg=" + pkg - + " id=" + id + " notification=" + notification); - } - } - mHandler.post(new Runnable() { @Override public void run() { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b3e7f5f01010..be9f44dcd299 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1753,11 +1753,21 @@ public class PackageManagerService extends IPackageManager.Stub { PermissionsState permissionsState = sb.getPermissionsState(); - for (String permission : pkg.requestedPermissions) { - BasePermission bp = mSettings.mPermissions.get(permission); - if (bp != null && bp.isRuntime() && (grantedPermissions == null - || ArrayUtils.contains(grantedPermissions, permission))) { - permissionsState.grantRuntimePermission(bp, userId); + final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED + | PackageManager.FLAG_PERMISSION_POLICY_FIXED; + + synchronized (mPackages) { + for (String permission : pkg.requestedPermissions) { + BasePermission bp = mSettings.mPermissions.get(permission); + if (bp != null && (bp.isRuntime() || bp.isDevelopment()) + && (grantedPermissions == null + || ArrayUtils.contains(grantedPermissions, permission))) { + final int flags = permissionsState.getPermissionFlags(permission, userId); + // Installer cannot change immutable permissions. + if ((flags & immutableFlags) == 0) { + grantRuntimePermission(pkg.packageName, permission, userId); + } + } } } } @@ -2276,7 +2286,7 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings.enableSystemPackageLPw(packageName); try { - scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, UserHandle.SYSTEM); + scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null); } catch (PackageManagerException e) { Slog.e(TAG, "Failed to parse original system package: " + e.getMessage()); @@ -3558,7 +3568,8 @@ public class PackageManagerService extends IPackageManager.Stub { killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED); } }); - } break; + } + break; } mOnPermissionChangeListeners.onPermissionsChanged(uid); @@ -5685,7 +5696,7 @@ public class PackageManagerService extends IPackageManager.Stub { } try { scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK, - scanFlags, currentTime, UserHandle.SYSTEM); + scanFlags, currentTime, null); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage()); @@ -5791,8 +5802,6 @@ public class PackageManagerService extends IPackageManager.Stub { */ private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { - Preconditions.checkNotNull(user); - if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile); parseFlags |= mDefParseFlags; PackageParser pp = new PackageParser(); @@ -12141,7 +12150,7 @@ public class PackageManagerService extends IPackageManager.Stub { int oldScanFlags = SCAN_UPDATE_SIGNATURE | SCAN_UPDATE_TIME; try { scanPackageTracedLI(restoreFile, oldParseFlags, oldScanFlags, origUpdateTime, - UserHandle.SYSTEM); + null); } catch (PackageManagerException e) { Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade: " + e.getMessage()); @@ -13276,8 +13285,7 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageParser.Package newPkg; try { - newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, SCAN_NO_PATHS, 0, - UserHandle.SYSTEM); + newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, SCAN_NO_PATHS, 0, null); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to restore system package:" + newPs.name + ": " + e.getMessage()); return false; @@ -15841,8 +15849,7 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mInstallLock) { PackageParser.Package pkg = null; try { - pkg = scanPackageTracedLI(new File(codePath), parseFlags, 0, 0, - UserHandle.SYSTEM); + pkg = scanPackageTracedLI(new File(codePath), parseFlags, 0, 0, null); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to scan " + codePath + ": " + e.getMessage()); } @@ -16005,8 +16012,7 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mInstallLock) { final PackageParser.Package pkg; try { - pkg = scanPackageTracedLI(ps.codePath, parseFlags, SCAN_INITIAL, 0, - UserHandle.SYSTEM); + pkg = scanPackageTracedLI(ps.codePath, parseFlags, SCAN_INITIAL, 0, null); loaded.add(pkg.applicationInfo); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to scan " + ps.codePath + ": " + e.getMessage()); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index b36a22e4461f..4dd73889a663 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -16,14 +16,15 @@ package com.android.server.pm; -import android.accounts.Account; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityManagerNative; import android.app.IStopUserCallback; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManagerInternal; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -46,6 +47,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; +import android.os.UserManagerInternal; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.system.ErrnoException; @@ -59,9 +61,11 @@ import android.util.SparseBooleanArray; import android.util.TimeUtils; import android.util.Xml; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IAppOpsService; import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.LocalServices; @@ -83,11 +87,18 @@ import java.util.List; import libcore.io.IoUtils; +/** + * Service for {@link UserManager}. + * + * Method naming convention: + * - Methods suffixed with "Locked" should be called within the {@code this} lock. + * - Methods suffixed with "RL" should be called within the {@link #mRestrictionsLock} lock. + */ public class UserManagerService extends IUserManager.Stub { private static final String LOG_TAG = "UserManagerService"; - private static final boolean DBG = false; + private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE private static final String TAG_NAME = "name"; private static final String ATTR_FLAGS = "flags"; @@ -160,7 +171,38 @@ public class UserManagerService extends IUserManager.Stub { private final File mUserListFile; private final SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>(); - private final SparseArray<Bundle> mUserRestrictions = new SparseArray<Bundle>(); + + private final Object mRestrictionsLock = new Object(); + + /** + * User restrictions set via UserManager. This doesn't include restrictions set by + * device owner / profile owners. + * + * DO NOT Change existing {@link Bundle} in it. When changing a restriction for a user, + * a new {@link Bundle} should always be created and set. This is because a {@link Bundle} + * maybe shared between {@link #mBaseUserRestrictions} and + * {@link #mCachedEffectiveUserRestrictions}, but they should always updated separately. + * (Otherwise we won't be able to detect what restrictions have changed in + * {@link #updateUserRestrictionsInternalRL). + */ + @GuardedBy("mRestrictionsLock") + private final SparseArray<Bundle> mBaseUserRestrictions = new SparseArray<>(); + + /** + * Cached user restrictions that are in effect -- i.e. {@link #mBaseUserRestrictions} combined + * with device / profile owner restrictions. We'll initialize it lazily; use + * {@link #getEffectiveUserRestrictions} to access it. + * + * DO NOT Change existing {@link Bundle} in it. When changing a restriction for a user, + * a new {@link Bundle} should always be created and set. This is because a {@link Bundle} + * maybe shared between {@link #mBaseUserRestrictions} and + * {@link #mCachedEffectiveUserRestrictions}, but they should always updated separately. + * (Otherwise we won't be able to detect what restrictions have changed in + * {@link #updateUserRestrictionsInternalRL). + */ + @GuardedBy("mRestrictionsLock") + private final SparseArray<Bundle> mCachedEffectiveUserRestrictions = new SparseArray<>(); + private final Bundle mGuestRestrictions = new Bundle(); /** @@ -176,6 +218,8 @@ public class UserManagerService extends IUserManager.Stub { private IAppOpsService mAppOpsService; + private final LocalService mLocalService; + private static UserManagerService sInstance; public static UserManagerService getInstance() { @@ -231,6 +275,8 @@ public class UserManagerService extends IUserManager.Stub { sInstance = this; } } + mLocalService = new LocalService(); + LocalServices.addService(UserManagerInternal.class, mLocalService); } void systemReady() { @@ -258,8 +304,9 @@ public class UserManagerService extends IUserManager.Stub { mAppOpsService = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); for (int i = 0; i < mUserIds.length; ++i) { + final int userId = mUserIds[i]; try { - mAppOpsService.setUserRestrictions(mUserRestrictions.get(mUserIds[i]), mUserIds[i]); + mAppOpsService.setUserRestrictions(getEffectiveUserRestrictions(userId), userId); } catch (RemoteException e) { Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions"); } @@ -588,75 +635,171 @@ public class UserManagerService extends IUserManager.Stub { } } + @GuardedBy("mRestrictionsLock") + private Bundle computeEffectiveUserRestrictionsRL(int userId) { + final DevicePolicyManagerInternal dpmi = + LocalServices.getService(DevicePolicyManagerInternal.class); + final Bundle systemRestrictions = mBaseUserRestrictions.get(userId); + + final Bundle effective; + if (dpmi == null) { + // TODO Make sure it's because DPMS is disabled and not because we called it too early. + effective = systemRestrictions; + } else { + effective = dpmi.getComposedUserRestrictions(userId, systemRestrictions); + } + return effective; + } + + @GuardedBy("mRestrictionsLock") + private void invalidateEffectiveUserRestrictionsRL(int userId) { + if (DBG) { + Log.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId); + } + mCachedEffectiveUserRestrictions.remove(userId); + } + + private Bundle getEffectiveUserRestrictions(int userId) { + synchronized (mRestrictionsLock) { + Bundle restrictions = mCachedEffectiveUserRestrictions.get(userId); + if (restrictions == null) { + restrictions = computeEffectiveUserRestrictionsRL(userId); + mCachedEffectiveUserRestrictions.put(userId, restrictions); + } + return restrictions; + } + } + + /** @return a specific user restriction that's in effect currently. */ @Override public boolean hasUserRestriction(String restrictionKey, int userId) { - synchronized (mPackagesLock) { - Bundle restrictions = mUserRestrictions.get(userId); - return restrictions != null && restrictions.getBoolean(restrictionKey); - } + Bundle restrictions = getEffectiveUserRestrictions(userId); + return restrictions != null && restrictions.getBoolean(restrictionKey); } + /** + * @return UserRestrictions that are in effect currently. This always returns a new + * {@link Bundle}. + */ @Override public Bundle getUserRestrictions(int userId) { - synchronized (mPackagesLock) { - Bundle restrictions = mUserRestrictions.get(userId); - return restrictions != null ? new Bundle(restrictions) : new Bundle(); - } + Bundle restrictions = getEffectiveUserRestrictions(userId); + return restrictions != null ? new Bundle(restrictions) : new Bundle(); } @Override public void setUserRestriction(String key, boolean value, int userId) { checkManageUsersPermission("setUserRestriction"); - synchronized (mPackagesLock) { - if (!UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(key)) { - Bundle restrictions = getUserRestrictions(userId); - restrictions.putBoolean(key, value); - setUserRestrictionsInternalLocked(restrictions, userId); - } + if (!UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(key)) { + setUserRestrictionNoCheck(key, value, userId); } } @Override public void setSystemControlledUserRestriction(String key, boolean value, int userId) { checkSystemOrRoot("setSystemControlledUserRestriction"); - synchronized (mPackagesLock) { - Bundle restrictions = getUserRestrictions(userId); - restrictions.putBoolean(key, value); - setUserRestrictionsInternalLocked(restrictions, userId); + setUserRestrictionNoCheck(key, value, userId); + } + + private void setUserRestrictionNoCheck(String key, boolean value, int userId) { + synchronized (mRestrictionsLock) { + // Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create + // a copy. + final Bundle newRestrictions = new Bundle(); + UserRestrictionsUtils.merge(newRestrictions, mBaseUserRestrictions.get(userId)); + newRestrictions.putBoolean(key, value); + + updateUserRestrictionsInternalRL(newRestrictions, userId); } } - @Override - public void setUserRestrictions(Bundle restrictions, int userId) { - checkManageUsersPermission("setUserRestrictions"); - if (restrictions == null) return; + /** + * Optionally updating user restrictions, calculate the effective user restrictions by + * consulting {@link com.android.server.devicepolicy.DevicePolicyManagerService} and also + * apply it to {@link com.android.server.AppOpsService}. + * TODO applyUserRestrictionsLocked() should also apply to system settings. + * + * @param newRestrictions User restrictions to set. If null, only the effective restrictions + * will be updated. Note don't pass an existing Bundle in {@link #mBaseUserRestrictions} + * or {@link #mCachedEffectiveUserRestrictions}; that'll most likely cause a sub + * @param userId target user ID. + */ + @GuardedBy("mRestrictionsLock") + private void updateUserRestrictionsInternalRL( + @Nullable Bundle newRestrictions, int userId) { + if (DBG) { + Log.d(LOG_TAG, "updateUserRestrictionsInternalLocked userId=" + userId + + " bundle=" + newRestrictions); + } + final Bundle prevRestrictions = getEffectiveUserRestrictions(userId); - synchronized (mPackagesLock) { - final Bundle oldUserRestrictions = mUserRestrictions.get(userId); - // Restore the original state of system controlled restrictions from oldUserRestrictions - for (String key : UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS) { - restrictions.remove(key); - if (oldUserRestrictions.containsKey(key)) { - restrictions.putBoolean(key, oldUserRestrictions.getBoolean(key)); - } - } - setUserRestrictionsInternalLocked(restrictions, userId); + // Update system restrictions. + if (newRestrictions != null) { + // If newRestrictions == the current one, it's probably a bug. + Preconditions.checkState(mBaseUserRestrictions.get(userId) != newRestrictions); + Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId) + != newRestrictions); + mBaseUserRestrictions.put(userId, newRestrictions); } + + mCachedEffectiveUserRestrictions.put( + userId, computeEffectiveUserRestrictionsRL(userId)); + + applyUserRestrictionsRL(userId, mBaseUserRestrictions.get(userId), prevRestrictions); } - private void setUserRestrictionsInternalLocked(Bundle restrictions, int userId) { - final Bundle userRestrictions = mUserRestrictions.get(userId); - userRestrictions.clear(); - userRestrictions.putAll(restrictions); - long token = Binder.clearCallingIdentity(); + @GuardedBy("mRestrictionsLock") + private void applyUserRestrictionsRL(int userId, + Bundle newRestrictions, Bundle prevRestrictions) { + final long token = Binder.clearCallingIdentity(); try { - mAppOpsService.setUserRestrictions(userRestrictions, userId); + mAppOpsService.setUserRestrictions(newRestrictions, userId); } catch (RemoteException e) { Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions"); } finally { Binder.restoreCallingIdentity(token); } - scheduleWriteUserLocked(mUsers.get(userId)); + + // TODO Move the code from DPMS.setUserRestriction(). + } + + @GuardedBy("mRestrictionsLock") + private void updateEffectiveUserRestrictionsRL(int userId) { + updateUserRestrictionsInternalRL(null, userId); + } + + @GuardedBy("mRestrictionsLock") + private void updateEffectiveUserRestrictionsForAllUsersRL() { + // First, invalidate all cached values. + synchronized (mRestrictionsLock) { + mCachedEffectiveUserRestrictions.clear(); + } + // We don't want to call into ActivityManagerNative while taking a lock, so we'll call + // it on a handler. + final Runnable r = new Runnable() { + @Override + public void run() { + // Then get the list of running users. + final int[] runningUsers; + try { + runningUsers = ActivityManagerNative.getDefault().getRunningUserIds(); + } catch (RemoteException e) { + Log.w(LOG_TAG, "Unable to access ActivityManagerNative"); + return; + } + // Then re-calculate the effective restrictions and apply, only for running users. + // It's okay if a new user has started after the getRunningUserIds() call, + // because we'll do the same thing (re-calculate the restrictions and apply) + // when we start a user. + // TODO: "Apply restrictions upon user start hasn't been implemented. Implement it. + synchronized (mRestrictionsLock) { + for (int i = 0; i < runningUsers.length; i++) { + updateUserRestrictionsInternalRL(null, runningUsers[i]); + } + } + } + }; + mHandler.post(r); } /** @@ -926,7 +1069,9 @@ public class UserManagerService extends IUserManager.Stub { mUserVersion = USER_VERSION; Bundle restrictions = new Bundle(); - mUserRestrictions.append(UserHandle.USER_SYSTEM, restrictions); + synchronized (mRestrictionsLock) { + mBaseUserRestrictions.append(UserHandle.USER_SYSTEM, restrictions); + } updateUserIdsLocked(); initDefaultGuestRestrictions(); @@ -989,9 +1134,13 @@ public class UserManagerService extends IUserManager.Stub { serializer.startTag(null, TAG_NAME); serializer.text(userInfo.name); serializer.endTag(null, TAG_NAME); - Bundle restrictions = mUserRestrictions.get(userInfo.id); + Bundle restrictions; + synchronized (mRestrictionsLock) { + restrictions = mBaseUserRestrictions.get(userInfo.id); + } if (restrictions != null) { - UserRestrictionsUtils.writeRestrictions(serializer, restrictions, TAG_RESTRICTIONS); + UserRestrictionsUtils + .writeRestrictions(serializer, restrictions, TAG_RESTRICTIONS); } serializer.endTag(null, TAG_USER); @@ -1131,7 +1280,9 @@ public class UserManagerService extends IUserManager.Stub { userInfo.guestToRemove = guestToRemove; userInfo.profileGroupId = profileGroupId; userInfo.restrictedProfileParentId = restrictedProfileParentId; - mUserRestrictions.append(id, restrictions); + synchronized (mRestrictionsLock) { + mBaseUserRestrictions.append(id, restrictions); + } return userInfo; } catch (IOException ioe) { @@ -1333,7 +1484,9 @@ public class UserManagerService extends IUserManager.Stub { scheduleWriteUserLocked(userInfo); updateUserIdsLocked(); Bundle restrictions = new Bundle(); - mUserRestrictions.append(userId, restrictions); + synchronized (mRestrictionsLock) { + mBaseUserRestrictions.append(userId, restrictions); + } } } mPm.newUserCreated(userId); @@ -1616,25 +1769,6 @@ public class UserManagerService extends IUserManager.Stub { } } - @Override - public void removeRestrictions() { - checkManageUsersPermission("remove restrictions"); - final int userHandle = UserHandle.getCallingUserId(); - removeRestrictionsForUser(userHandle, true); - } - - private void removeRestrictionsForUser(final int userHandle, boolean unhideApps) { - synchronized (mPackagesLock) { - // Remove all user restrictions - setUserRestrictions(new Bundle(), userHandle); - // Remove any app restrictions - cleanAppRestrictions(userHandle); - } - if (unhideApps) { - unhideAllInstalledAppsForUser(userHandle); - } - } - private void unhideAllInstalledAppsForUser(final int userHandle) { mHandler.post(new Runnable() { @Override @@ -2062,7 +2196,10 @@ public class UserManagerService extends IUserManager.Stub { } pw.println(" Restrictions:"); UserRestrictionsUtils.dumpRestrictions( - pw, " ", mUserRestrictions.get(user.id)); + pw, " ", mBaseUserRestrictions.get(user.id)); + pw.println(" Effective restrictions:"); + UserRestrictionsUtils.dumpRestrictions( + pw, " ", mCachedEffectiveUserRestrictions.get(user.id)); } pw.println(); pw.println("Guest restrictions:"); @@ -2095,4 +2232,49 @@ public class UserManagerService extends IUserManager.Stub { boolean isInitialized(int userId) { return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0; } + + private class LocalService extends UserManagerInternal { + + @Override + public Object getUserRestrictionsLock() { + return mRestrictionsLock; + } + + @Override + @GuardedBy("mRestrictionsLock") + public void updateEffectiveUserRestrictionsRL(int userId) { + UserManagerService.this.updateEffectiveUserRestrictionsRL(userId); + } + + @Override + @GuardedBy("mRestrictionsLock") + public void updateEffectiveUserRestrictionsForAllUsersRL() { + UserManagerService.this.updateEffectiveUserRestrictionsForAllUsersRL(); + } + + @Override + public Bundle getBaseUserRestrictions(int userId) { + synchronized (mRestrictionsLock) { + return mBaseUserRestrictions.get(userId); + } + } + + @Override + public void setBaseUserRestrictionsByDpmsForMigration( + int userId, Bundle baseRestrictions) { + synchronized (mRestrictionsLock) { + mBaseUserRestrictions.put(userId, new Bundle(baseRestrictions)); + invalidateEffectiveUserRestrictionsRL(userId); + } + + synchronized (mPackagesLock) { + final UserInfo userInfo = mUsers.get(userId); + if (userInfo != null) { + writeUserLocked(userInfo); + } else { + Slog.w(LOG_TAG, "UserInfo not found for " + userId); + } + } + } + } } diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index db1fd2e28dfa..23e3b35ae3da 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -86,7 +86,6 @@ public class UserRestrictionsUtils { String tag) throws IOException { serializer.startTag(null, tag); for (String key : USER_RESTRICTIONS) { - // if (restrictions.getBoolean(key) && !NON_PERSIST_USER_RESTRICTIONS.contains(key)) { serializer.attribute(null, key, "true"); @@ -105,6 +104,17 @@ public class UserRestrictionsUtils { } } + public static void merge(Bundle dest, Bundle in) { + if (in == null) { + return; + } + for (String key : in.keySet()) { + if (in.getBoolean(key, false)) { + dest.putBoolean(key, true); + } + } + } + public static void dumpRestrictions(PrintWriter pw, String prefix, Bundle restrictions) { boolean noneSet = true; if (restrictions != null) { @@ -114,9 +124,11 @@ public class UserRestrictionsUtils { noneSet = false; } } - } - if (noneSet) { - pw.println(prefix + "none"); + if (noneSet) { + pw.println(prefix + "none"); + } + } else { + pw.println(prefix + "null"); } } } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 2365a4591310..eb0ca232462b 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -64,7 +64,6 @@ import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import android.view.animation.ScaleAnimation; import android.view.animation.TranslateAnimation; -import android.view.animation.TranslateYAnimation; import com.android.internal.util.DumpUtils.Dump; import com.android.server.AttributeCache; @@ -175,7 +174,7 @@ public class AppTransition implements Dump { private Rect mTmpFromClipRect = new Rect(); private Rect mTmpToClipRect = new Rect(); - private final Rect mTmpStartRect = new Rect(); + private final Rect mTmpRect = new Rect(); private final static int APP_STATE_IDLE = 0; private final static int APP_STATE_READY = 1; @@ -456,17 +455,19 @@ public class AppTransition implements Dump { return -startPos / denom; } - private Animation createScaleUpAnimationLocked( - int transit, boolean enter, int appWidth, int appHeight) { - Animation a = null; - getDefaultNextAppTransitionStartRect(mTmpStartRect); + private Animation createScaleUpAnimationLocked(int transit, boolean enter, + Rect containingFrame) { + Animation a; + getDefaultNextAppTransitionStartRect(mTmpRect); + final int appWidth = containingFrame.width(); + final int appHeight = containingFrame.height(); if (enter) { // Entering app zooms out from the center of the initial rect. - float scaleW = mTmpStartRect.width() / (float) appWidth; - float scaleH = mTmpStartRect.height() / (float) appHeight; + float scaleW = mTmpRect.width() / (float) appWidth; + float scaleH = mTmpRect.height() / (float) appHeight; Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1, - computePivot(mTmpStartRect.left, scaleW), - computePivot(mTmpStartRect.right, scaleH)); + computePivot(mTmpRect.left, scaleW), + computePivot(mTmpRect.right, scaleH)); scale.setInterpolator(mDecelerateInterpolator); Animation alpha = new AlphaAnimation(0, 1); @@ -543,20 +544,20 @@ public class AppTransition implements Dump { final int appWidth = appFrame.width(); final int appHeight = appFrame.height(); - // mTmpStartRect will contain an area around the launcher icon that was pressed. We will + // mTmpRect will contain an area around the launcher icon that was pressed. We will // clip reveal from that area in the final area of the app. - getDefaultNextAppTransitionStartRect(mTmpStartRect); + getDefaultNextAppTransitionStartRect(mTmpRect); float t = 0f; if (appHeight > 0) { - t = (float) mTmpStartRect.left / appHeight; + t = (float) mTmpRect.left / appHeight; } int translationY = mClipRevealTranslationY + (int)(appHeight / 7f * t); - int centerX = mTmpStartRect.centerX(); - int centerY = mTmpStartRect.centerY(); - int halfWidth = mTmpStartRect.width() / 2; - int halfHeight = mTmpStartRect.height() / 2; + int centerX = mTmpRect.centerX(); + int centerY = mTmpRect.centerY(); + int halfWidth = mTmpRect.width() / 2; + int halfHeight = mTmpRect.height() / 2; // Clip third of the from size of launch icon, expand to full width/height Animation clipAnimLR = new ClipRectLRAnimation( @@ -691,19 +692,19 @@ public class AppTransition implements Dump { float scaleW = appWidth / thumbWidth; float unscaledHeight = thumbHeight * scaleW; - getNextAppTransitionStartRect(taskId, mTmpStartRect); - float unscaledStartY = mTmpStartRect.top - (unscaledHeight - thumbHeight) / 2f; + getNextAppTransitionStartRect(taskId, mTmpRect); + float unscaledStartY = mTmpRect.top - (unscaledHeight - thumbHeight) / 2f; if (mNextAppTransitionScaleUp) { // Animation up from the thumbnail to the full screen Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, - mTmpStartRect.left + (thumbWidth / 2f), mTmpStartRect.top + (thumbHeight / 2f)); + mTmpRect.left + (thumbWidth / 2f), mTmpRect.top + (thumbHeight / 2f)); scale.setInterpolator(mTouchResponseInterpolator); scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); Animation alpha = new AlphaAnimation(1, 0); alpha.setInterpolator(mThumbnailFadeOutInterpolator); alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION); final float toX = appRect.left + appRect.width() / 2 - - (mTmpStartRect.left + thumbWidth / 2); + (mTmpRect.left + thumbWidth / 2); final float toY = appRect.top + mNextAppTransitionInsets.top + -unscaledStartY; Animation translate = new TranslateAnimation(0, toX, 0, toY); translate.setInterpolator(mTouchResponseInterpolator); @@ -718,7 +719,7 @@ public class AppTransition implements Dump { } else { // Animation down from the full screen to the thumbnail Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, - mTmpStartRect.left + (thumbWidth / 2f), mTmpStartRect.top + (thumbHeight / 2f)); + mTmpRect.left + (thumbWidth / 2f), mTmpRect.top + (thumbHeight / 2f)); scale.setInterpolator(mTouchResponseInterpolator); scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); Animation alpha = new AlphaAnimation(0f, 1f); @@ -746,14 +747,15 @@ public class AppTransition implements Dump { * activity that is leaving, and the activity that is entering. */ Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState, - int appWidth, int appHeight, int orientation, int transit, Rect containingFrame, - Rect contentInsets, @Nullable Rect surfaceInsets, boolean resizedWindow, - int taskId) { + int orientation, int transit, Rect containingFrame, Rect contentInsets, + @Nullable Rect surfaceInsets, boolean freeform, int taskId) { Animation a; - getDefaultNextAppTransitionStartRect(mTmpStartRect); - final int thumbWidthI = mTmpStartRect.width(); + final int appWidth = containingFrame.width(); + final int appHeight = containingFrame.height(); + getDefaultNextAppTransitionStartRect(mTmpRect); + final int thumbWidthI = mTmpRect.width(); final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; - final int thumbHeightI = mTmpStartRect.height(); + final int thumbHeightI = mTmpRect.height(); final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; // Used for the ENTER_SCALE_UP and EXIT_SCALE_DOWN transitions @@ -762,8 +764,8 @@ public class AppTransition implements Dump { switch (thumbTransitState) { case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: { - if (resizedWindow) { - a = createAspectScaledThumbnailEnterNonFullscreenAnimationLocked( + if (freeform) { + a = createAspectScaledThumbnailEnterFreeformAnimationLocked( containingFrame, surfaceInsets, taskId); } else { mTmpFromClipRect.set(containingFrame); @@ -794,8 +796,8 @@ public class AppTransition implements Dump { mNextAppTransitionInsets.set(contentInsets); Animation scaleAnim = new ScaleAnimation(scale, 1, scale, 1, - computePivot(mTmpStartRect.left, scale), - computePivot(mTmpStartRect.top, scale)); + computePivot(mTmpRect.left, scale), + computePivot(mTmpRect.top, scale)); Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect); Animation translateAnim = new TranslateAnimation(0, 0, -scaledTopDecor, 0); @@ -831,44 +833,49 @@ public class AppTransition implements Dump { } case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: { // App window scaling down from full screen - mTmpFromClipRect.set(containingFrame); - mTmpToClipRect.set(containingFrame); - // exclude top screen decor (status bar) region from the destination clip. - mTmpToClipRect.top = contentInsets.top; - if (orientation == Configuration.ORIENTATION_PORTRAIT) { - // In portrait, we scale the width and clip to the top/left square - scale = thumbWidth / appWidth; - scaledTopDecor = (int) (scale * contentInsets.top); - int unscaledThumbHeight = (int) (thumbHeight / scale); - mTmpToClipRect.bottom = mTmpToClipRect.top + unscaledThumbHeight; + if (freeform) { + a = createAspectScaledThumbnailExitFreeformAnimationLocked( + containingFrame, surfaceInsets, taskId); } else { - // In landscape, we scale the height and clip to the top/left square. We only - // scale the part that is not covered by status bar and the nav bar. - scale = thumbHeight / (appHeight - contentInsets.top - contentInsets.bottom); - scaledTopDecor = (int) (scale * contentInsets.top); - int unscaledThumbWidth = (int) (thumbWidth / scale); - mTmpToClipRect.right = mTmpToClipRect.left + unscaledThumbWidth; - // This removes the navigation bar from the last frame, so it better matches the - // thumbnail. We need to do this explicitly in landscape, because in portrait we - // already crop vertically. - mTmpToClipRect.bottom = mTmpToClipRect.bottom - contentInsets.bottom; - } + mTmpFromClipRect.set(containingFrame); + mTmpToClipRect.set(containingFrame); + // exclude top screen decor (status bar) region from the destination clip. + mTmpToClipRect.top = contentInsets.top; + if (orientation == Configuration.ORIENTATION_PORTRAIT) { + // In portrait, we scale the width and clip to the top/left square + scale = thumbWidth / appWidth; + scaledTopDecor = (int) (scale * contentInsets.top); + int unscaledThumbHeight = (int) (thumbHeight / scale); + mTmpToClipRect.bottom = mTmpToClipRect.top + unscaledThumbHeight; + } else { + // In landscape, we scale the height and clip to the top/left square. We only + // scale the part that is not covered by status bar and the nav bar. + scale = thumbHeight / (appHeight - contentInsets.top - contentInsets.bottom); + scaledTopDecor = (int) (scale * contentInsets.top); + int unscaledThumbWidth = (int) (thumbWidth / scale); + mTmpToClipRect.right = mTmpToClipRect.left + unscaledThumbWidth; + // This removes the navigation bar from the last frame, so it better matches the + // thumbnail. We need to do this explicitly in landscape, because in portrait we + // already crop vertically. + mTmpToClipRect.bottom = mTmpToClipRect.bottom - contentInsets.bottom; + } - mNextAppTransitionInsets.set(contentInsets); + mNextAppTransitionInsets.set(contentInsets); - Animation scaleAnim = new ScaleAnimation(1, scale, 1, scale, - computePivot(mTmpStartRect.left, scale), - computePivot(mTmpStartRect.top, scale)); - Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect); - Animation translateAnim = new TranslateAnimation(0, 0, 0, -scaledTopDecor); + Animation scaleAnim = new ScaleAnimation(1, scale, 1, scale, + computePivot(mTmpRect.left, scale), + computePivot(mTmpRect.top, scale)); + Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect); + Animation translateAnim = new TranslateAnimation(0, 0, 0, -scaledTopDecor); - AnimationSet set = new AnimationSet(true); - set.addAnimation(clipAnim); - set.addAnimation(scaleAnim); - set.addAnimation(translateAnim); + AnimationSet set = new AnimationSet(true); + set.addAnimation(clipAnim); + set.addAnimation(scaleAnim); + set.addAnimation(translateAnim); - a = set; - a.setZAdjustment(Animation.ZORDER_TOP); + a = set; + a.setZAdjustment(Animation.ZORDER_TOP); + } break; } default: @@ -881,27 +888,48 @@ public class AppTransition implements Dump { mTouchResponseInterpolator); } - private Animation createAspectScaledThumbnailEnterNonFullscreenAnimationLocked( - Rect frame, @Nullable Rect surfaceInsets, int taskId) { - getNextAppTransitionStartRect(taskId, mTmpStartRect); - float width = frame.width(); - float height = frame.height(); - float scaleWidth = mTmpStartRect.width() / width; - float scaleHeight = mTmpStartRect.height() / height; + private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame, + @Nullable Rect surfaceInsets, int taskId) { + getNextAppTransitionStartRect(taskId, mTmpRect); + return createAspectScaledThumbnailFreeformAnimationLocked(mTmpRect, frame, surfaceInsets, + true); + } + + private Animation createAspectScaledThumbnailExitFreeformAnimationLocked(Rect frame, + @Nullable Rect surfaceInsets, int taskId) { + getNextAppTransitionStartRect(taskId, mTmpRect); + return createAspectScaledThumbnailFreeformAnimationLocked(frame, mTmpRect, surfaceInsets, + false); + } + + private AnimationSet createAspectScaledThumbnailFreeformAnimationLocked(Rect sourceFrame, + Rect destFrame, @Nullable Rect surfaceInsets, boolean enter) { + final float sourceWidth = sourceFrame.width(); + final float sourceHeight = sourceFrame.height(); + final float destWidth = destFrame.width(); + final float destHeight = destFrame.height(); + final float scaleH = enter ? sourceWidth / destWidth : destWidth / sourceWidth; + final float scaleV = enter ? sourceHeight / destHeight : destHeight / sourceHeight; AnimationSet set = new AnimationSet(true); - int surfaceInsetsHorizontal = surfaceInsets == null + final int surfaceInsetsH = surfaceInsets == null ? 0 : surfaceInsets.left + surfaceInsets.right; - int surfaceInsetsVertical = surfaceInsets == null + final int surfaceInsetsV = surfaceInsets == null ? 0 : surfaceInsets.top + surfaceInsets.bottom; // We want the scaling to happen from the center of the surface. In order to achieve that, // we need to account for surface insets that will be used to enlarge the surface. - ScaleAnimation scale = new ScaleAnimation(scaleWidth, 1, scaleHeight, 1, - (width + surfaceInsetsHorizontal) / 2, (height + surfaceInsetsVertical) / 2); - int fromX = mTmpStartRect.left + mTmpStartRect.width() / 2 - - (frame.left + frame.width() / 2); - int fromY = mTmpStartRect.top + mTmpStartRect.height() / 2 - - (frame.top + frame.height() / 2); - TranslateAnimation translation = new TranslateAnimation(fromX, 0, fromY, 0); + final float scaleHCenter = ((enter ? destWidth : sourceWidth) + surfaceInsetsH) / 2; + final float scaleVCenter = ((enter ? destHeight : sourceHeight) + surfaceInsetsV) / 2; + final ScaleAnimation scale = enter ? + new ScaleAnimation(scaleH, 1, scaleV, 1, scaleHCenter, scaleVCenter) + : new ScaleAnimation(1, scaleH, 1, scaleV, scaleHCenter, scaleVCenter); + final int sourceHCenter = sourceFrame.left + sourceFrame.width() / 2; + final int sourceVCenter = sourceFrame.top + sourceFrame.height() / 2; + final int destHCenter = destFrame.left + destFrame.width() / 2; + final int destVCenter = destFrame.top + destFrame.height() / 2; + final int fromX = enter ? sourceHCenter - destHCenter : destHCenter - sourceHCenter; + final int fromY = enter ? sourceVCenter - destVCenter : destVCenter - sourceVCenter; + final TranslateAnimation translation = enter ? new TranslateAnimation(fromX, 0, fromY, 0) + : new TranslateAnimation(0, fromX, 0, fromY); set.addAnimation(scale); set.addAnimation(translation); return set; @@ -914,7 +942,7 @@ public class AppTransition implements Dump { Animation createThumbnailScaleAnimationLocked(int appWidth, int appHeight, int transit, Bitmap thumbnailHeader) { Animation a; - getDefaultNextAppTransitionStartRect(mTmpStartRect); + getDefaultNextAppTransitionStartRect(mTmpRect); final int thumbWidthI = thumbnailHeader.getWidth(); final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; final int thumbHeightI = thumbnailHeader.getHeight(); @@ -925,8 +953,8 @@ public class AppTransition implements Dump { float scaleW = appWidth / thumbWidth; float scaleH = appHeight / thumbHeight; Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH, - computePivot(mTmpStartRect.left, 1 / scaleW), - computePivot(mTmpStartRect.top, 1 / scaleH)); + computePivot(mTmpRect.left, 1 / scaleW), + computePivot(mTmpRect.top, 1 / scaleH)); scale.setInterpolator(mDecelerateInterpolator); Animation alpha = new AlphaAnimation(1, 0); @@ -942,8 +970,8 @@ public class AppTransition implements Dump { float scaleW = appWidth / thumbWidth; float scaleH = appHeight / thumbHeight; a = new ScaleAnimation(scaleW, 1, scaleH, 1, - computePivot(mTmpStartRect.left, 1 / scaleW), - computePivot(mTmpStartRect.top, 1 / scaleH)); + computePivot(mTmpRect.left, 1 / scaleW), + computePivot(mTmpRect.top, 1 / scaleH)); } return prepareThumbnailAnimation(a, appWidth, appHeight, transit); @@ -953,11 +981,13 @@ public class AppTransition implements Dump { * This animation is created when we are doing a thumbnail transition, for the activity that is * leaving, and the activity that is entering. */ - Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, int appWidth, - int appHeight, int transit, int taskId) { + Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, Rect containingFrame, + int transit, int taskId) { + final int appWidth = containingFrame.width(); + final int appHeight = containingFrame.height(); Bitmap thumbnailHeader = getAppTransitionThumbnailHeader(taskId); Animation a; - getDefaultNextAppTransitionStartRect(mTmpStartRect); + getDefaultNextAppTransitionStartRect(mTmpRect); final int thumbWidthI = thumbnailHeader != null ? thumbnailHeader.getWidth() : appWidth; final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight; @@ -969,8 +999,8 @@ public class AppTransition implements Dump { float scaleW = thumbWidth / appWidth; float scaleH = thumbHeight / appHeight; a = new ScaleAnimation(scaleW, 1, scaleH, 1, - computePivot(mTmpStartRect.left, scaleW), - computePivot(mTmpStartRect.top, scaleH)); + computePivot(mTmpRect.left, scaleW), + computePivot(mTmpRect.top, scaleH)); break; } case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: { @@ -997,8 +1027,8 @@ public class AppTransition implements Dump { float scaleW = thumbWidth / appWidth; float scaleH = thumbHeight / appHeight; Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH, - computePivot(mTmpStartRect.left, scaleW), - computePivot(mTmpStartRect.top, scaleH)); + computePivot(mTmpRect.left, scaleW), + computePivot(mTmpRect.top, scaleH)); Animation alpha = new AlphaAnimation(1, 0); @@ -1016,18 +1046,22 @@ public class AppTransition implements Dump { return prepareThumbnailAnimation(a, appWidth, appHeight, transit); } - private Animation createRelaunchAnimation(int appWidth, int appHeight, - Rect containingFrame) { + private Animation createRelaunchAnimation(Rect containingFrame, Rect contentInsets) { getDefaultNextAppTransitionStartRect(mTmpFromClipRect); final int left = mTmpFromClipRect.left; final int top = mTmpFromClipRect.top; mTmpFromClipRect.offset(-left, -top); - mTmpToClipRect.set(0, 0, appWidth, appHeight); + // TODO: Isn't that strange that we ignore exact position of the containingFrame? + mTmpToClipRect.set(0, 0, containingFrame.width(), containingFrame.height()); AnimationSet set = new AnimationSet(true); float fromWidth = mTmpFromClipRect.width(); float toWidth = mTmpToClipRect.width(); float fromHeight = mTmpFromClipRect.height(); - float toHeight = mTmpToClipRect.height(); + // While the window might span the whole display, the actual content will be cropped to the + // system decoration frame, for example when the window is docked. We need to take into + // account the visible height when constructing the animation. + float toHeight = mTmpToClipRect.height() - contentInsets.top - contentInsets.bottom; + int translateAdjustment = 0; if (fromWidth <= toWidth && fromHeight <= toHeight) { // The final window is larger in both dimensions than current window (e.g. we are // maximizing), so we can simply unclip the new window and there will be no disappearing @@ -1037,12 +1071,17 @@ public class AppTransition implements Dump { // The disappearing window has one larger dimension. We need to apply scaling, so the // first frame of the entry animation matches the old window. set.addAnimation(new ScaleAnimation(fromWidth / toWidth, 1, fromHeight / toHeight, 1)); + // We might not be going exactly full screen, but instead be aligned under the status + // bar using cropping. We still need to account for the cropped part, which will also + // be scaled. + translateAdjustment = (int) (contentInsets.top * fromHeight / toHeight); } - // We might not be going exactly full screen, but instead be aligned under the status bar. - // We need to take this into account when creating the translate animation. + // We animate the translation from the old position of the removed window, to the new + // position of the added window. The latter might not be full screen, for example docked for + // docked windows. TranslateAnimation translate = new TranslateAnimation(left - containingFrame.left, - 0, top - containingFrame.top, 0); + 0, top - containingFrame.top - translateAdjustment, 0); set.addAnimation(translate); set.setDuration(DEFAULT_APP_TRANSITION_DURATION); set.setZAdjustment(Animation.ZORDER_TOP); @@ -1060,10 +1099,30 @@ public class AppTransition implements Dump { && mNextAppTransitionType != NEXT_TRANSIT_TYPE_CLIP_REVEAL; } + /** + * + * @param frame These are the bounds of the window when it finishes the animation. This is where + * the animation must usually finish in entrance animation, as the next frame will + * display the window at these coordinates. In case of exit animation, this is + * where the animation must start, as the frame before the animation is displaying + * the window at these bounds. + * @param insets Knowing where the window will be positioned is not enough. Some parts of the + * window might be obscured, usually by the system windows (status bar and + * navigation bar) and we use content insets to convey that information. This + * usually affects the animation aspects vertically, as the system decoration is + * at the top and the bottom. For example when we animate from full screen to + * recents, we want to exclude the covered parts, because they won't match the + * thumbnail after the last frame is executed. + * @param surfaceInsets In rare situation the surface is larger than the content and we need to + * know about this to make the animation frames match. We currently use + * this for freeform windows, which have larger surfaces to display + * shadows. When we animate them from recents, we want to match the content + * to the recents thumbnail and hence need to account for the surface being + * bigger. + */ Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, - int appWidth, int appHeight, int orientation, Rect containingFrame, Rect contentInsets, - @Nullable Rect surfaceInsets, Rect appFrame, boolean isVoiceInteraction, - boolean resizedWindow, int taskId) { + int orientation, Rect frame, Rect insets, @Nullable Rect surfaceInsets, + boolean isVoiceInteraction, boolean freeform, int taskId) { Animation a; if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN || transit == TRANSIT_TASK_OPEN @@ -1086,7 +1145,7 @@ public class AppTransition implements Dump { + " anim=" + a + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3)); } else if (transit == TRANSIT_ACTIVITY_RELAUNCH) { - a = createRelaunchAnimation(appWidth, appHeight, containingFrame); + a = createRelaunchAnimation(frame, insets); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, "applyAnimation:" + " anim=" + a + " nextAppTransition=" + mNextAppTransition @@ -1108,14 +1167,14 @@ public class AppTransition implements Dump { + " transit=" + appTransitionToString(transit) + " Callers=" + Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) { - a = createClipRevealAnimationLocked(transit, enter, appFrame); + a = createClipRevealAnimationLocked(transit, enter, frame); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, "applyAnimation:" + " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL" + " transit=" + appTransitionToString(transit) + " Callers=" + Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) { - a = createScaleUpAnimationLocked(transit, enter, appWidth, appHeight); + a = createScaleUpAnimationLocked(transit, enter, frame); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, "applyAnimation:" + " anim=" + a + " nextAppTransition=ANIM_SCALE_UP" @@ -1126,7 +1185,7 @@ public class AppTransition implements Dump { mNextAppTransitionScaleUp = (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP); a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter), - appWidth, appHeight, transit, taskId); + frame, transit, taskId); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) { String animName = mNextAppTransitionScaleUp ? "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN"; @@ -1140,8 +1199,8 @@ public class AppTransition implements Dump { mNextAppTransitionScaleUp = (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP); a = createAspectScaledThumbnailEnterExitAnimationLocked( - getThumbnailTransitionState(enter), appWidth, appHeight, orientation, transit, - containingFrame, contentInsets, surfaceInsets, resizedWindow, taskId); + getThumbnailTransitionState(enter), orientation, transit, frame, + insets, surfaceInsets, freeform, taskId); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) { String animName = mNextAppTransitionScaleUp ? "ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN"; @@ -1458,15 +1517,15 @@ public class AppTransition implements Dump { pw.print(Integer.toHexString(mNextAppTransitionInPlace)); break; case NEXT_TRANSIT_TYPE_SCALE_UP: { - getDefaultNextAppTransitionStartRect(mTmpStartRect); + getDefaultNextAppTransitionStartRect(mTmpRect); pw.print(prefix); pw.print("mNextAppTransitionStartX="); - pw.print(mTmpStartRect.left); + pw.print(mTmpRect.left); pw.print(" mNextAppTransitionStartY="); - pw.println(mTmpStartRect.top); + pw.println(mTmpRect.top); pw.print(prefix); pw.print("mNextAppTransitionStartWidth="); - pw.print(mTmpStartRect.width()); + pw.print(mTmpRect.width()); pw.print(" mNextAppTransitionStartHeight="); - pw.println(mTmpStartRect.height()); + pw.println(mTmpRect.height()); break; } case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP: diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java index bf7063fba67e..a61e83fc12df 100644 --- a/services/core/java/com/android/server/wm/AppWindowAnimator.java +++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java @@ -300,7 +300,8 @@ public class AppWindowAnimator { return false; } - if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed) + if ((mAppToken.allDrawn || mAppToken.mAnimatingWithSavedSurface + || animating || mAppToken.startingDisplayed) && animation != null) { if (!animating) { if (DEBUG_ANIM) Slog.v(TAG, diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 5a1ed582358c..c5bd3a79ac5b 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -17,6 +17,9 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static com.android.server.wm.WindowManagerService.DEBUG_ANIM; +import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS; +import static com.android.server.wm.WindowManagerService.TAG; import com.android.server.input.InputApplicationHandle; import com.android.server.wm.WindowManagerService.H; @@ -52,6 +55,13 @@ class AppWindowToken extends WindowToken { final boolean voiceInteraction; + // Whether the window has a saved surface from last pause, which can be + // used to start an entering animation earlier. + boolean mHasSavedSurface; + + // Whether we're performing an entering animation with a saved surface. + boolean mAnimatingWithSavedSurface; + Task mTask; boolean appFullscreen; int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -114,11 +124,14 @@ class AppWindowToken extends WindowToken { // This application will have its window replaced due to relaunch. This allows window manager // to differentiate between simple removal of a window and replacement. In the latter case it // will preserve the old window until the new one is drawn. - boolean mReplacingWindow; + boolean mWillReplaceWindow; // If true, the replaced window was already requested to be removed. boolean mReplacingRemoveRequested; // Whether the replacement of the window should trigger app transition animation. boolean mAnimateReplacingWindow; + // If not null, the window that will be used to replace the old one. This is being set when + // the window is added and unset when this window reports its first draw. + WindowState mReplacingWindow; AppWindowToken(WindowManagerService _service, IApplicationToken _token, boolean _voiceInteraction) { @@ -257,10 +270,13 @@ class AppWindowToken extends WindowToken { final int N = allAppWindows.size(); for (int i=0; i<N; i++) { WindowState win = allAppWindows.get(i); + // If we're animating with a saved surface, we're already visible. + // Return true so that the alpha doesn't get cleared. if (!win.mAppFreezing - && (win.mViewVisibility == View.VISIBLE || - (win.mWinAnimator.isAnimating() && - !service.mAppTransition.isTransitionSet())) + && (win.mViewVisibility == View.VISIBLE + || mAnimatingWithSavedSurface + || (win.mWinAnimator.isAnimating() && + !service.mAppTransition.isTransitionSet())) && !win.mDestroying && win.isDrawnLw()) { return true; } @@ -283,6 +299,52 @@ class AppWindowToken extends WindowToken { } } + /** + * Checks whether we should save surfaces for this app. + * + * @return true if the surfaces should be saved, false otherwise. + */ + boolean shouldSaveSurface() { + // We want to save surface if the app's windows are "allDrawn", or if we're + // currently animating with save surfaces. (If the app didn't even finish + // drawing when the user exits, but we have a saved surface from last time, + // we still want to keep that surface.) + mHasSavedSurface = allDrawn || mAnimatingWithSavedSurface; + if (mHasSavedSurface) { + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, + "Saving surface: " + this); + return true; + } + return false; + } + + void restoreSavedSurfaces() { + if (!mHasSavedSurface) { + return; + } + + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, + "Restoring saved surfaces: " + this + ", allDrawn=" + allDrawn); + + mHasSavedSurface = false; + mAnimatingWithSavedSurface = true; + for (int i = windows.size() - 1; i >= 0; i--) { + WindowState ws = windows.get(i); + ws.mWinAnimator.mDrawState = WindowStateAnimator.READY_TO_SHOW; + } + } + + void destroySavedSurfaces() { + if (mHasSavedSurface) { + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, + "Destroying saved surface: " + this); + for (int i = windows.size() - 1; i >= 0; i--) { + final WindowState win = windows.get(i); + win.mWinAnimator.destroySurfaceLocked(); + } + } + } + @Override void removeAllWindows() { for (int winNdx = allAppWindows.size() - 1; winNdx >= 0; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 39479c1a9681..fab8ee55b22c 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -18,7 +18,7 @@ package com.android.server.wm; import static android.app.ActivityManager.DOCKED_STACK_ID; import static android.app.ActivityManager.HOME_STACK_ID; - +import static android.app.ActivityManager.PINNED_STACK_ID; import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerService.TAG; import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP; @@ -243,10 +243,30 @@ class DisplayContent { } void moveStack(TaskStack stack, boolean toTop) { + if (stack.mStackId == PINNED_STACK_ID && !toTop) { + // Pinned stack is always-on-top silly... + Slog.w(TAG, "Ignoring move of always-on-top stack=" + stack + " to bottom"); + return; + } + if (!mStacks.remove(stack)) { Slog.wtf(TAG, "moving stack that was not added: " + stack, new Throwable()); } - mStacks.add(toTop ? mStacks.size() : 0, stack); + + int addIndex = toTop ? mStacks.size() : 0; + + if (toTop + && mService.isStackVisibleLocked(PINNED_STACK_ID) + && stack.mStackId != PINNED_STACK_ID) { + // The pinned stack is always the top most stack (always-on-top) when it is visible. + // So, stack is moved just below the pinned stack. + addIndex--; + TaskStack topStack = mStacks.get(addIndex); + if (topStack.mStackId != PINNED_STACK_ID) { + throw new IllegalStateException("Pinned stack isn't top stack??? " + mStacks); + } + } + mStacks.add(addIndex, stack); } void detachStack(TaskStack stack) { diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index 2d8712375757..04cba81726ec 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -18,6 +18,8 @@ package com.android.server.wm; import static android.app.ActivityManager.DOCKED_STACK_ID; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW; +import static android.view.PointerIcon.STYLE_VERTICAL_DOUBLE_ARROW; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; @@ -87,6 +89,8 @@ public class DockedStackDividerController implements View.OnTouchListener, DimLa final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE; final int width = landscape ? mDividerWidth : MATCH_PARENT; final int height = landscape ? MATCH_PARENT : mDividerWidth; + view.setPointerShape( + landscape ? STYLE_HORIZONTAL_DOUBLE_ARROW : STYLE_VERTICAL_DOUBLE_ARROW); WindowManager.LayoutParams params = new WindowManager.LayoutParams( width, height, TYPE_DOCK_DIVIDER, FLAG_TOUCHABLE_WHEN_WAKING | FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index f5e97e5a5d7c..f35ea669a71d 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -77,7 +77,6 @@ class DragState { mSurfaceControl = surface; mFlags = flags; mLocalWin = localWin; - mUid = Binder.getCallingUid(); mNotifiedWindows = new ArrayList<WindowState>(); } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 6c391ade60cc..5f911c36825e 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -17,6 +17,8 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; +import static com.android.server.wm.WindowManagerService.DEBUG_FOCUS_LIGHT; +import static com.android.server.wm.WindowManagerService.DEBUG_INPUT; import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH; import android.app.ActivityManagerNative; import android.graphics.Rect; @@ -179,7 +181,17 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { if (modal && child.mAppToken != null) { // Limit the outer touch to the activity stack region. flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; - child.getVisibleBounds(mTmpRect, BOUNDS_FOR_TOUCH); + // If this is a modal window we need to dismiss it if it's not full screen and the touch + // happens outside of the frame that displays the content. This means we need to + // intercept touches outside of that window. The dim layer user associated with the + // window (task or stack) will give us the good bounds, as they would be used to display + // the dim layer. + final DimLayer.DimLayerUser dimLayerUser = child.getDimLayerUser(); + if (dimLayerUser != null) { + dimLayerUser.getBounds(mTmpRect); + } else { + child.getVisibleBounds(mTmpRect, BOUNDS_FOR_TOUCH); + } inputWindowHandle.touchableRegion.set(mTmpRect); } else { // Not modal or full screen modal @@ -227,7 +239,9 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { inputWindowHandle.scaleFactor = 1; } - + if (DEBUG_INPUT) { + Slog.d(WindowManagerService.TAG, "addInputWindowHandle: " + inputWindowHandle); + } addInputWindowHandleLw(inputWindowHandle); } @@ -428,7 +442,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { * Layer assignment is assumed to be complete by the time this is called. */ public void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) { - if (WindowManagerService.DEBUG_FOCUS_LIGHT || WindowManagerService.DEBUG_INPUT) { + if (DEBUG_FOCUS_LIGHT || DEBUG_INPUT) { Slog.d(WindowManagerService.TAG, "Input focus has changed to " + newWindow); } @@ -464,7 +478,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { public void pauseDispatchingLw(WindowToken window) { if (! window.paused) { - if (WindowManagerService.DEBUG_INPUT) { + if (DEBUG_INPUT) { Slog.v(WindowManagerService.TAG, "Pausing WindowToken " + window); } @@ -475,7 +489,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { public void resumeDispatchingLw(WindowToken window) { if (window.paused) { - if (WindowManagerService.DEBUG_INPUT) { + if (DEBUG_INPUT) { Slog.v(WindowManagerService.TAG, "Resuming WindowToken " + window); } @@ -486,7 +500,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { public void freezeInputDispatchingLw() { if (! mInputDispatchFrozen) { - if (WindowManagerService.DEBUG_INPUT) { + if (DEBUG_INPUT) { Slog.v(WindowManagerService.TAG, "Freezing input dispatching"); } @@ -497,7 +511,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { public void thawInputDispatchingLw() { if (mInputDispatchFrozen) { - if (WindowManagerService.DEBUG_INPUT) { + if (DEBUG_INPUT) { Slog.v(WindowManagerService.TAG, "Thawing input dispatching"); } @@ -508,7 +522,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { public void setEventDispatchingLw(boolean enabled) { if (mInputDispatchEnabled != enabled) { - if (WindowManagerService.DEBUG_INPUT) { + if (DEBUG_INPUT) { Slog.v(WindowManagerService.TAG, "Setting event dispatching to " + enabled); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 65278813e372..ad4419604896 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.ActivityManager.DOCKED_STACK_ID; import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.HOME_STACK_ID; import static android.app.ActivityManager.PINNED_STACK_ID; import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION; import static com.android.server.wm.WindowManagerService.TAG; @@ -324,6 +325,10 @@ class Task implements DimLayer.DimLayerUser { return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers; } + boolean inHomeStack() { + return mStack != null && mStack.mStackId == HOME_STACK_ID; + } + boolean inFreeformWorkspace() { return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID; } @@ -337,6 +342,11 @@ class Task implements DimLayer.DimLayerUser { return tokensCount > 0 ? mAppTokens.get(tokensCount - 1).findMainWindow() : null; } + AppWindowToken getTopAppWindowToken() { + final int tokensCount = mAppTokens.size(); + return tokensCount > 0 ? mAppTokens.get(tokensCount - 1) : null; + } + @Override public boolean isFullscreen() { if (useCurrentBounds()) { diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 9b3d47823d7a..df664bd271bd 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -409,7 +409,7 @@ public class TaskStack implements DimLayer.DimLayerUser { final boolean dockedOnTopOrLeft = WindowManagerService.sDockedStackCreateMode == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2, - mDisplayContent.mDividerControllerLocked.getWidthAdjustment(), + mDisplayContent.mDividerControllerLocked.getWidth(), dockedOnTopOrLeft); } @@ -459,7 +459,7 @@ public class TaskStack implements DimLayer.DimLayerUser { dockedStack.getRawBounds(mTmpRect2); final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT; getStackDockedModeBounds(mTmpRect, outBounds, mStackId, mTmpRect2, - mDisplayContent.mDividerControllerLocked.getWidthAdjustment(), dockedOnTopOrLeft); + mDisplayContent.mDividerControllerLocked.getWidth(), dockedOnTopOrLeft); } @@ -469,13 +469,13 @@ public class TaskStack implements DimLayer.DimLayerUser { * @param outBounds Output bounds that should be used for the stack. * @param stackId Id of stack we are calculating the bounds for. * @param dockedBounds Bounds of the docked stack. - * @param adjustment Additional adjustment to make to the output bounds close to the side of the - * dock. - * @param dockOntopOrLeft If the docked stack is on the top or left side of the screen. + * @param dockDividerWidth We need to know the width of the divider make to the output bounds + * close to the side of the dock. + * @param dockOnTopOrLeft If the docked stack is on the top or left side of the screen. */ private static void getStackDockedModeBounds( - Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int adjustment, - boolean dockOntopOrLeft) { + Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int dockDividerWidth, + boolean dockOnTopOrLeft) { final boolean dockedStack = stackId == DOCKED_STACK_ID; final boolean splitHorizontally = displayRect.width() > displayRect.height(); @@ -484,34 +484,34 @@ public class TaskStack implements DimLayer.DimLayerUser { // The initial bounds of the docked stack when it is created half the screen space and // its bounds can be adjusted after that. The bounds of all other stacks are adjusted // to occupy whatever screen space the docked stack isn't occupying. - if (dockOntopOrLeft) { + if (dockOnTopOrLeft) { if (splitHorizontally) { - outBounds.right = displayRect.centerX() - adjustment; + outBounds.right = displayRect.centerX() - dockDividerWidth / 2; } else { - outBounds.bottom = displayRect.centerY() - adjustment; + outBounds.bottom = displayRect.centerY() - dockDividerWidth / 2; } } else { if (splitHorizontally) { - outBounds.left = displayRect.centerX() + adjustment; + outBounds.left = displayRect.centerX() + dockDividerWidth / 2; } else { - outBounds.top = displayRect.centerY() + adjustment; + outBounds.top = displayRect.centerY() + dockDividerWidth / 2; } } return; } // Other stacks occupy whatever space is left by the docked stack. - if (!dockOntopOrLeft) { + if (!dockOnTopOrLeft) { if (splitHorizontally) { - outBounds.right = dockedBounds.left - adjustment; + outBounds.right = dockedBounds.left - dockDividerWidth; } else { - outBounds.bottom = dockedBounds.top - adjustment; + outBounds.bottom = dockedBounds.top - dockDividerWidth; } } else { if (splitHorizontally) { - outBounds.left = dockedBounds.right + adjustment; + outBounds.left = dockedBounds.right + dockDividerWidth; } else { - outBounds.top = dockedBounds.bottom + adjustment; + outBounds.top = dockedBounds.bottom + dockDividerWidth; } } } diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 928a1170625c..f4a41404a188 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -31,6 +31,7 @@ import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PEN import android.content.Context; import android.os.RemoteException; +import android.os.Trace; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; @@ -413,7 +414,7 @@ public class WindowAnimator { final AppWindowToken atoken = win.mAppToken; if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) { - if (atoken == null || atoken.allDrawn) { + if (atoken == null || atoken.allDrawn || atoken.mAnimatingWithSavedSurface) { if (winAnimator.performShowLocked()) { setPendingLayoutChanges(displayId, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM); @@ -731,8 +732,15 @@ public class WindowAnimator { mWindowPlacerLocked.requestTraversal(); } + if (mAnimating && !wasAnimating && Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0); + } + if (!mAnimating && wasAnimating) { mWindowPlacerLocked.requestTraversal(); + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0); + } } mService.destroyPreservedSurfaceLocked(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index cd5fbb0a6b31..140fbaf01f58 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1327,8 +1327,14 @@ public class WindowManagerService extends IWindowManager.Stub addAttachedWindowToListLocked(win, addToToken); } - if (win.mAppToken != null && addToToken) { - win.mAppToken.allAppWindows.add(win); + final AppWindowToken appToken = win.mAppToken; + if (appToken != null) { + if (addToToken) { + appToken.allAppWindows.add(win); + } + if (appToken.mWillReplaceWindow) { + appToken.mReplacingWindow = win; + } } } @@ -2053,7 +2059,7 @@ public class WindowManagerService extends IWindowManager.Stub } private void prepareWindowReplacementTransition(AppWindowToken atoken) { - if (atoken == null || !atoken.mReplacingWindow || !atoken.mAnimateReplacingWindow) { + if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) { return; } atoken.allDrawn = false; @@ -2157,6 +2163,8 @@ public class WindowManagerService extends IWindowManager.Stub + " isAnimating=" + win.mWinAnimator.isAnimating() + " app-animation=" + (win.mAppToken != null ? win.mAppToken.mAppAnimator.animation : null) + + " mWillReplaceWindow=" + + (win.mAppToken != null ? win.mAppToken.mWillReplaceWindow : false) + " inPendingTransaction=" + (win.mAppToken != null ? win.mAppToken.inPendingTransaction : false) + " mDisplayFrozen=" + mDisplayFrozen); @@ -2168,8 +2176,8 @@ public class WindowManagerService extends IWindowManager.Stub // animation wouldn't be seen. if (win.mHasSurface && okToDisplay()) { final AppWindowToken appToken = win.mAppToken; - if (appToken != null && appToken.mReplacingWindow) { - // This window is going to be replaced. We need to kepp it around until the new one + if (appToken != null && appToken.mWillReplaceWindow) { + // This window is going to be replaced. We need to keep it around until the new one // gets added, then we will get rid of this one. if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Preserving " + win + " until the new one is " + "added"); @@ -2629,6 +2637,11 @@ public class WindowManagerService extends IWindowManager.Stub } } dragResizing = win.isDragResizing(); + if (win.isAnimatingWithSavedSurface()) { + // If we're animating with a saved surface now, request client to report draw. + // We still need to know when the real thing is drawn. + toBeDisplayed = true; + } try { if (!win.mHasSurface) { surfaceChanged = true; @@ -2679,9 +2692,14 @@ public class WindowManagerService extends IWindowManager.Stub if (winAnimator.mSurfaceControl != null) { if (DEBUG_VISIBILITY) Slog.i(TAG, "Relayout invis " + win + ": mExiting=" + win.mExiting); + // If we are using a saved surface to do enter animation, just let the + // animation run and don't destroy the surface. This could happen when + // the app sets visibility to invisible for the first time after resume, + // or when the user exits immediately after a resume. In both cases, we + // don't want to destroy the saved surface. // If we are not currently running the exit animation, we // need to see about starting one. - if (!win.mExiting) { + if (!win.mExiting && !win.isAnimatingWithSavedSurface()) { surfaceChanged = true; // Try starting an animation; if there isn't one, we // can destroy the surface right away. @@ -2707,7 +2725,9 @@ public class WindowManagerService extends IWindowManager.Stub if (mInputMethodWindow == win) { mInputMethodWindow = null; } - winAnimator.destroySurfaceLocked(); + if (!win.shouldSaveSurface()) { + winAnimator.destroySurfaceLocked(); + } } //TODO (multidisplay): Magnification is supported only for the default if (mAccessibilityController != null @@ -2833,7 +2853,8 @@ public class WindowManagerService extends IWindowManager.Stub try { synchronized (mWindowMap) { WindowState win = windowForClientLocked(session, client, false); - if (DEBUG_ADD_REMOVE) Slog.d(TAG, "finishDrawingWindow: " + win); + if (DEBUG_ADD_REMOVE) Slog.d(TAG, "finishDrawingWindow: " + win + " mDrawState=" + + (win != null ? win.mWinAnimator.drawStateToString() : "null")); if (win != null && win.mWinAnimator.finishDrawingLocked()) { if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) { getDefaultDisplayContentLocked().pendingLayoutChanges |= @@ -2862,46 +2883,44 @@ public class WindowManagerService extends IWindowManager.Stub "applyAnimation: atoken=" + atoken); // Determine the visible rect to calculate the thumbnail clip - WindowState win = atoken.findMainWindow(); - Rect containingFrame = new Rect(0, 0, width, height); - Rect contentInsets = new Rect(); - Rect appFrame = new Rect(0, 0, width, height); + final WindowState win = atoken.findMainWindow(); + final Rect frame = new Rect(0, 0, width, height); + final Rect insets = new Rect(); Rect surfaceInsets = null; final boolean fullscreen = win != null && win.isFullscreen(width, height); final boolean freeform = win != null && win.inFreeformWorkspace(); + final boolean docked = win != null && win.inDockedWorkspace(); if (win != null) { // Containing frame will usually cover the whole screen, including dialog windows. // For freeform workspace windows it will not cover the whole screen and it also // won't exactly match the final freeform window frame (e.g. when overlapping with // the status bar). In that case we need to use the final frame. if (freeform) { - containingFrame.set(win.mFrame); + frame.set(win.mFrame); } else { - containingFrame.set(win.mContainingFrame); + frame.set(win.mContainingFrame); } surfaceInsets = win.getAttrs().surfaceInsets; - if (fullscreen) { + if (fullscreen || docked) { // For fullscreen windows use the window frames and insets to set the thumbnail - // clip. For none-fullscreen windows we use the app display region so the clip + // clip. For non-fullscreen windows we use the app display region so the clip // isn't affected by the window insets. - contentInsets.set(win.mContentInsets); - appFrame.set(win.mFrame); - } else { - appFrame.set(containingFrame); + insets.set(win.mContentInsets); } } - final int containingWidth = containingFrame.width(); - final int containingHeight = containingFrame.height(); if (atoken.mLaunchTaskBehind) { // Differentiate the two animations. This one which is briefly on the screen // gets the !enter animation, and the other activity which remains on the // screen gets the enter animation. Both appear in the mOpeningApps set. enter = false; } - Animation a = mAppTransition.loadAnimation(lp, transit, enter, containingWidth, - containingHeight, mCurConfiguration.orientation, containingFrame, contentInsets, - surfaceInsets, appFrame, isVoiceInteraction, freeform, atoken.mTask.mTaskId); + if (DEBUG_APP_TRANSITIONS) Slog.d(TAG, "Loading animation for app transition." + + " transit=" + AppTransition.appTransitionToString(transit) + " enter=" + enter + + " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets); + Animation a = mAppTransition.loadAnimation(lp, transit, enter, + mCurConfiguration.orientation, frame, insets, surfaceInsets, isVoiceInteraction, + freeform, atoken.mTask.mTaskId); if (a != null) { if (DEBUG_ANIM) { RuntimeException e = null; @@ -2911,6 +2930,8 @@ public class WindowManagerService extends IWindowManager.Stub } Slog.v(TAG, "Loaded animation " + a + " for " + atoken, e); } + final int containingWidth = frame.width(); + final int containingHeight = frame.height(); atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight, mAppTransition.canSkipFirstFrame()); } @@ -3262,10 +3283,8 @@ public class WindowManagerService extends IWindowManager.Stub } } - final TaskStack dockedStack = mStackIdToStack.get(DOCKED_STACK_ID); - final TaskStack freeformStack = mStackIdToStack.get(FREEFORM_WORKSPACE_STACK_ID); - if ((dockedStack != null && dockedStack.isVisibleLocked()) - || (freeformStack != null && freeformStack.isVisibleLocked())) { + if (isStackVisibleLocked(DOCKED_STACK_ID) + || isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) { // We don't let app affect the system orientation when in freeform or docked mode since // they don't occupy the entire display and their request can conflict with other apps. return SCREEN_ORIENTATION_UNSPECIFIED; @@ -3909,7 +3928,7 @@ public class WindowManagerService extends IWindowManager.Stub // transition animation // * or this is an opening app and windows are being replaced. if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) || - (visible && wtoken.mReplacingWindow)) { + (visible && wtoken.mWillReplaceWindow)) { boolean changed = false; if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Changing app " + wtoken + " hidden=" + wtoken.hidden @@ -4496,6 +4515,11 @@ public class WindowManagerService extends IWindowManager.Stub } } + boolean isStackVisibleLocked(int stackId) { + final TaskStack stack = mStackIdToStack.get(stackId); + return (stack != null && stack.isVisibleLocked()); + } + public void setDockedStackCreateMode(int mode) { synchronized (mWindowMap) { sDockedStackCreateMode = mode; @@ -4657,7 +4681,7 @@ public class WindowManagerService extends IWindowManager.Stub throw new IllegalArgumentException("resizeStack: stackId " + stackId + " not found."); } - if (stack.setBounds(bounds, configs, taskBounds)) { + if (stack.setBounds(bounds, configs, taskBounds) && stack.isVisibleLocked()) { stack.resizeWindows(); stack.getDisplayContent().layoutNeeded = true; mWindowPlacerLocked.performSurfacePlacement(); @@ -4710,6 +4734,26 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** + * Starts deferring layout passes. Useful when doing multiple changes but to optimize + * performance, only one layout pass should be done. This can be called multiple times, and + * layouting will be resumed once the last caller has called {@link #continueSurfaceLayout} + */ + public void deferSurfaceLayout() { + synchronized (mWindowMap) { + mWindowPlacerLocked.deferLayout(); + } + } + + /** + * Resumes layout passes after deferring them. See {@link #deferSurfaceLayout()} + */ + public void continueSurfaceLayout() { + synchronized (mWindowMap) { + mWindowPlacerLocked.continueLayout(); + } + } + public void getTaskBounds(int taskId, Rect bounds) { synchronized (mWindowMap) { Task task = mTaskIdToTask.get(taskId); @@ -5396,8 +5440,8 @@ public class WindowManagerService extends IWindowManager.Stub if (visible) { // TODO(multi-display): support multiple displays if (mCircularDisplayMask == null) { - int screenOffset = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.circular_display_mask_offset); + int screenOffset = mContext.getResources().getInteger( + com.android.internal.R.integer.config_windowOutsetBottom); int maskThickness = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.circular_display_mask_thickness); @@ -6070,6 +6114,10 @@ public class WindowManagerService extends IWindowManager.Stub final WindowList windows = displayContent.getWindowList(); for (int i = windows.size() - 1; i >= 0; i--) { WindowState w = windows.get(i); + // Discard surface after orientation change, these can't be reused. + if (w.mAppToken != null) { + w.mAppToken.destroySavedSurfaces(); + } if (w.mHasSurface) { if (DEBUG_ORIENTATION) Slog.v(TAG, "Set mOrientationChanging of " + w); w.mOrientationChanging = true; @@ -6972,7 +7020,7 @@ public class WindowManagerService extends IWindowManager.Stub + " asbinder=" + window.asBinder()); } - final int callerPid = Binder.getCallingPid(); + final int callerUid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); IBinder token = null; @@ -6997,6 +7045,7 @@ public class WindowManagerService extends IWindowManager.Stub final IBinder winBinder = window.asBinder(); token = new Binder(); mDragState = new DragState(this, token, surface, flags, winBinder); + mDragState.mUid = callerUid; token = mDragState.mToken = new Binder(); // 5 second timeout for this window to actually begin the drag @@ -8346,7 +8395,7 @@ public class WindowManagerService extends IWindowManager.Stub final int numTokens = tokens.size(); for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) { final AppWindowToken wtoken = tokens.get(tokenNdx); - if (wtoken.mIsExiting && !wtoken.mReplacingWindow) { + if (wtoken.mIsExiting && !wtoken.mWillReplaceWindow) { continue; } i = reAddAppWindowsLocked(displayContent, i, wtoken); @@ -8416,7 +8465,8 @@ public class WindowManagerService extends IWindowManager.Stub } else if (wtoken != null) { winAnimator.mAnimLayer = w.mLayer + wtoken.mAppAnimator.animLayerAdjustment; - if (wtoken.mReplacingWindow && wtoken.mAnimateReplacingWindow) { + if (wtoken.mWillReplaceWindow && wtoken.mAnimateReplacingWindow && + wtoken.mReplacingWindow != w) { // We know that we will be animating a relaunching window in the near future, // which will receive a z-order increase. We want the replaced window to // immediately receive the same treatment, e.g. to be above the dock divider. @@ -8723,12 +8773,14 @@ public class WindowManagerService extends IWindowManager.Stub } else if (ws.mAppToken != null && ws.mAppToken.clientHidden) { Slog.w(TAG, "LEAKED SURFACE (app token hidden): " + ws + " surface=" + wsa.mSurfaceControl - + " token=" + ws.mAppToken); + + " token=" + ws.mAppToken + + " saved=" + ws.mAppToken.mHasSavedSurface); if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null); wsa.mSurfaceControl.destroy(); wsa.mSurfaceShown = false; wsa.mSurfaceControl = null; ws.mHasSurface = false; + ws.mAppToken.mHasSavedSurface = false; leakedSurface = true; } } @@ -9962,7 +10014,9 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Attempted to set replacing window on non-existing app token " + token); return; } - appWindowToken.mReplacingWindow = true; + if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Marking app token " + appWindowToken + + " as replacing window."); + appWindowToken.mWillReplaceWindow = true; appWindowToken.mAnimateReplacingWindow = animate; } } @@ -10168,8 +10222,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public boolean isStackVisible(int stackId) { synchronized (mWindowMap) { - final TaskStack stack = mStackIdToStack.get(stackId); - return (stack != null && stack.isVisibleLocked()); + return WindowManagerService.this.isStackVisibleLocked(stackId); } } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 7ea64f153061..1d2cb7535b97 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -28,6 +28,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE; +import static com.android.server.wm.WindowManagerService.DEBUG_ANIM; +import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS; import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION; import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT; import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION; @@ -35,6 +38,7 @@ import static com.android.server.wm.WindowManagerService.DEBUG_POWER; import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE; import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY; +import android.app.ActivityManager; import android.app.AppOpsManager; import android.graphics.Point; import android.os.PowerManager; @@ -55,7 +59,6 @@ import android.content.res.Configuration; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Rect; -import android.graphics.RectF; import android.graphics.Region; import android.os.IBinder; import android.os.RemoteException; @@ -446,7 +449,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { + WindowManagerService.TYPE_LAYER_OFFSET; mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type); mAttachedWindow = attachedWindow; - if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + mAttachedWindow); + if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + + mAttachedWindow); final WindowList childWindows = mAttachedWindow.mChildWindows; final int numChildWindows = childWindows.size(); @@ -552,7 +556,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { @Override public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf, Rect osf) { - if (mAppToken != null && mAppToken.mReplacingWindow + if (mAppToken != null && mAppToken.mWillReplaceWindow && (mExiting || !mAppToken.mReplacingRemoveRequested)) { // This window is being replaced and either already got information that it's being // removed or we are still waiting for some information. Because of this we don't @@ -1307,10 +1311,12 @@ final class WindowState implements WindowManagerPolicy.WindowState { void maybeRemoveReplacedWindow() { AppWindowToken token = mAppToken; - if (token != null && token.mReplacingWindow) { - token.mReplacingWindow = false; + if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == this) { + if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this); + token.mWillReplaceWindow = false; token.mAnimateReplacingWindow = false; token.mReplacingRemoveRequested = false; + token.mReplacingWindow = null; for (int i = token.allAppWindows.size() - 1; i >= 0; i--) { WindowState win = token.allAppWindows.get(i); if (win.mExiting) { @@ -1501,6 +1507,28 @@ final class WindowState implements WindowManagerPolicy.WindowState { return mExiting || (mService.mClosingApps.contains(mAppToken)); } + boolean isAnimatingWithSavedSurface() { + return mAppToken != null && mAppToken.mAnimatingWithSavedSurface; + } + + boolean shouldSaveSurface() { + if (ActivityManager.isLowRamDeviceStatic()) { + // Don't save surfaces on Svelte devices. + return false; + } + + Task task = getTask(); + if (task == null || task.inHomeStack() + || task.getTopAppWindowToken() != mAppToken) { + // Don't save surfaces for home stack apps. These usually resume and draw + // first frame very fast. Saving surfaces are mostly a waste of memory. + // Don't save if the window is not the topmost window. + return false; + } + + return mAppToken.shouldSaveSurface(); + } + @Override public boolean isDefaultDisplay() { final DisplayContent displayContent = getDisplayContent(); diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 1754123d0546..80f1094d61a6 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -38,7 +38,6 @@ import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; -import android.graphics.RectF; import android.graphics.Region; import android.os.Debug; import android.os.RemoteException; @@ -526,6 +525,12 @@ class WindowStateAnimator { Slog.v(TAG, "Finishing drawing window " + mWin + ": mDrawState=" + drawStateToString()); } + if (mWin.mAppToken != null) { + // App has drawn something to its windows, we're no longer animating with + // the saved surfaces. If the user exits now, we only want to save again + // if allDrawn is true. + mWin.mAppToken.mAnimatingWithSavedSurface = false; + } if (mDrawState == DRAW_PENDING) { if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION) Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + this + " in " @@ -536,6 +541,7 @@ class WindowStateAnimator { mDrawState = COMMIT_DRAW_PENDING; return true; } + return false; } @@ -555,7 +561,8 @@ class WindowStateAnimator { mDrawState = READY_TO_SHOW; boolean result = false; final AppWindowToken atoken = mWin.mAppToken; - if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) { + if (atoken == null || atoken.allDrawn || atoken.mAnimatingWithSavedSurface || + mWin.mAttrs.type == TYPE_APPLICATION_STARTING) { result = performShowLocked(); } if (mDestroyPreservedSurfaceUponRedraw && result) { @@ -996,12 +1003,16 @@ class WindowStateAnimator { } void destroySurfaceLocked() { - if (mWin.mAppToken != null && mWin == mWin.mAppToken.startingWindow) { - mWin.mAppToken.startingDisplayed = false; + final AppWindowToken wtoken = mWin.mAppToken; + if (wtoken != null) { + wtoken.mHasSavedSurface = false; + wtoken.mAnimatingWithSavedSurface = false; + if (mWin == wtoken.startingWindow) { + wtoken.startingDisplayed = false; + } } if (mSurfaceControl != null) { - int i = mWin.mChildWindows.size(); while (i > 0) { i--; @@ -1428,7 +1439,7 @@ class WindowStateAnimator { // living in a different stack. If we suddenly crop it to the new stack bounds, it might // get cut off. We don't want it to happen, so we let it ignore the stack bounds until it // gets removed. The window that will replace it will abide them. - if (appToken != null && appToken.mCropWindowsToStack && !appToken.mReplacingWindow) { + if (appToken != null && appToken.mCropWindowsToStack && !appToken.mWillReplaceWindow) { TaskStack stack = w.getTask().mStack; stack.getBounds(mTmpStackBounds); // When we resize we use the big surface approach, which means we can't trust the @@ -1854,8 +1865,7 @@ class WindowStateAnimator { } } - if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING - && mWin.mAppToken != null) { + if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING && mWin.mAppToken != null) { mWin.mAppToken.firstWindowDrawn = true; if (mWin.mAppToken.startingData != null) { diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index 0c004b2046a2..aca0f5b6de39 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -17,6 +17,7 @@ import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.WindowManagerService.DEBUG; import static com.android.server.wm.WindowManagerService.DEBUG_ADD_REMOVE; +import static com.android.server.wm.WindowManagerService.DEBUG_ANIM; import static com.android.server.wm.WindowManagerService.DEBUG_APP_TRANSITIONS; import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT; import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT_REPEATS; @@ -111,13 +112,34 @@ class WindowSurfacePlacer { private int mPreferredModeId = 0; private boolean mTraversalScheduled; + private int mDeferDepth = 0; public WindowSurfacePlacer(WindowManagerService service) { mService = service; mWallpaperControllerLocked = mService.mWallpaperControllerLocked; } + /** + * See {@link WindowManagerService#deferSurfaceLayout()} + */ + void deferLayout() { + mDeferDepth++; + } + + /** + * See {@link WindowManagerService#continueSurfaceLayout()} + */ + void continueLayout() { + mDeferDepth--; + if (mDeferDepth <= 0) { + performSurfacePlacement(); + } + } + final void performSurfacePlacement() { + if (mDeferDepth > 0) { + return; + } int loopCount = 6; do { mTraversalScheduled = false; @@ -340,6 +362,10 @@ class WindowSurfacePlacer { // Don't remove this window until rotation has completed. continue; } + // Discard the saved surface if window size is changed, it can't be reused. + if (win.mAppToken != null) { + win.mAppToken.destroySavedSurfaces(); + } win.reportResized(); mService.mResizingWindows.remove(i); } @@ -371,7 +397,9 @@ class WindowSurfacePlacer { if (mWallpaperControllerLocked.isWallpaperTarget(win)) { wallpaperDestroyed = true; } - win.mWinAnimator.destroySurfaceLocked(); + if (!win.shouldSaveSurface()) { + win.mWinAnimator.destroySurfaceLocked(); + } } while (i > 0); mService.mDestroySurface.clear(); } @@ -1163,7 +1191,10 @@ class WindowSurfacePlacer { if (animLp != null) { int layer = -1; for (int j = 0; j < wtoken.windows.size(); j++) { - WindowState win = wtoken.windows.get(j); + final WindowState win = wtoken.windows.get(j); + // Clearing the mExiting flag before entering animation. It will be set + // to true if app window is removed, or window relayout to invisible. + win.mExiting = false; if (win.mWinAnimator.mAnimLayer > layer) { layer = win.mWinAnimator.mAnimLayer; } @@ -1174,6 +1205,8 @@ class WindowSurfacePlacer { } } createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer); + + wtoken.restoreSavedSurfaces(); } AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ? null : @@ -1219,6 +1252,10 @@ class WindowSurfacePlacer { + wtoken.allDrawn + " startingDisplayed=" + wtoken.startingDisplayed + " startingMoved=" + wtoken.startingMoved); + + if (wtoken.mHasSavedSurface || wtoken.mAnimatingWithSavedSurface) { + continue; + } if (!wtoken.allDrawn && !wtoken.startingDisplayed && !wtoken.startingMoved) { return false; } diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp index 89b2a47d73c6..01acdeffb853 100644 --- a/services/core/jni/com_android_server_tv_TvInputHal.cpp +++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp @@ -414,12 +414,9 @@ int JTvInputHal::removeStream(int deviceId, int streamId) { return NO_ERROR; } if (Surface::isValid(connection.mSurface)) { - connection.mSurface.clear(); - } - if (connection.mSurface != NULL) { connection.mSurface->setSidebandStream(NULL); - connection.mSurface.clear(); } + connection.mSurface.clear(); if (connection.mThread != NULL) { connection.mThread->shutdown(); connection.mThread.clear(); @@ -616,6 +613,9 @@ static int nativeAddOrUpdateStream(JNIEnv* env, jclass clazz, return BAD_VALUE; } sp<Surface> surface(android_view_Surface_getSurface(env, jsurface)); + if (!Surface::isValid(surface)) { + return BAD_VALUE; + } return tvInputHal->addOrUpdateStream(deviceId, streamId, surface); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 8385685cfcdb..b4c8f966ba71 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -16,6 +16,8 @@ package com.android.server.devicepolicy; +import com.google.android.collect.Sets; + import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; @@ -88,6 +90,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.os.UserManagerInternal; import android.os.storage.StorageManager; import android.provider.ContactsContract.QuickContact; import android.provider.ContactsInternal; @@ -183,8 +186,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final int MONITORING_CERT_NOTIFICATION_ID = R.string.ssl_ca_cert_warning; private static final int PROFILE_WIPED_NOTIFICATION_ID = 1001; - private static final boolean DBG = false; - private static final String ATTR_PERMISSION_PROVIDER = "permission-provider"; private static final String ATTR_SETUP_COMPLETE = "setup-complete"; private static final String ATTR_PERMISSION_POLICY = "permission-policy"; @@ -274,6 +275,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final Injector mInjector; final IPackageManager mIPackageManager; final UserManager mUserManager; + final UserManagerInternal mUserManagerInternal; final LocalService mLocalService; @@ -357,8 +359,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { getSendingUserId()); if (Intent.ACTION_BOOT_COMPLETED.equals(action) || ACTION_EXPIRED_PASSWORD_NOTIFICATION.equals(action)) { - if (DBG) Slog.v(LOG_TAG, "Sending password expiration notifications for action " - + action + " for user " + userHandle); + if (VERBOSE_LOG) { + Slog.v(LOG_TAG, "Sending password expiration notifications for action " + + action + " for user " + userHandle); + } mHandler.post(new Runnable() { @Override public void run() { @@ -1014,7 +1018,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void handlePackagesChanged(String packageName, int userHandle) { boolean removed = false; - if (DBG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle); + if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle); DevicePolicyData policy = getUserData(userHandle); synchronized (this) { for (int i = policy.mAdminList.size() - 1; i >= 0; i--) { @@ -1079,6 +1083,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return UserManager.get(mContext); } + UserManagerInternal getUserManagerInternal() { + return LocalServices.getService(UserManagerInternal.class); + } + NotificationManager getNotificationManager() { return mContext.getSystemService(NotificationManager.class); } @@ -1233,6 +1241,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mOwners = Preconditions.checkNotNull(injector.newOwners()); mUserManager = Preconditions.checkNotNull(injector.getUserManager()); + mUserManagerInternal = Preconditions.checkNotNull(injector.getUserManagerInternal()); mIPackageManager = Preconditions.checkNotNull(injector.getIPackageManager()); mLocalService = new LocalService(); @@ -1327,10 +1336,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { mOwners.load(); findOwnerComponentIfNecessaryLocked(); + migrateUserRestrictionsIfNecessaryLocked(); // TODO PO may not have a class name either due to b/17652534. Address that too. updateDeviceOwnerLocked(); + + // TODO Notify UM to update restrictions (?) } } @@ -1350,12 +1362,111 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (doComponent == null) { Slog.e(LOG_TAG, "Device-owner isn't registered as device-admin"); } else { - mOwners.setDeviceOwner( + mOwners.setDeviceOwnerWithRestrictionsMigrated( doComponent, mOwners.getDeviceOwnerName(), - mOwners.getDeviceOwnerUserId()); + mOwners.getDeviceOwnerUserId(), + !mOwners.getDeviceOwnerUserRestrictionsNeedsMigration()); mOwners.writeDeviceOwner(); + if (VERBOSE_LOG) { + Log.v(LOG_TAG, "Device owner component filled in"); + } + } + } + + /** + * We didn't use to persist user restrictions for each owners but only persisted in user + * manager. + */ + private void migrateUserRestrictionsIfNecessaryLocked() { + boolean migrated = false; + // Migrate for the DO. Basically all restrictions should be considered to be set by DO, + // except for the "system controlled" ones. + if (mOwners.getDeviceOwnerUserRestrictionsNeedsMigration()) { + if (VERBOSE_LOG) { + Log.v(LOG_TAG, "Migrating DO user restrictions"); + } + migrated = true; + + // Migrate user 0 restrictions to DO, except for "system" restrictions. + final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked(); + + migrateUserRestrictionsForUser(UserHandle.SYSTEM, deviceOwnerAdmin, + /* exceptionList =*/ UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS); + + mOwners.setDeviceOwnerUserRestrictionsMigrated(); + } + + // Migrate for POs. We have a few more exceptions. + final Set<String> normalExceptionList = Sets.newArraySet( + UserManager.DISALLOW_OUTGOING_CALLS, + UserManager.DISALLOW_SMS); + normalExceptionList.addAll(UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS); + + final Set<String> managedExceptionList = new ArraySet<>(normalExceptionList.size() + 1); + managedExceptionList.addAll(normalExceptionList); + managedExceptionList.add(UserManager.DISALLOW_WALLPAPER); + + for (UserInfo ui : mUserManager.getUsers()) { + final int userId = ui.id; + if (mOwners.getProfileOwnerUserRestrictionsNeedsMigration(userId)) { + if (userId != UserHandle.USER_SYSTEM) { + if (VERBOSE_LOG) { + Log.v(LOG_TAG, "Migrating PO user restrictions for user " + userId); + } + migrated = true; + + final ActiveAdmin profileOwnerAdmin = getProfileOwnerAdminLocked(userId); + + final Set<String> exceptionList = + ui.isManagedProfile() ? managedExceptionList : normalExceptionList; + + migrateUserRestrictionsForUser(ui.getUserHandle(), profileOwnerAdmin, + exceptionList); + } + + mOwners.setProfileOwnerUserRestrictionsMigrated(userId); + } + } + if (VERBOSE_LOG && migrated) { + Log.v(LOG_TAG, "User restrictions migrated."); + } + } + + private void migrateUserRestrictionsForUser(UserHandle user, ActiveAdmin admin, + Set<String> exceptionList) { + final Bundle origRestrictions = mUserManagerInternal.getBaseUserRestrictions( + user.getIdentifier()); + + final Bundle newSystemRestrictions = new Bundle(); + final Bundle newOwnerRestrictions = new Bundle(); + + for (String key : origRestrictions.keySet()) { + if (!origRestrictions.getBoolean(key)) { + continue; + } + if (exceptionList.contains(key)) { + newSystemRestrictions.putBoolean(key, true); + } else { + newOwnerRestrictions.putBoolean(key, true); + } } + + if (VERBOSE_LOG) { + Log.v(LOG_TAG, "origRestrictions=" + origRestrictions); + Log.v(LOG_TAG, "newSystemRestrictions=" + newSystemRestrictions); + Log.v(LOG_TAG, "newOwnerRestrictions=" + newOwnerRestrictions); + } + mUserManagerInternal.setBaseUserRestrictionsByDpmsForMigration(user.getIdentifier(), + newSystemRestrictions); + + if (admin != null) { + admin.ensureUserRestrictions().clear(); + admin.ensureUserRestrictions().putAll(newOwnerRestrictions); + } else { + Slog.w(LOG_TAG, "ActiveAdmin for DO/PO not found. user=" + user.getIdentifier()); + } + saveSettingsLocked(user.getIdentifier()); } private ComponentName findAdminComponentWithPackageLocked(String packageName, int userId) { @@ -1373,7 +1484,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { nFound++; } } - if (nFound > 0) { + if (nFound > 1) { Slog.w(LOG_TAG, "Multiple DA found; assume the first one is DO."); } return found; @@ -1636,6 +1747,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { ? mInjector.getDevicePolicyFilePathForSystemUser() + DEVICE_POLICIES_XML : new File(mInjector.environmentGetUserSystemDirectory(userHandle), DEVICE_POLICIES_XML).getAbsolutePath(); + if (VERBOSE_LOG) { + Log.v(LOG_TAG, "Opening " + base); + } return new JournaledFile(new File(base), new File(base + ".tmp")); } @@ -1808,7 +1922,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { try { DeviceAdminInfo dai = findAdmin( ComponentName.unflattenFromString(name), userHandle); - if (DBG && (UserHandle.getUserId(dai.getActivityInfo().applicationInfo.uid) + if (VERBOSE_LOG + && (UserHandle.getUserId(dai.getActivityInfo().applicationInfo.uid) != userHandle)) { Slog.w(LOG_TAG, "findAdmin returned an incorrect uid " + dai.getActivityInfo().applicationInfo.uid + " for user " @@ -1988,8 +2103,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long token = mInjector.binderClearCallingIdentity(); try { String value = cameraDisabled ? "1" : "0"; - if (DBG) Slog.v(LOG_TAG, "Change in camera state [" - + cameraPropertyForUser + "] = " + value); + if (VERBOSE_LOG) { + Slog.v(LOG_TAG, "Change in camera state [" + + cameraPropertyForUser + "] = " + value); + } mInjector.systemPropertiesSet(cameraPropertyForUser, value); } finally { mInjector.binderRestoreCallingIdentity(token); @@ -4513,8 +4630,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } UserHandle callingUser = mInjector.binderGetCallingUserHandle(); // Check if this is the profile owner who is calling - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + final ActiveAdmin admin = + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); synchronized (this) { + admin.userRestrictions = null; clearUserPoliciesLocked(callingUser); final int userId = callingUser.getIdentifier(); mOwners.removeProfileOwner(userId); @@ -4533,38 +4652,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final long ident = mInjector.binderClearCallingIdentity(); try { - clearUserRestrictions(userHandle); mIPackageManager.updatePermissionFlagsForAllApps( PackageManager.FLAG_PERMISSION_POLICY_FIXED, 0 /* flagValues */, userHandle.getIdentifier()); + // TODO This will not revert audio mute restrictions if they were set. b/24981972 + synchronized (mUserManagerInternal.getUserRestrictionsLock()) { + mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle.getIdentifier()); + } } catch (RemoteException re) { } finally { mInjector.binderRestoreCallingIdentity(ident); } } - - private void clearUserRestrictions(UserHandle userHandle) { - Bundle userRestrictions = mUserManager.getUserRestrictions(); - mUserManager.setUserRestrictions(new Bundle(), userHandle); - if (userRestrictions.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME)) { - try { - mInjector.getIAudioService().setMasterMute(true, 0, mContext.getPackageName(), - userHandle.getIdentifier()); - } catch (RemoteException e) { - // Not much we can do here. - } - } - if (userRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE)) { - try { - mInjector.getIAudioService().setMicrophoneMute(true, mContext.getPackageName(), - userHandle.getIdentifier()); - } catch (RemoteException e) { - // Not much we can do here. - } - } - } - @Override public boolean hasUserSetupCompleted() { return hasUserSetupCompleted(UserHandle.getCallingUserId()); @@ -5503,95 +5603,123 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public void setUserRestriction(ComponentName who, String key, boolean enabled) { + public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) { Preconditions.checkNotNull(who, "ComponentName is null"); final int userHandle = mInjector.userHandleGetCallingUserId(); final UserHandle user = new UserHandle(userHandle); - synchronized (this) { - ActiveAdmin activeAdmin = - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - boolean isDeviceOwner = isDeviceOwner(who); - if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM - && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) { - throw new SecurityException("Profile owners cannot set user restriction " + key); - } - if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) { - throw new SecurityException("User restriction " + key + " cannot be changed"); - } - boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user); + synchronized (mUserManagerInternal.getUserRestrictionsLock()) { + synchronized (this) { + ActiveAdmin activeAdmin = + getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + boolean isDeviceOwner = isDeviceOwner(who); + if (!isDeviceOwner && userHandle != UserHandle.USER_SYSTEM + && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) { + throw new SecurityException( + "Profile owners cannot set user restriction " + key); + } + if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) { + throw new SecurityException("User restriction " + key + " cannot be changed"); + } - long id = mInjector.binderClearCallingIdentity(); - try { - if (enabled && !alreadyRestricted) { - if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { - mInjector.getIAudioService() - .setMicrophoneMute(true, mContext.getPackageName(), userHandle); - } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { - mInjector.getIAudioService() - .setMasterMute(true, 0, mContext.getPackageName(), userHandle); - } else if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) { - mInjector.settingsSecurePutIntForUser( - Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0, - userHandle); - } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) { - mInjector.settingsSecurePutIntForUser( - Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF, - userHandle); - mInjector.settingsSecurePutStringForUser( - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "", - userHandle); - } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) { - // Only disable adb if changing for system user, since it is global - // TODO: should this be admin user? - if (userHandle == UserHandle.USER_SYSTEM) { + final long id = mInjector.binderClearCallingIdentity(); + try { + // Original value. + final boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user); + + // Save the restriction to ActiveAdmin. + // TODO When DO sets a restriction, it'll always be treated as device-wide. + // If there'll be a policy that can be set by both, we'll need scoping support, + // and need to have another Bundle in DO active admin to hold restrictions as + // PO. + activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner); + saveSettingsLocked(userHandle); + + // Tell UserManager the new value. Note this needs to be done before calling + // into AudioService, because AS will check AppOps that'll be updated by UM. + if (isDeviceOwner) { + mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersRL(); + } else { + mUserManagerInternal.updateEffectiveUserRestrictionsRL(userHandle); + } + + // New value. + final boolean enabled = mUserManager.hasUserRestriction(key, user); + + // TODO The rest of the code should move to UserManagerService. + + if (enabled && !alreadyRestricted) { + if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { + mInjector.getIAudioService() + .setMicrophoneMute(true, mContext.getPackageName(), userHandle); + } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { + mInjector.getIAudioService() + .setMasterMute(true, 0, mContext.getPackageName(), userHandle); + } else if (UserManager.DISALLOW_CONFIG_WIFI.equals(key)) { + mInjector.settingsSecurePutIntForUser( + Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0, + userHandle); + } else if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) { + mInjector.settingsSecurePutIntForUser( + Settings.Secure.LOCATION_MODE, + Settings.Secure.LOCATION_MODE_OFF, + userHandle); + mInjector.settingsSecurePutStringForUser( + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "", + userHandle); + } else if (UserManager.DISALLOW_DEBUGGING_FEATURES.equals(key)) { + // Only disable adb if changing for system user, since it is global + // TODO: should this be admin user? + if (userHandle == UserHandle.USER_SYSTEM) { + mInjector.settingsGlobalPutStringForUser( + Settings.Global.ADB_ENABLED, "0", userHandle); + } + } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) { mInjector.settingsGlobalPutStringForUser( - Settings.Global.ADB_ENABLED, "0", userHandle); + Settings.Global.PACKAGE_VERIFIER_ENABLE, "1", + userHandle); + mInjector.settingsGlobalPutStringForUser( + Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1", + userHandle); + } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) { + mInjector.settingsSecurePutIntForUser( + Settings.Secure.INSTALL_NON_MARKET_APPS, 0, + userHandle); } - } else if (UserManager.ENSURE_VERIFY_APPS.equals(key)) { - mInjector.settingsGlobalPutStringForUser( - Settings.Global.PACKAGE_VERIFIER_ENABLE, "1", - userHandle); - mInjector.settingsGlobalPutStringForUser( - Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, "1", - userHandle); - } else if (UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES.equals(key)) { - mInjector.settingsSecurePutIntForUser( - Settings.Secure.INSTALL_NON_MARKET_APPS, 0, - userHandle); } - } - mUserManager.setUserRestriction(key, enabled, user); - activeAdmin.ensureUserRestrictions().putBoolean(key, enabled); - saveSettingsLocked(userHandle); - if (enabled != alreadyRestricted) { - if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) { - // Send out notifications however as some clients may want to reread the - // value which actually changed due to a restriction having been applied. - final String property = Settings.Secure.SYS_PROP_SETTING_VERSION; - long version = mInjector.systemPropertiesGetLong(property, 0) + 1; - mInjector.systemPropertiesSet(property, Long.toString(version)); - - final String name = Settings.Secure.LOCATION_PROVIDERS_ALLOWED; - Uri url = Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name); - mContext.getContentResolver().notifyChange(url, null, true, userHandle); + if (enabled != alreadyRestricted) { + if (UserManager.DISALLOW_SHARE_LOCATION.equals(key)) { + // Send out notifications however as some clients may want to reread the + // value which actually changed due to a restriction having been + // applied. + final String property = Settings.Secure.SYS_PROP_SETTING_VERSION; + long version = mInjector.systemPropertiesGetLong(property, 0) + 1; + mInjector.systemPropertiesSet(property, Long.toString(version)); + + final String name = Settings.Secure.LOCATION_PROVIDERS_ALLOWED; + Uri url = Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name); + mContext.getContentResolver().notifyChange(url, null, true, userHandle); + } } - } - if (!enabled && alreadyRestricted) { - if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { - mInjector.getIAudioService() - .setMicrophoneMute(false, mContext.getPackageName(), userHandle); - } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { - mInjector.getIAudioService() - .setMasterMute(false, 0, mContext.getPackageName(), userHandle); + if (!enabled && alreadyRestricted) { + if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { + mInjector.getIAudioService() + .setMicrophoneMute(false, mContext.getPackageName(), + userHandle); + } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { + mInjector.getIAudioService() + .setMasterMute(false, 0, mContext.getPackageName(), userHandle); + } } + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Failed to talk to AudioService.", re); + } finally { + mInjector.binderRestoreCallingIdentity(id); } - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Failed to talk to AudioService.", re); - } finally { - mInjector.binderRestoreCallingIdentity(id); + + sendChangedNotification(userHandle); } - sendChangedNotification(userHandle); } } @@ -5650,7 +5778,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long id = mInjector.binderClearCallingIdentity(); try { - if (DBG) { + if (VERBOSE_LOG) { Slog.v(LOG_TAG, "installing " + packageName + " for " + userId); } @@ -5705,7 +5833,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 0, // no flags primaryUser.id); - if (DBG) Slog.d(LOG_TAG, "Enabling system activities: " + activitiesToEnable); + if (VERBOSE_LOG) { + Slog.d(LOG_TAG, "Enabling system activities: " + activitiesToEnable); + } int numberOfAppsInstalled = 0; if (activitiesToEnable != null) { for (ResolveInfo info : activitiesToEnable) { @@ -6275,7 +6405,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private final class LocalService extends DevicePolicyManagerInternal { + @VisibleForTesting + final class LocalService extends DevicePolicyManagerInternal { private List<OnCrossProfileWidgetProvidersChangeListener> mWidgetProviderListeners; @Override @@ -6322,6 +6453,30 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @Override + public Bundle getComposedUserRestrictions(int userId, Bundle inBundle) { + synchronized (DevicePolicyManagerService.this) { + final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId); + + final Bundle deviceOwnerRestrictions = + deviceOwner == null ? null : deviceOwner.userRestrictions; + final Bundle profileOwnerRestrictions = + profileOwner == null ? null : profileOwner.userRestrictions; + + if (deviceOwnerRestrictions == null && profileOwnerRestrictions == null) { + // No restrictions to merge. + return inBundle; + } + + final Bundle composed = new Bundle(inBundle); + UserRestrictionsUtils.merge(composed, deviceOwnerRestrictions); + UserRestrictionsUtils.merge(composed, profileOwnerRestrictions); + + return composed; + } + } + private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) { final List<OnCrossProfileWidgetProvidersChangeListener> listeners; synchronized (DevicePolicyManagerService.this) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 799267d9bf44..12b3775d3071 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -30,7 +30,6 @@ import android.util.Slog; import android.util.Xml; import com.android.internal.util.FastXmlSerializer; -import com.android.internal.util.Preconditions; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -75,6 +74,7 @@ class Owners { private static final String ATTR_PACKAGE = "package"; private static final String ATTR_COMPONENT_NAME = "component"; private static final String ATTR_USERID = "userId"; + private static final String ATTR_USER_RESTRICTIONS_MIGRATED = "userRestrictionsMigrated"; private static final String TAG_SYSTEM_UPDATE_POLICY = "system-update-policy"; @@ -155,7 +155,16 @@ class Owners { Slog.e(TAG, "Invalid user id for device owner user: " + userId); return; } - mDeviceOwner = new OwnerInfo(ownerName, admin); + // For a newly set DO, there's no need for migration. + setDeviceOwnerWithRestrictionsMigrated(admin, ownerName, userId, + /* userRestrictionsMigrated =*/ true); + } + + // Note this should be only called during migration. Normally when DO is set, + // userRestrictionsMigrated should always be true. + void setDeviceOwnerWithRestrictionsMigrated(ComponentName admin, String ownerName, int userId, + boolean userRestrictionsMigrated) { + mDeviceOwner = new OwnerInfo(ownerName, admin, userRestrictionsMigrated); mDeviceOwnerUserId = userId; } @@ -165,7 +174,9 @@ class Owners { } void setProfileOwner(ComponentName admin, String ownerName, int userId) { - mProfileOwners.put(userId, new OwnerInfo(ownerName, admin)); + // For a newly set PO, there's no need for migration. + mProfileOwners.put(userId, new OwnerInfo(ownerName, admin, + /* userRestrictionsMigrated =*/ true)); } void removeProfileOwner(int userId) { @@ -207,6 +218,38 @@ class Owners { return mDeviceOwner != null; } + /** + * @return true if user restrictions need to be migrated for DO. + */ + boolean getDeviceOwnerUserRestrictionsNeedsMigration() { + return mDeviceOwner != null && !mDeviceOwner.userRestrictionsMigrated; + } + + /** + * @return true if user restrictions need to be migrated for PO. + */ + boolean getProfileOwnerUserRestrictionsNeedsMigration(int userId) { + OwnerInfo profileOwner = mProfileOwners.get(userId); + return profileOwner != null && !profileOwner.userRestrictionsMigrated; + } + + /** Sets the user restrictions migrated flag, and also writes to the file. */ + void setDeviceOwnerUserRestrictionsMigrated() { + if (mDeviceOwner != null) { + mDeviceOwner.userRestrictionsMigrated = true; + } + writeDeviceOwner(); + } + + /** Sets the user restrictions migrated flag, and also writes to the file. */ + void setProfileOwnerUserRestrictionsMigrated(int userId) { + OwnerInfo profileOwner = mProfileOwners.get(userId); + if (profileOwner != null) { + profileOwner.userRestrictionsMigrated = true; + } + writeProfileOwner(userId); + } + private boolean readLegacyOwnerFile(File file) { if (!file.exists()) { // Already migrated or the device has no owners. @@ -226,7 +269,8 @@ class Owners { if (tag.equals(TAG_DEVICE_OWNER)) { String name = parser.getAttributeValue(null, ATTR_NAME); String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); - mDeviceOwner = new OwnerInfo(name, packageName); + mDeviceOwner = new OwnerInfo(name, packageName, + /* userRestrictionsMigrated =*/ false); mDeviceOwnerUserId = UserHandle.USER_SYSTEM; } else if (tag.equals(TAG_DEVICE_INITIALIZER)) { // Deprecated tag @@ -241,7 +285,8 @@ class Owners { ComponentName admin = ComponentName.unflattenFromString( profileOwnerComponentStr); if (admin != null) { - profileOwnerInfo = new OwnerInfo(profileOwnerName, admin); + profileOwnerInfo = new OwnerInfo(profileOwnerName, admin, + /* userRestrictionsMigrated =*/ false); } else { // This shouldn't happen but switch from package name -> component name // might have written bad device owner files. b/17652534 @@ -250,7 +295,8 @@ class Owners { } } if (profileOwnerInfo == null) { - profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName); + profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName, + /* userRestrictionsMigrated =*/ false); } mProfileOwners.put(userId, profileOwnerInfo); } else if (TAG_SYSTEM_UPDATE_POLICY.equals(tag)) { @@ -503,21 +549,24 @@ class Owners { } } - private static class OwnerInfo { + static class OwnerInfo { public final String name; public final String packageName; public final ComponentName admin; + public boolean userRestrictionsMigrated; - public OwnerInfo(String name, String packageName) { + public OwnerInfo(String name, String packageName, boolean userRestrictionsMigrated) { this.name = name; this.packageName = packageName; this.admin = new ComponentName(packageName, ""); + this.userRestrictionsMigrated = userRestrictionsMigrated; } - public OwnerInfo(String name, ComponentName admin) { + public OwnerInfo(String name, ComponentName admin, boolean userRestrictionsMigrated) { this.name = name; this.admin = admin; this.packageName = admin.getPackageName(); + this.userRestrictionsMigrated = userRestrictionsMigrated; } public void writeToXml(XmlSerializer out, String tag) throws IOException { @@ -529,6 +578,8 @@ class Owners { if (admin != null) { out.attribute(null, ATTR_COMPONENT_NAME, admin.flattenToString()); } + out.attribute(null, ATTR_USER_RESTRICTIONS_MIGRATED, + String.valueOf(userRestrictionsMigrated)); out.endTag(null, tag); } @@ -537,12 +588,16 @@ class Owners { final String name = parser.getAttributeValue(null, ATTR_NAME); final String componentName = parser.getAttributeValue(null, ATTR_COMPONENT_NAME); + final String userRestrictionsMigratedStr = + parser.getAttributeValue(null, ATTR_USER_RESTRICTIONS_MIGRATED); + final boolean userRestrictionsMigrated = + ("true".equals(userRestrictionsMigratedStr)); // Has component name? If so, return [name, component] if (componentName != null) { final ComponentName admin = ComponentName.unflattenFromString(componentName); if (admin != null) { - return new OwnerInfo(name, admin); + return new OwnerInfo(name, admin, userRestrictionsMigrated); } else { // This shouldn't happen but switch from package name -> component name // might have written bad device owner files. b/17652534 @@ -552,7 +607,7 @@ class Owners { } // Else, build with [name, package] - return new OwnerInfo(name, packageName); + return new OwnerInfo(name, packageName, userRestrictionsMigrated); } public void dump(String prefix, PrintWriter pw) { diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java index 28cb114ccf6b..c9efc69b5979 100644 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ b/services/net/java/android/net/dhcp/DhcpClient.java @@ -417,9 +417,10 @@ public class DhcpClient extends BaseDhcpStateMachine { encap, mTransactionId, getSecs(), clientAddress, DO_UNICAST, mHwAddr, requestedAddress, serverAddress, REQUESTED_PARAMS, null); + String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null; String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() + " request=" + requestedAddress.getHostAddress() + - " to=" + serverAddress.getHostAddress(); + " serverid=" + serverStr; return transmitPacket(packet, description, to); } @@ -822,7 +823,8 @@ public class DhcpClient extends BaseDhcpStateMachine { public void enter() { super.enter(); if (!setIpAddress(mDhcpLease.ipAddress) || - !connectUdpSock((mDhcpLease.serverAddress))) { + (mDhcpLease.serverAddress != null && + !connectUdpSock((mDhcpLease.serverAddress)))) { notifyFailure(); // There's likely no point in going into DhcpInitState here, we'll probably just // repeat the transaction, get the same IP address as before, and fail. @@ -878,11 +880,15 @@ public class DhcpClient extends BaseDhcpStateMachine { } protected boolean sendPacket() { + // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but... + // http://b/25343517 . Try to make things work anyway by using broadcast renews. + Inet4Address to = (mDhcpLease.serverAddress != null) ? + mDhcpLease.serverAddress : INADDR_BROADCAST; return sendRequestPacket( (Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr INADDR_ANY, // DHCP_REQUESTED_IP - INADDR_ANY, // DHCP_SERVER_IDENTIFIER - (Inet4Address) mDhcpLease.serverAddress); // packet destination address + null, // DHCP_SERVER_IDENTIFIER + to); // packet destination address } protected void receivePacket(DhcpPacket packet) { diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java index 53f55cdc847f..88155f715f49 100644 --- a/services/net/java/android/net/ip/IpReachabilityMonitor.java +++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java @@ -403,7 +403,6 @@ public class IpReachabilityMonitor { // TODO: simply the number of objects by making this extend Thread. private final class NetlinkSocketObserver implements Runnable { - private static final String TAG = "NetlinkSocketObserver"; private NetlinkSocket mSocket; @Override diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml new file mode 100644 index 000000000000..9564969ab57c --- /dev/null +++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml @@ -0,0 +1,4 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<device-owner package="com.android.frameworks.servicestests" /> +<profile-owner package="com.android.frameworks.servicestests" name="0" userId="10" component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin2" /> +<profile-owner package="com.android.frameworks.servicestests" name="0" userId="11" component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin3" /> diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml new file mode 100644 index 000000000000..48cb814fa907 --- /dev/null +++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<policies setup-complete="true"> + <admin name="com.google.android.gms/com.google.android.gms.mdm.receivers.MdmDeviceAdminReceiver"> + </admin> + <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"> + </admin> +</policies> diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml new file mode 100644 index 000000000000..6b53840434c1 --- /dev/null +++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml @@ -0,0 +1,5 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<policies setup-complete="true"> + <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin2"> + </admin> +</policies> diff --git a/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml new file mode 100644 index 000000000000..2bcc5d41a7cb --- /dev/null +++ b/services/tests/servicestests/assets/DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml @@ -0,0 +1,5 @@ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<policies setup-complete="true"> + <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin3"> + </admin> +</policies> diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java new file mode 100644 index 000000000000..dfa9f8f383c5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2015 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.server.devicepolicy; + +import com.android.server.LocalServices; +import com.android.server.SystemService; +import com.android.server.devicepolicy.DevicePolicyManagerServiceTestable.OwnersTestable; + +import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManagerInternal; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Pair; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; + +public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { + private DpmMockContext mContext; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mContext = getContext(); + + when(mContext.packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN))) + .thenReturn(true); + } + + public void testMigration() throws Exception { + final File user10dir = mMockContext.addUser(10, 0); + final File user11dir = mMockContext.addUser(11, UserInfo.FLAG_MANAGED_PROFILE); + final File user12dir = mMockContext.addUser(12, 0); + + setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID); + setUpPackageManagerForAdmin(admin2, UserHandle.getUid(10, 123)); + setUpPackageManagerForAdmin(admin3, UserHandle.getUid(11, 456)); + + // Create the legacy owners & policies file. + DpmTestUtils.writeToFile( + (new File(mContext.dataDir, OwnersTestable.LEGACY_FILE)).getAbsoluteFile(), + DpmTestUtils.readAsset(mRealTestContext, + "DevicePolicyManagerServiceMigrationTest/legacy_device_owner.xml")); + + DpmTestUtils.writeToFile( + (new File(mContext.systemUserDataDir, "device_policies.xml")).getAbsoluteFile(), + DpmTestUtils.readAsset(mRealTestContext, + "DevicePolicyManagerServiceMigrationTest/legacy_device_policies.xml")); + + DpmTestUtils.writeToFile( + (new File(user10dir, "device_policies.xml")).getAbsoluteFile(), + DpmTestUtils.readAsset(mRealTestContext, + "DevicePolicyManagerServiceMigrationTest/legacy_device_policies_10.xml")); + DpmTestUtils.writeToFile( + (new File(user11dir, "device_policies.xml")).getAbsoluteFile(), + DpmTestUtils.readAsset(mRealTestContext, + "DevicePolicyManagerServiceMigrationTest/legacy_device_policies_11.xml")); + + // Set up UserManager + when(mMockContext.userManagerInternal.getBaseUserRestrictions( + eq(UserHandle.USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions( + UserManager.DISALLOW_ADD_USER, + UserManager.DISALLOW_RECORD_AUDIO)); + + when(mMockContext.userManagerInternal.getBaseUserRestrictions( + eq(10))).thenReturn(DpmTestUtils.newRestrictions( + UserManager.DISALLOW_REMOVE_USER, + UserManager.DISALLOW_SMS, + UserManager.DISALLOW_OUTGOING_CALLS, + UserManager.DISALLOW_WALLPAPER, + UserManager.DISALLOW_RECORD_AUDIO)); + + when(mMockContext.userManagerInternal.getBaseUserRestrictions( + eq(11))).thenReturn(DpmTestUtils.newRestrictions( + UserManager.DISALLOW_REMOVE_USER, + UserManager.DISALLOW_SMS, + UserManager.DISALLOW_OUTGOING_CALLS, + UserManager.DISALLOW_WALLPAPER, + UserManager.DISALLOW_RECORD_AUDIO)); + + final Map<Integer, Bundle> newBaseRestrictions = new HashMap<>(); + + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + Integer userId = (Integer) invocation.getArguments()[0]; + Bundle bundle = (Bundle) invocation.getArguments()[1]; + + newBaseRestrictions.put(userId, bundle); + + return null; + } + }).when(mContext.userManagerInternal).setBaseUserRestrictionsByDpmsForMigration( + anyInt(), any(Bundle.class)); + + // Initialize DPM/DPMS and let it migrate the persisted information. + // (Need clearCallingIdentity() to pass permission checks.) + + final DevicePolicyManagerServiceTestable dpms; + + final long ident = mContext.binder.clearCallingIdentity(); + try { + LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + + dpms = new DevicePolicyManagerServiceTestable(mContext, dataDir); + + dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY); + dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED); + } finally { + mContext.binder.restoreCallingIdentity(ident); + } + + // Now all information should be migrated. + assertFalse(dpms.mOwners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(12)); + + // Check the new base restrictions. + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions( + UserManager.DISALLOW_RECORD_AUDIO + ), + newBaseRestrictions.get(UserHandle.USER_SYSTEM)); + + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions( + UserManager.DISALLOW_SMS, + UserManager.DISALLOW_OUTGOING_CALLS, + UserManager.DISALLOW_RECORD_AUDIO + ), + newBaseRestrictions.get(10)); + + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions( + UserManager.DISALLOW_SMS, + UserManager.DISALLOW_OUTGOING_CALLS, + UserManager.DISALLOW_WALLPAPER, + UserManager.DISALLOW_RECORD_AUDIO + ), + newBaseRestrictions.get(11)); + + // Check the new owner restrictions. + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions( + UserManager.DISALLOW_ADD_USER + ), + dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()); + + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions( + UserManager.DISALLOW_REMOVE_USER, + UserManager.DISALLOW_WALLPAPER + ), + dpms.getProfileOwnerAdminLocked(10).ensureUserRestrictions()); + + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions( + UserManager.DISALLOW_REMOVE_USER + ), + dpms.getProfileOwnerAdminLocked(11).ensureUserRestrictions()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index b109e7bc9d03..2c01b8aaac12 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -27,6 +27,7 @@ import android.os.Looper; import android.os.PowerManagerInternal; import android.os.UserHandle; import android.os.UserManager; +import android.os.UserManagerInternal; import android.view.IWindowManager; import java.io.File; @@ -107,6 +108,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @Override + UserManagerInternal getUserManagerInternal() { + return context.userManagerInternal; + } + + @Override PowerManagerInternal getPowerManagerInternal() { return context.powerManagerInternal; } @@ -153,7 +159,7 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi @Override String getDevicePolicyFilePathForSystemUser() { - return context.systemUserDataDir.getAbsolutePath(); + return context.systemUserDataDir.getAbsolutePath() + "/"; } @Override diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index d6a60c7f3e65..727858b77f41 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -27,7 +27,6 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; @@ -70,9 +69,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { private DpmMockContext mContext; public DevicePolicyManager dpm; public DevicePolicyManagerServiceTestable dpms; - public ComponentName admin1; - public ComponentName admin2; - public ComponentName admin3; @Override protected void setUp() throws Exception { @@ -85,10 +81,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { initializeDpms(); - admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class); - admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class); - admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class); - setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID); setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID); setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_UID); @@ -113,67 +105,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { } } - private void setUpPackageManagerForAdmin(ComponentName admin, int packageUid) throws Exception { - setUpPackageManagerForAdmin(admin, packageUid, - PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED); - } - - private void setUpPackageManagerForAdmin(ComponentName admin, int packageUid, - int enabledSetting) throws Exception { - - // Set up queryBroadcastReceivers(). - - final Intent resolveIntent = new Intent(); - resolveIntent.setComponent(admin); - final List<ResolveInfo> realResolveInfo = - mRealTestContext.getPackageManager().queryBroadcastReceivers( - resolveIntent, - PackageManager.GET_META_DATA); - assertNotNull(realResolveInfo); - assertEquals(1, realResolveInfo.size()); - - // We need to change AI, so set a clone. - realResolveInfo.set(0, DpmTestUtils.cloneParcelable(realResolveInfo.get(0))); - - // We need to rewrite the UID in the activity info. - realResolveInfo.get(0).activityInfo.applicationInfo.uid = packageUid; - - doReturn(realResolveInfo).when(mContext.packageManager).queryBroadcastReceivers( - MockUtils.checkIntentComponent(admin), - eq(PackageManager.GET_META_DATA - | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS), - eq(UserHandle.getUserId(packageUid))); - - // Set up getApplicationInfo(). - - final ApplicationInfo ai = DpmTestUtils.cloneParcelable( - mRealTestContext.getPackageManager().getApplicationInfo( - admin1.getPackageName(), - PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS)); - - ai.enabledSetting = enabledSetting; - ai.uid = packageUid; - - doReturn(ai).when(mContext.ipackageManager).getApplicationInfo( - eq(admin1.getPackageName()), - eq(PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS), - eq(UserHandle.getUserId(packageUid))); - - // Set up getPackageInfo(). - - final PackageInfo pi = DpmTestUtils.cloneParcelable( - mRealTestContext.getPackageManager().getPackageInfo( - admin1.getPackageName(), 0)); - assertTrue(pi.applicationInfo.flags != 0); - - pi.applicationInfo.uid = packageUid; - - doReturn(pi).when(mContext.ipackageManager).getPackageInfo( - eq(admin1.getPackageName()), - eq(0), - eq(UserHandle.getUserId(packageUid))); - } - private void setUpUserManager() { // Emulate UserManager.set/getApplicationRestriction(). final Map<Pair<String, UserHandle>, Bundle> appRestrictions = new HashMap<>(); @@ -220,7 +151,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertTrue(dpm.setProfileOwner(admin, "owner-name", DpmMockContext.CALLER_USER_HANDLE)); // Check - assertEquals(admin1, dpm.getProfileOwnerAsUser(DpmMockContext.CALLER_USER_HANDLE)); + assertEquals(admin, dpm.getProfileOwnerAsUser(DpmMockContext.CALLER_USER_HANDLE)); } public void testHasNoFeature() throws Exception { @@ -743,6 +674,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertEquals("", dpms.getDeviceOwner().getClassName()); // Then create a new DPMS to have it load the settings from files. + when(mContext.userManager.getUserRestrictions(any(UserHandle.class))) + .thenReturn(new Bundle()); initializeDpms(); // Now the DO component name is a full name. @@ -802,32 +735,33 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertTrue(dpm.setDeviceOwner(admin1, "owner-name", UserHandle.USER_SYSTEM)); - assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_SMS)); - assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions(), + dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions() + ); dpm.addUserRestriction(admin1, UserManager.DISALLOW_SMS); dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS); - assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_SMS)); - assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions( + UserManager.DISALLOW_SMS, UserManager.DISALLOW_OUTGOING_CALLS), + dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions() + ); dpm.clearUserRestriction(admin1, UserManager.DISALLOW_SMS); - assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_SMS)); - assertTrue(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS), + dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions() + ); dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS); - assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_SMS)); - assertFalse(dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions(), + dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions() + ); // TODO Check inner calls. // TODO Make sure restrictions are written to the file. @@ -836,42 +770,106 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testSetUserRestriction_asPo() { setAsProfileOwner(admin1); - assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) - .ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES)); - assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) - .ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions(), + dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) + .ensureUserRestrictions() + ); dpm.addUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES); dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS); - assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) - .ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES)); - assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) - .ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, + UserManager.DISALLOW_OUTGOING_CALLS + ), + dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) + .ensureUserRestrictions() + ); dpm.clearUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES); - assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) - .ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES)); - assertTrue(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) - .ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)); + + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions( + UserManager.DISALLOW_OUTGOING_CALLS + ), + dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) + .ensureUserRestrictions() + ); dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS); - assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) - .ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES)); - assertFalse(dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) - .ensureUserRestrictions() - .getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions(), + dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE) + .ensureUserRestrictions() + ); // TODO Check inner calls. // TODO Make sure restrictions are written to the file. } + + public void testGetComposedUserRestrictions_noDoNoPo() throws Exception { + final Bundle in = DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS); + + Bundle actual = dpms.mLocalService.getComposedUserRestrictions( + UserHandle.USER_SYSTEM, in); + assertTrue(in == actual); + + actual = dpms.mLocalService.getComposedUserRestrictions( + DpmMockContext.CALLER_USER_HANDLE, in); + assertTrue(in == actual); + } + + public void testGetComposedUserRestrictions() throws Exception { + mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); + mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); + mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL); + + // First, set DO. + + // Call from a process on the system user. + mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; + + // Make sure admin1 is installed on system user. + setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID); + + // Call. + dpm.setActiveAdmin(admin1, /* replace =*/ false, UserHandle.USER_SYSTEM); + assertTrue(dpm.setDeviceOwner(admin1, "owner-name", + UserHandle.USER_SYSTEM)); + + dpm.addUserRestriction(admin1, "rest1"); + dpm.addUserRestriction(admin1, "rest2"); + + // Set PO on CALLER_USER_HANDLE. + mContext.binder.callingUid = DpmMockContext.CALLER_UID; + + setAsProfileOwner(admin2); + + dpm.addUserRestriction(admin2, "restA"); + dpm.addUserRestriction(admin2, "restB"); + + final Bundle in = DpmTestUtils.newRestrictions("abc"); + + Bundle actual = dpms.mLocalService.getComposedUserRestrictions( + UserHandle.USER_SYSTEM, in); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions("abc", "rest1", "rest2"), + actual); + + actual = dpms.mLocalService.getComposedUserRestrictions( + DpmMockContext.CALLER_USER_HANDLE, in); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions("abc", "rest1", "rest2", "restA", "restB"), + actual); + + actual = dpms.mLocalService.getComposedUserRestrictions( + DpmMockContext.CALLER_USER_HANDLE + 1, in); + DpmTestUtils.assertRestrictions( + DpmTestUtils.newRestrictions("abc", "rest1", "rest2"), + actual); + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index d1b483551809..cc337b0a5131 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -36,6 +36,7 @@ import android.os.PowerManager.WakeLock; import android.os.PowerManagerInternal; import android.os.UserHandle; import android.os.UserManager; +import android.os.UserManagerInternal; import android.test.mock.MockContentResolver; import android.test.mock.MockContext; import android.view.IWindowManager; @@ -203,6 +204,7 @@ public class DpmMockContext extends MockContext { public final EnvironmentForMock environment; public final SystemPropertiesForMock systemProperties; public final UserManager userManager; + public final UserManagerInternal userManagerInternal; public final UserManagerForMock userManagerForMock; public final PowerManagerForMock powerManager; public final PowerManagerInternal powerManagerInternal; @@ -233,6 +235,7 @@ public class DpmMockContext extends MockContext { environment = mock(EnvironmentForMock.class); systemProperties= mock(SystemPropertiesForMock.class); userManager = mock(UserManager.class); + userManagerInternal = mock(UserManagerInternal.class); userManagerForMock = mock(UserManagerForMock.class); powerManager = mock(PowerManagerForMock.class); powerManagerInternal = mock(PowerManagerInternal.class); @@ -257,6 +260,9 @@ public class DpmMockContext extends MockContext { // System user is always running. setUserRunning(UserHandle.USER_SYSTEM, true); + + // This method must return an object. + when(userManagerInternal.getUserRestrictionsLock()).thenReturn(new Object()); } public File addUser(int userId, int flags) { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java index 63bf12558dd1..e11f3fb0b6da 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java @@ -16,10 +16,21 @@ package com.android.server.devicepolicy; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.UserHandle; import android.test.AndroidTestCase; import java.io.File; +import java.util.List; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; public abstract class DpmTestBase extends AndroidTestCase { public static final String TAG = "DpmTest"; @@ -29,6 +40,10 @@ public abstract class DpmTestBase extends AndroidTestCase { public File dataDir; + public ComponentName admin1; + public ComponentName admin2; + public ComponentName admin3; + @Override protected void setUp() throws Exception { super.setUp(); @@ -37,10 +52,77 @@ public abstract class DpmTestBase extends AndroidTestCase { mMockContext = new DpmMockContext( mRealTestContext, new File(mRealTestContext.getCacheDir(), "test-data")); + + admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class); + admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class); + admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class); } @Override public DpmMockContext getContext() { return mMockContext; } + + + protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid) + throws Exception { + setUpPackageManagerForAdmin(admin, packageUid, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED); + } + + protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid, + int enabledSetting) throws Exception { + + // Set up queryBroadcastReceivers(). + + final Intent resolveIntent = new Intent(); + resolveIntent.setComponent(admin); + final List<ResolveInfo> realResolveInfo = + mRealTestContext.getPackageManager().queryBroadcastReceivers( + resolveIntent, + PackageManager.GET_META_DATA); + assertNotNull(realResolveInfo); + assertEquals(1, realResolveInfo.size()); + + // We need to change AI, so set a clone. + realResolveInfo.set(0, DpmTestUtils.cloneParcelable(realResolveInfo.get(0))); + + // We need to rewrite the UID in the activity info. + realResolveInfo.get(0).activityInfo.applicationInfo.uid = packageUid; + + doReturn(realResolveInfo).when(mMockContext.packageManager).queryBroadcastReceivers( + MockUtils.checkIntentComponent(admin), + eq(PackageManager.GET_META_DATA + | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS), + eq(UserHandle.getUserId(packageUid))); + + // Set up getApplicationInfo(). + + final ApplicationInfo ai = DpmTestUtils.cloneParcelable( + mRealTestContext.getPackageManager().getApplicationInfo( + admin.getPackageName(), + PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS)); + + ai.enabledSetting = enabledSetting; + ai.uid = packageUid; + + doReturn(ai).when(mMockContext.ipackageManager).getApplicationInfo( + eq(admin.getPackageName()), + eq(PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS), + eq(UserHandle.getUserId(packageUid))); + + // Set up getPackageInfo(). + + final PackageInfo pi = DpmTestUtils.cloneParcelable( + mRealTestContext.getPackageManager().getPackageInfo( + admin.getPackageName(), 0)); + assertTrue(pi.applicationInfo.flags != 0); + + pi.applicationInfo.uid = packageUid; + + doReturn(pi).when(mMockContext.ipackageManager).getPackageInfo( + eq(admin.getPackageName()), + eq(0), + eq(UserHandle.getUserId(packageUid))); + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java index 7506273accb7..cceb2d2761fe 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java @@ -16,21 +16,35 @@ package com.android.server.devicepolicy; +import com.google.android.collect.Lists; +import com.google.android.collect.Sets; + +import android.content.Context; +import android.os.Bundle; import android.os.FileUtils; import android.os.Parcel; import android.os.Parcelable; +import android.test.AndroidTestCase; import android.util.Log; import android.util.Printer; import org.junit.Assert; +import java.io.BufferedReader; import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.Set; -public class DpmTestUtils { - private DpmTestUtils() { - } +import junit.framework.AssertionFailedError; +public class DpmTestUtils extends AndroidTestCase { public static void clearDir(File dir) { if (dir.exists()) { Assert.assertTrue("failed to delete dir", FileUtils.deleteContents(dir)); @@ -43,6 +57,44 @@ public class DpmTestUtils { return list == null ? 0 : list.size(); } + public static Bundle newRestrictions(String... restrictions) { + final Bundle ret = new Bundle(); + for (String restriction : restrictions) { + ret.putBoolean(restriction, true); + } + return ret; + } + + public static void assertRestrictions(Bundle expected, Bundle actual) { + final ArrayList<String> elist; + if (expected == null) { + elist = null; + } else { + elist = Lists.newArrayList(); + for (String key : expected.keySet()) { + if (expected.getBoolean(key)) { + elist.add(key); + } + } + Collections.sort(elist); + } + + final ArrayList<String> alist; + if (actual == null) { + alist = null; + } else { + alist = Lists.newArrayList(); + for (String key : actual.keySet()) { + if (actual.getBoolean(key)) { + alist.add(key); + } + } + Collections.sort(alist); + } + + assertEquals(elist, alist); + } + public static <T extends Parcelable> T cloneParcelable(T source) { Parcel p = Parcel.obtain(); p.writeParcelable(source, 0); @@ -58,4 +110,57 @@ public class DpmTestUtils { Log.i(DpmTestBase.TAG, x); } }; + + public static String readAsset(Context context, String assetPath) throws IOException { + final StringBuilder sb = new StringBuilder(); + try (BufferedReader br = new BufferedReader( + new InputStreamReader( + context.getResources().getAssets().open(assetPath)))) { + String line; + while ((line = br.readLine()) != null) { + sb.append(line); + sb.append(System.lineSeparator()); + } + } + return sb.toString(); + } + + public static void writeToFile(File path, String content) + throws IOException { + path.getParentFile().mkdirs(); + + try (FileWriter writer = new FileWriter(path)) { + Log.i(DpmTestBase.TAG, "Writing to " + path); + Log.i(DpmTestBase.TAG, content); + writer.write(content); + } + } + + private static boolean checkAssertRestrictions(Bundle a, Bundle b) { + try { + assertRestrictions(a, b); + return true; + } catch (AssertionFailedError e) { + return false; + } + } + + public void testAssertRestrictions() { + final Bundle a = newRestrictions(); + final Bundle b = newRestrictions("a"); + final Bundle c = newRestrictions("a"); + final Bundle d = newRestrictions("b", "c"); + final Bundle e = newRestrictions("b", "c"); + + assertTrue(checkAssertRestrictions(null, null)); + assertFalse(checkAssertRestrictions(null, a)); + assertFalse(checkAssertRestrictions(a, null)); + assertTrue(checkAssertRestrictions(a, a)); + + assertFalse(checkAssertRestrictions(a, b)); + assertTrue(checkAssertRestrictions(b, c)); + + assertFalse(checkAssertRestrictions(c, d)); + assertTrue(checkAssertRestrictions(d, e)); + } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java index 79845d21281f..4e1176233c9f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java @@ -47,31 +47,6 @@ import static org.mockito.Mockito.when; (mmma frameworks/base/services/tests/servicestests/ for non-ninja build) */ public class OwnersTest extends DpmTestBase { - private String readAsset(String assetPath) throws IOException { - final StringBuilder sb = new StringBuilder(); - try (BufferedReader br = new BufferedReader( - new InputStreamReader( - mRealTestContext.getResources().getAssets().open(assetPath)))) { - String line; - while ((line = br.readLine()) != null) { - sb.append(line); - sb.append(System.lineSeparator()); - } - } - return sb.toString(); - } - - private void createLegacyFile(File path, String content) - throws IOException { - path.getParentFile().mkdirs(); - - try (FileWriter writer = new FileWriter(path)) { - Log.i(TAG, "Writing to " + path); - Log.i(TAG, content); - writer.write(content); - } - } - public void testUpgrade01() throws Exception { getContext().addUsers(10, 11, 20, 21); @@ -79,8 +54,8 @@ public class OwnersTest extends DpmTestBase { { final OwnersTestable owners = new OwnersTestable(getContext()); - createLegacyFile(owners.getLegacyConfigFileWithTestOverride(), - readAsset("OwnersTest/test01/input.xml")); + DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(), + DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test01/input.xml")); owners.load(); @@ -99,6 +74,12 @@ public class OwnersTest extends DpmTestBase { assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); assertNull(owners.getSystemUpdatePolicy()); assertEquals(0, owners.getProfileOwnerKeys().size()); + + assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)); } // Then re-read and check. @@ -110,6 +91,12 @@ public class OwnersTest extends DpmTestBase { assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); assertNull(owners.getSystemUpdatePolicy()); assertEquals(0, owners.getProfileOwnerKeys().size()); + + assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)); } } @@ -120,8 +107,8 @@ public class OwnersTest extends DpmTestBase { { final OwnersTestable owners = new OwnersTestable(getContext()); - createLegacyFile(owners.getLegacyConfigFileWithTestOverride(), - readAsset("OwnersTest/test02/input.xml")); + DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(), + DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test02/input.xml")); owners.load(); @@ -142,6 +129,12 @@ public class OwnersTest extends DpmTestBase { assertNull(owners.getSystemUpdatePolicy()); assertEquals(0, owners.getProfileOwnerKeys().size()); + + assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)); } // Then re-read and check. @@ -156,6 +149,12 @@ public class OwnersTest extends DpmTestBase { assertNull(owners.getSystemUpdatePolicy()); assertEquals(0, owners.getProfileOwnerKeys().size()); + + assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)); } } @@ -166,8 +165,8 @@ public class OwnersTest extends DpmTestBase { { final OwnersTestable owners = new OwnersTestable(getContext()); - createLegacyFile(owners.getLegacyConfigFileWithTestOverride(), - readAsset("OwnersTest/test03/input.xml")); + DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(), + DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test03/input.xml")); owners.load(); @@ -196,6 +195,12 @@ public class OwnersTest extends DpmTestBase { owners.getProfileOwnerComponent(11)); assertEquals("1", owners.getProfileOwnerName(11)); assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11)); + + assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)); } // Then re-read and check. @@ -218,9 +223,19 @@ public class OwnersTest extends DpmTestBase { owners.getProfileOwnerComponent(11)); assertEquals("1", owners.getProfileOwnerName(11)); assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11)); + + assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)); } } + /** + * Note this also tests {@link Owners#setDeviceOwnerUserRestrictionsMigrated()} + * and {@link Owners#setProfileOwnerUserRestrictionsMigrated(int)}. + */ public void testUpgrade04() throws Exception { getContext().addUsers(10, 11, 20, 21); @@ -228,8 +243,8 @@ public class OwnersTest extends DpmTestBase { { final OwnersTestable owners = new OwnersTestable(getContext()); - createLegacyFile(owners.getLegacyConfigFileWithTestOverride(), - readAsset("OwnersTest/test04/input.xml")); + DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(), + DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml")); owners.load(); @@ -262,6 +277,12 @@ public class OwnersTest extends DpmTestBase { owners.getProfileOwnerComponent(11)); assertEquals("1", owners.getProfileOwnerName(11)); assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11)); + + assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)); } // Then re-read and check. @@ -288,6 +309,40 @@ public class OwnersTest extends DpmTestBase { owners.getProfileOwnerComponent(11)); assertEquals("1", owners.getProfileOwnerName(11)); assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11)); + + assertTrue(owners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)); + + owners.setDeviceOwnerUserRestrictionsMigrated(); + } + + { + final OwnersTestable owners = new OwnersTestable(getContext()); + owners.load(); + + assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)); + + owners.setProfileOwnerUserRestrictionsMigrated(11); + } + + { + final OwnersTestable owners = new OwnersTestable(getContext()); + owners.load(); + + assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertTrue(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)); + + owners.setProfileOwnerUserRestrictionsMigrated(11); } } @@ -298,8 +353,8 @@ public class OwnersTest extends DpmTestBase { { final OwnersTestable owners = new OwnersTestable(getContext()); - createLegacyFile(owners.getLegacyConfigFileWithTestOverride(), - readAsset("OwnersTest/test05/input.xml")); + DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(), + DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test05/input.xml")); owners.load(); @@ -319,6 +374,12 @@ public class OwnersTest extends DpmTestBase { assertNull(owners.getSystemUpdatePolicy()); assertEquals(0, owners.getProfileOwnerKeys().size()); + + assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)); } // Then re-read and check. @@ -332,6 +393,12 @@ public class OwnersTest extends DpmTestBase { assertNull(owners.getSystemUpdatePolicy()); assertEquals(0, owners.getProfileOwnerKeys().size()); + + assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)); } } @@ -342,8 +409,8 @@ public class OwnersTest extends DpmTestBase { { final OwnersTestable owners = new OwnersTestable(getContext()); - createLegacyFile(owners.getLegacyConfigFileWithTestOverride(), - readAsset("OwnersTest/test06/input.xml")); + DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(), + DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test06/input.xml")); owners.load(); @@ -362,6 +429,12 @@ public class OwnersTest extends DpmTestBase { assertNotNull(owners.getSystemUpdatePolicy()); assertEquals(5, owners.getSystemUpdatePolicy().getPolicyType()); + + assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)); } // Then re-read and check. @@ -375,6 +448,12 @@ public class OwnersTest extends DpmTestBase { assertNotNull(owners.getSystemUpdatePolicy()); assertEquals(5, owners.getSystemUpdatePolicy().getPolicyType()); + + assertFalse(owners.getDeviceOwnerUserRestrictionsNeedsMigration()); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(10)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(11)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(20)); + assertFalse(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)); } } @@ -384,8 +463,8 @@ public class OwnersTest extends DpmTestBase { final OwnersTestable owners = new OwnersTestable(getContext()); // First, migrate to create new-style config files. - createLegacyFile(owners.getLegacyConfigFileWithTestOverride(), - readAsset("OwnersTest/test04/input.xml")); + DpmTestUtils.writeToFile(owners.getLegacyConfigFileWithTestOverride(), + DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/test04/input.xml")); owners.load(); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 86f5ed7346cb..66c7dbb16ede 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -211,11 +211,14 @@ public class UserManagerTest extends AndroidTestCase { public void testRestrictions() { UserInfo testUser = createUser("User 1", 0); - Bundle restrictions = new Bundle(); - restrictions.putBoolean(UserManager.DISALLOW_INSTALL_APPS, true); - restrictions.putBoolean(UserManager.DISALLOW_CONFIG_WIFI, false); - mUserManager.setUserRestrictions(restrictions, new UserHandle(testUser.id)); + + mUserManager.setUserRestriction( + UserManager.DISALLOW_INSTALL_APPS, true, new UserHandle(testUser.id)); + mUserManager.setUserRestriction( + UserManager.DISALLOW_CONFIG_WIFI, false, new UserHandle(testUser.id)); + Bundle stored = mUserManager.getUserRestrictions(new UserHandle(testUser.id)); + // Note this will fail if DO already sets those restrictions. assertEquals(stored.getBoolean(UserManager.DISALLOW_CONFIG_WIFI), false); assertEquals(stored.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS), false); assertEquals(stored.getBoolean(UserManager.DISALLOW_INSTALL_APPS), true); diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index 31763e7a90ba..129e5377882a 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -212,6 +212,10 @@ public final class UsbAlsaManager { } private AlsaDevice waitForAlsaDevice(int card, int device, int type) { + if (DEBUG) { + Slog.e(TAG, "waitForAlsaDevice(c:" + card + " d:" + device + ")"); + } + AlsaDevice testDevice = new AlsaDevice(type, card, device); // This value was empirically determined. @@ -292,7 +296,8 @@ public final class UsbAlsaManager { */ /* package */ UsbAudioDevice selectAudioCard(int card) { if (DEBUG) { - Slog.d(TAG, "selectAudioCard() card:" + card); + Slog.d(TAG, "selectAudioCard() card:" + card + + " isCardUsb(): " + mCardsParser.isCardUsb(card)); } if (!mCardsParser.isCardUsb(card)) { // Don't. AudioPolicyManager has logic for falling back to internal devices. @@ -304,6 +309,10 @@ public final class UsbAlsaManager { boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card); boolean hasCapture = mDevicesParser.hasCaptureDevices(card); + if (DEBUG) { + Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture); + } + int deviceClass = (mCardsParser.isCardUsb(card) ? UsbAudioDevice.kAudioDeviceClass_External @@ -320,10 +329,6 @@ public final class UsbAlsaManager { return null; } - if (DEBUG) { - Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture); - } - UsbAudioDevice audioDevice = new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass); AlsaCardsParser.AlsaCardRecord cardRecord = mCardsParser.getCardRecordFor(card); @@ -339,14 +344,13 @@ public final class UsbAlsaManager { if (DEBUG) { Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()"); } - mCardsParser.scan(); return selectAudioCard(mCardsParser.getDefaultCard()); } /* package */ void usbDeviceAdded(UsbDevice usbDevice) { if (DEBUG) { Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName() + - "nm:" + usbDevice.getProductName()); + " nm:" + usbDevice.getProductName()); } // Is there an audio interface in there? @@ -361,27 +365,22 @@ public final class UsbAlsaManager { isAudioDevice = true; } } + + if (DEBUG) { + Slog.d(TAG, " isAudioDevice: " + isAudioDevice); + } if (!isAudioDevice) { return; } - ArrayList<AlsaCardsParser.AlsaCardRecord> prevScanRecs = mCardsParser.getScanRecords(); - mCardsParser.scan(); - - int addedCard = -1; - ArrayList<AlsaCardsParser.AlsaCardRecord> - newScanRecs = mCardsParser.getNewCardRecords(prevScanRecs); - if (newScanRecs.size() > 0) { - // This is where we select the just connected device - // NOTE - to switch to prefering the first-connected device, just always - // take the else clause below. - addedCard = newScanRecs.get(0).mCardNum; - } else { - addedCard = mCardsParser.getDefaultUsbCard(); - } + int addedCard = mCardsParser.getDefaultUsbCard(); // If the default isn't a USB device, let the existing "select internal mechanism" // handle the selection. + if (DEBUG) { + Slog.d(TAG, " mCardsParser.isCardUsb(" + addedCard + ") = " + + mCardsParser.isCardUsb(addedCard)); + } if (mCardsParser.isCardUsb(addedCard)) { UsbAudioDevice audioDevice = selectAudioCard(addedCard); if (audioDevice != null) { @@ -429,6 +428,10 @@ public final class UsbAlsaManager { } } } + + if (DEBUG) { + Slog.d(TAG, "deviceAdded() - done"); + } } /* package */ void usbDeviceRemoved(UsbDevice usbDevice) { @@ -439,7 +442,7 @@ public final class UsbAlsaManager { UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice); if (audioDevice != null) { - if (audioDevice.mHasPlayback || audioDevice.mHasPlayback) { + if (audioDevice.mHasPlayback || audioDevice.mHasCapture) { notifyDeviceState(audioDevice, false); // if there any external devices left, select one of them diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 3160e3961495..e0f95cfa18a0 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -756,6 +756,8 @@ public class UsbDeviceManager { if (mUsbDataUnlocked && active && mCurrentUser != UserHandle.USER_NULL) { Slog.v(TAG, "Current user switched to " + mCurrentUser + "; resetting USB host stack for MTP or PTP"); + // avoid leaking sensitive data from previous user + mUsbDataUnlocked = false; setEnabledFunctions(mCurrentFunctions, true); } mCurrentUser = msg.arg1; diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java index f263b4da7b29..861a37990629 100644 --- a/telephony/java/com/android/ims/ImsCallProfile.java +++ b/telephony/java/com/android/ims/ImsCallProfile.java @@ -178,6 +178,7 @@ public class ImsCallProfile implements Parcelable { * Codec: Codec info. * DisplayText: Display text for the call. * AdditionalCallInfo: Additional call info. + * CallRadioTech: The radio tech on which the call is placed. */ public static final String EXTRA_OI = "oi"; public static final String EXTRA_CNA = "cna"; @@ -187,6 +188,7 @@ public class ImsCallProfile implements Parcelable { public static final String EXTRA_CODEC = "Codec"; public static final String EXTRA_DISPLAY_TEXT = "DisplayText"; public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo"; + public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech"; public int mServiceType; public int mCallType; |