diff options
51 files changed, 1919 insertions, 322 deletions
diff --git a/api/current.txt b/api/current.txt index e9e2d04..6d35ecd 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3761,7 +3761,9 @@ package android.app { method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock(); method public void set(int, long, android.app.PendingIntent); method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent); + method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent); method public void setExact(int, long, android.app.PendingIntent); + method public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent); method public void setInexactRepeating(int, long, long, android.app.PendingIntent); method public void setRepeating(int, long, long, android.app.PendingIntent); method public void setTime(long); @@ -27230,7 +27232,6 @@ package android.renderscript { method public void copyTo(short[]); method public void copyTo(int[]); method public void copyTo(float[]); - method public void copyToFieldPacker(int, int, int, int, android.renderscript.FieldPacker); method public static android.renderscript.Allocation createCubemapFromBitmap(android.renderscript.RenderScript, android.graphics.Bitmap, android.renderscript.Allocation.MipmapControl, int); method public static android.renderscript.Allocation createCubemapFromBitmap(android.renderscript.RenderScript, android.graphics.Bitmap); method public static android.renderscript.Allocation createCubemapFromCubeFaces(android.renderscript.RenderScript, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.renderscript.Allocation.MipmapControl, int); diff --git a/api/system-current.txt b/api/system-current.txt index 5ff1b99..81a43c1 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3851,7 +3851,9 @@ package android.app { method public void set(int, long, android.app.PendingIntent); method public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource); method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent); + method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent); method public void setExact(int, long, android.app.PendingIntent); + method public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent); method public void setInexactRepeating(int, long, long, android.app.PendingIntent); method public void setRepeating(int, long, long, android.app.PendingIntent); method public void setTime(long); @@ -9504,6 +9506,7 @@ package android.content.pm { method public abstract android.content.pm.PackageInstaller getPackageInstaller(); method public abstract java.lang.String[] getPackagesForUid(int); method public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int); + method public abstract int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle); method public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.PermissionInfo getPermissionInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String); @@ -9521,7 +9524,7 @@ package android.content.pm { method public abstract android.graphics.drawable.Drawable getUserBadgedIcon(android.graphics.drawable.Drawable, android.os.UserHandle); method public abstract java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle); method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo); - method public abstract void grantPermission(java.lang.String, java.lang.String, android.os.UserHandle); + method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public abstract boolean hasSystemFeature(java.lang.String); method public abstract boolean isSafeMode(); method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int); @@ -9537,10 +9540,11 @@ package android.content.pm { method public abstract android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int); method public abstract android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int); method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int); - method public abstract void revokePermission(java.lang.String, java.lang.String, android.os.UserHandle); + method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public abstract void setApplicationEnabledSetting(java.lang.String, int, int); method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int); method public abstract void setInstallerPackageName(java.lang.String, java.lang.String); + method public abstract void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle); method public abstract void verifyPendingInstall(int, int); field public static final java.lang.String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS"; field public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; // 0x0 @@ -9626,6 +9630,10 @@ package android.content.pm { field public static final java.lang.String FEATURE_WEBVIEW = "android.software.webview"; field public static final java.lang.String FEATURE_WIFI = "android.hardware.wifi"; field public static final java.lang.String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct"; + field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4 + field public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 8; // 0x8 + field public static final int FLAG_PERMISSION_USER_FIXED = 2; // 0x2 + field public static final int FLAG_PERMISSION_USER_SET = 1; // 0x1 field public static final int GET_ACTIVITIES = 1; // 0x1 field public static final int GET_CONFIGURATIONS = 16384; // 0x4000 field public static final int GET_DISABLED_COMPONENTS = 512; // 0x200 @@ -9678,6 +9686,7 @@ package android.content.pm { field public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103; // 0xffffff99 field public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102; // 0xffffff9a field public static final int INSTALL_SUCCEEDED = 1; // 0x1 + field public static final int MASK_PERMISSION_FLAGS = 15; // 0xf field public static final int MATCH_ALL = 131072; // 0x20000 field public static final int MATCH_DEFAULT_ONLY = 65536; // 0x10000 field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L @@ -9698,6 +9707,9 @@ package android.content.pm { ctor public PackageManager.NameNotFoundException(java.lang.String); } + public static abstract class PackageManager.PermissionFlags implements java.lang.annotation.Annotation { + } + public class PackageStats implements android.os.Parcelable { ctor public PackageStats(java.lang.String); ctor public PackageStats(android.os.Parcel); @@ -29244,7 +29256,6 @@ package android.renderscript { method public void copyTo(short[]); method public void copyTo(int[]); method public void copyTo(float[]); - method public void copyToFieldPacker(int, int, int, int, android.renderscript.FieldPacker); method public static android.renderscript.Allocation createCubemapFromBitmap(android.renderscript.RenderScript, android.graphics.Bitmap, android.renderscript.Allocation.MipmapControl, int); method public static android.renderscript.Allocation createCubemapFromBitmap(android.renderscript.RenderScript, android.graphics.Bitmap); method public static android.renderscript.Allocation createCubemapFromCubeFaces(android.renderscript.RenderScript, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.renderscript.Allocation.MipmapControl, int); @@ -34039,6 +34050,7 @@ package android.test.mock { method public android.content.pm.PackageInstaller getPackageInstaller(); method public java.lang.String[] getPackagesForUid(int); method public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int); + method public int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle); method public android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public android.content.pm.PermissionInfo getPermissionInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String); @@ -34056,7 +34068,7 @@ package android.test.mock { method public android.graphics.drawable.Drawable getUserBadgedIcon(android.graphics.drawable.Drawable, android.os.UserHandle); method public java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle); method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo); - method public void grantPermission(java.lang.String, java.lang.String, android.os.UserHandle); + method public void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public boolean hasSystemFeature(java.lang.String); method public boolean isSafeMode(); method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int); @@ -34072,11 +34084,12 @@ package android.test.mock { method public android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int); method public android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int); method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int); - method public void revokePermission(java.lang.String, java.lang.String, android.os.UserHandle); + method public void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public void setApplicationEnabledSetting(java.lang.String, int, int); method public void setComponentEnabledSetting(android.content.ComponentName, int, int); method public boolean setDefaultBrowserPackageName(java.lang.String, int); method public void setInstallerPackageName(java.lang.String, java.lang.String); + method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle); method public void verifyPendingInstall(int, int); } diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index 39de1dc7..eb834f2 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -1609,9 +1609,9 @@ public final class Pm { try { if (grant) { - mPm.grantPermission(pkg, perm, userId); + mPm.grantRuntimePermission(pkg, perm, userId); } else { - mPm.revokePermission(pkg, perm, userId); + mPm.revokeRuntimePermission(pkg, perm, userId); } return 0; } catch (RemoteException e) { diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 6503d89..951fe49 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -16,9 +16,10 @@ package android.animation; +import android.util.ArrayMap; + import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; /** @@ -68,7 +69,7 @@ public final class AnimatorSet extends Animator { * to a single node representing that Animator, not create a new Node * if one already exists. */ - private HashMap<Animator, Node> mNodeMap = new HashMap<Animator, Node>(); + private ArrayMap<Animator, Node> mNodeMap = new ArrayMap<Animator, Node>(); /** * Set of all nodes created for this AnimatorSet. This list is used upon @@ -646,7 +647,7 @@ public final class AnimatorSet extends Animator { anim.mTerminated = false; anim.mStarted = false; anim.mPlayingSet = new ArrayList<Animator>(); - anim.mNodeMap = new HashMap<Animator, Node>(); + anim.mNodeMap = new ArrayMap<Animator, Node>(); anim.mNodes = new ArrayList<Node>(nodeCount); anim.mSortedNodes = new ArrayList<Node>(nodeCount); anim.mReversible = mReversible; diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index b0fda9c..5e7bd0d 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -71,10 +71,7 @@ import java.io.IOException; * {@link android.content.Context#getSystemService * Context.getSystemService(Context.ALARM_SERVICE)}. */ -public class AlarmManager -{ - private static final String TAG = "AlarmManager"; - +public class AlarmManager { /** * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()} * (wall clock time in UTC), which will wake up the device when @@ -558,7 +555,93 @@ public class AlarmManager long intervalMillis, PendingIntent operation) { setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null, null); } - + + /** + * Like {@link #set(int, long, PendingIntent)}, but this alarm will be allowed to execute + * even when the system is in low-power idle modes. This type of alarm must <b>only</b> + * be used for situations where it is actually required that the alarm go off while in + * idle -- a reasonable example would be for a calendar notification that should make a + * sound so the user is aware of it. These alarms can significantly impact the power use + * of the device when idle (and thus cause significant battery blame to the app scheduling + * them), so they should be used with care. + * + * <p>Unlike other alarms, the system is free to reschedule this type of alarm to happen + * out of order with any other alarms, even those from the same app. This will clearly happen + * when the device is idle (since this alarm can go off while idle, when any other alarms + * from the app will be held until later), but may also happen even when not idle.</p> + * + * <p>Regardless of the app's target SDK version, this call always allows batching of the + * alarm.</p> + * + * @param type One of {@link #ELAPSED_REALTIME}, {@link #ELAPSED_REALTIME_WAKEUP}, + * {@link #RTC}, or {@link #RTC_WAKEUP}. + * @param triggerAtMillis time in milliseconds that the alarm should go + * off, using the appropriate clock (depending on the alarm type). + * @param operation Action to perform when the alarm goes off; + * typically comes from {@link PendingIntent#getBroadcast + * IntentSender.getBroadcast()}. + * + * @see #set(int, long, PendingIntent) + * @see #setExactAndAllowWhileIdle + * @see #cancel + * @see android.content.Context#sendBroadcast + * @see android.content.Context#registerReceiver + * @see android.content.Intent#filterEquals + * @see #ELAPSED_REALTIME + * @see #ELAPSED_REALTIME_WAKEUP + * @see #RTC + * @see #RTC_WAKEUP + */ + public void setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) { + setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, operation, + null, null); + } + + /** + * Like {@link #setExact(int, long, PendingIntent)}, but this alarm will be allowed to execute + * even when the system is in low-power idle modes. If you don't need exact scheduling of + * the alarm but still need to execute while idle, consider using + * {@link #setAndAllowWhileIdle}. This type of alarm must <b>only</b> + * be used for situations where it is actually required that the alarm go off while in + * idle -- a reasonable example would be for a calendar notification that should make a + * sound so the user is aware of it. These alarms can significantly impact the power use + * of the device when idle (and thus cause significant battery blame to the app scheduling + * them), so they should be used with care. + * + * <p>Unlike other alarms, the system is free to reschedule this type of alarm to happen + * out of order with any other alarms, even those from the same app. This will clearly happen + * when the device is idle (since this alarm can go off while idle, when any other alarms + * from the app will be held until later), but may also happen even when not idle. + * Note that the OS will allow itself more flexibility for scheduling these alarms than + * regular exact alarms, since the application has opted into this behavior. When the + * device is idle it may take even more liberties with scheduling in order to optimize + * for battery life.</p> + * + * @param type One of {@link #ELAPSED_REALTIME}, {@link #ELAPSED_REALTIME_WAKEUP}, + * {@link #RTC}, or {@link #RTC_WAKEUP}. + * @param triggerAtMillis time in milliseconds that the alarm should go + * off, using the appropriate clock (depending on the alarm type). + * @param operation Action to perform when the alarm goes off; + * typically comes from {@link PendingIntent#getBroadcast + * IntentSender.getBroadcast()}. + * + * @see #set + * @see #setRepeating + * @see #setWindow + * @see #cancel + * @see android.content.Context#sendBroadcast + * @see android.content.Context#registerReceiver + * @see android.content.Intent#filterEquals + * @see #ELAPSED_REALTIME + * @see #ELAPSED_REALTIME_WAKEUP + * @see #RTC + * @see #RTC_WAKEUP + */ + public void setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) { + setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation, + null, null); + } + /** * Remove any alarms with a matching {@link Intent}. * Any alarm, of any type, whose Intent matches this one (as defined by diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 07eee12..04f6430 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -446,18 +446,40 @@ final class ApplicationPackageManager extends PackageManager { } @Override - public void grantPermission(String packageName, String permissionName, UserHandle user) { + public void grantRuntimePermission(String packageName, String permissionName, + UserHandle user) { + try { + mPM.grantRuntimePermission(packageName, permissionName, user.getIdentifier()); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public void revokeRuntimePermission(String packageName, String permissionName, + UserHandle user) { + try { + mPM.revokeRuntimePermission(packageName, permissionName, user.getIdentifier()); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public int getPermissionFlags(String permissionName, String packageName, UserHandle user) { try { - mPM.grantPermission(packageName, permissionName, user.getIdentifier()); + return mPM.getPermissionFlags(permissionName, packageName, user.getIdentifier()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } } @Override - public void revokePermission(String packageName, String permissionName, UserHandle user) { + public void updatePermissionFlags(String permissionName, String packageName, + int flagMask, int flagValues, UserHandle user) { try { - mPM.revokePermission(packageName, permissionName, user.getIdentifier()); + mPM.updatePermissionFlags(permissionName, packageName, flagMask, + flagValues, user.getIdentifier()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 94b0223..ddff782 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -96,9 +96,14 @@ interface IPackageManager { void removePermission(String name); - void grantPermission(String packageName, String permissionName, int userId); + void grantRuntimePermission(String packageName, String permissionName, int userId); - void revokePermission(String packageName, String permissionName, int userId); + void revokeRuntimePermission(String packageName, String permissionName, int userId); + + int getPermissionFlags(String permissionName, String packageName, int userId); + + void updatePermissionFlags(String permissionName, String packageName, int flagMask, + int flagValues, int userId); boolean isProtectedBroadcast(String actionName); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 51fa075..6401fe6 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1888,6 +1888,57 @@ public abstract class PackageManager { public static final String EXTRA_FAILURE_EXISTING_PERMISSION = "android.content.pm.extra.FAILURE_EXISTING_PERMISSION"; + /** + * Permission flag: The permission is set in its current state + * by the user and apps can still request it at runtime. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_USER_SET = 1 << 0; + + /** + * Permission flag: The permission is set in its current state + * by the user and it is fixed, i.e. apps can no longer request + * this permission. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_USER_FIXED = 1 << 1; + + /** + * Permission flag: The permission is set in its current state + * by device policy and neither apps nor the user can change + * its state. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_POLICY_FIXED = 1 << 2; + + /** + * Permission flag: The permission is set in a granted state but + * access to resources it guards is restricted by other means to + * enable revoking a permission on legacy apps that do not support + * runtime permissions. If this permission is upgraded to runtime + * because the app was updated to support runtime permissions, the + * the permission will be revoked in the upgrade process. + * + * @hide + */ + @SystemApi + public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 1 << 3; + + + /** + * Mask for all permission flags. + * + * @hide + */ + @SystemApi + public static final int MASK_PERMISSION_FLAGS = 0xF; + /** * Retrieve overall information about an application package that is * installed on the system. @@ -2374,6 +2425,20 @@ public abstract class PackageManager { */ public abstract void removePermission(String name); + + /** + * Permission flags set when granting or revoking a permission. + * + * @hide + */ + @SystemApi + @IntDef({FLAG_PERMISSION_USER_SET, + FLAG_PERMISSION_USER_FIXED, + FLAG_PERMISSION_POLICY_FIXED, + FLAG_PERMISSION_REVOKE_ON_UPGRADE}) + @Retention(RetentionPolicy.SOURCE) + public @interface PermissionFlags {} + /** * Grant a runtime permission to an application which the application does not * already have. The permission must have been requested by the application. @@ -2389,19 +2454,20 @@ public abstract class PackageManager { * @param permissionName The permission name to grant. * @param user The user for which to grant the permission. * - * @see #revokePermission(String, String, android.os.UserHandle) + * @see #revokeRuntimePermission(String, String, android.os.UserHandle) + * @see android.content.pm.PackageManager.PermissionFlags * * @hide */ @SystemApi - public abstract void grantPermission(@NonNull String packageName, + public abstract void grantRuntimePermission(@NonNull String packageName, @NonNull String permissionName, @NonNull UserHandle user); /** * Revoke a runtime permission that was previously granted by {@link - * #grantPermission(String, String, android.os.UserHandle)}. The permission - * must have been requested by and granted to the application. If the - * application is not allowed to hold the permission, a {@link + * #grantRuntimePermission(String, String, android.os.UserHandle)}. The + * permission must have been requested by and granted to the application. + * If the application is not allowed to hold the permission, a {@link * java.lang.SecurityException} is thrown. * <p> * <strong>Note: </strong>Using this API requires holding @@ -2413,15 +2479,47 @@ public abstract class PackageManager { * @param permissionName The permission name to revoke. * @param user The user for which to revoke the permission. * - * @see #grantPermission(String, String, android.os.UserHandle) + * @see #grantRuntimePermission(String, String, android.os.UserHandle) + * @see android.content.pm.PackageManager.PermissionFlags * * @hide */ @SystemApi - public abstract void revokePermission(@NonNull String packageName, + public abstract void revokeRuntimePermission(@NonNull String packageName, @NonNull String permissionName, @NonNull UserHandle user); /** + * Gets the state flags associated with a permission. + * + * @param permissionName The permission for which to get the flags. + * @param packageName The package name for which to get the flags. + * @param user The user for which to get permission flags. + * @return The permission flags. + * + * @hide + */ + @SystemApi + public abstract @PermissionFlags int getPermissionFlags(String permissionName, + String packageName, @NonNull UserHandle user); + + /** + * Updates the flags associated with a permission by replacing the flags in + * the specified mask with the provided flag values. + * + * @param permissionName The permission for which to update the flags. + * @param packageName The package name for which to update the flags. + * @param flagMask The flags which to replace. + * @param flagValues The flags with which to replace. + * @param user The user for which to update the permission flags. + * + * @hide + */ + @SystemApi + public abstract void updatePermissionFlags(String permissionName, + String packageName, @PermissionFlags int flagMask, int flagValues, + @NonNull UserHandle user); + + /** * Returns an {@link android.content.Intent} suitable for passing to * {@link android.app.Activity#startActivityForResult(android.content.Intent, int)} * which prompts the user to grant permissions to this application. diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index 00ab262..9a0d0d0 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -134,4 +134,6 @@ public abstract class PowerManagerInternal { } public abstract void setDeviceIdleMode(boolean enabled); + + public abstract void setDeviceIdleWhitelist(int[] appids); } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 51c4760..b476e9b 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -586,6 +586,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mGroupFlags |= FLAG_CLIP_CHILDREN; mGroupFlags |= FLAG_CLIP_TO_PADDING; mGroupFlags |= FLAG_ANIMATION_DONE; + mGroupFlags |= FLAG_ANIMATION_CACHE; + mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE; if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) { mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS; diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java index 6feb94b..bb4a948 100644 --- a/core/java/android/widget/AppSecurityPermissions.java +++ b/core/java/android/widget/AppSecurityPermissions.java @@ -244,7 +244,7 @@ public class AppSecurityPermissions { @Override public void onClick(DialogInterface dialog, int which) { PackageManager pm = getContext().getPackageManager(); - pm.revokePermission(mPackageName, mPerm.name, + pm.revokeRuntimePermission(mPackageName, mPerm.name, new UserHandle(mContext.getUserId())); PermissionItemView.this.setVisibility(View.GONE); } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 26cdf6b..4696757 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -668,29 +668,37 @@ public class ResolverActivity extends Activity { if (r.match > bestMatch) bestMatch = r.match; } if (alwaysCheck) { - PackageManager pm = getPackageManager(); + final int userId = getUserId(); + final PackageManager pm = getPackageManager(); // Set the preferred Activity pm.addPreferredActivity(filter, bestMatch, set, intent.getComponent()); - // Update Domain Verification status - int userId = getUserId(); - ComponentName cn = intent.getComponent(); - String packageName = cn.getPackageName(); - String dataScheme = (data != null) ? data.getScheme() : null; - - boolean isHttpOrHttps = (dataScheme != null) && - (dataScheme.equals(IntentFilter.SCHEME_HTTP) || - dataScheme.equals(IntentFilter.SCHEME_HTTPS)); - - boolean isViewAction = (action != null) && action.equals(Intent.ACTION_VIEW); - boolean hasCategoryBrowsable = (categories != null) && - categories.contains(Intent.CATEGORY_BROWSABLE); - - if (isHttpOrHttps && isViewAction && hasCategoryBrowsable) { - pm.updateIntentVerificationStatus(packageName, - PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, - userId); + if (ri.handleAllWebDataURI) { + // Set default Browser if needed + final String packageName = pm.getDefaultBrowserPackageName(userId); + if (TextUtils.isEmpty(packageName)) { + pm.setDefaultBrowserPackageName(ri.activityInfo.packageName, userId); + } + } else { + // Update Domain Verification status + ComponentName cn = intent.getComponent(); + String packageName = cn.getPackageName(); + String dataScheme = (data != null) ? data.getScheme() : null; + + boolean isHttpOrHttps = (dataScheme != null) && + (dataScheme.equals(IntentFilter.SCHEME_HTTP) || + dataScheme.equals(IntentFilter.SCHEME_HTTPS)); + + boolean isViewAction = (action != null) && action.equals(Intent.ACTION_VIEW); + boolean hasCategoryBrowsable = (categories != null) && + categories.contains(Intent.CATEGORY_BROWSABLE); + + if (isHttpOrHttps && isViewAction && hasCategoryBrowsable) { + pm.updateIntentVerificationStatus(packageName, + PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, + userId); + } } } else { try { diff --git a/docs/html/tools/debugging/ddms.jd b/docs/html/tools/debugging/ddms.jd index e9c2877..28ad11e 100644 --- a/docs/html/tools/debugging/ddms.jd +++ b/docs/html/tools/debugging/ddms.jd @@ -204,6 +204,10 @@ parent.link=index.html <li>Click the <strong>Start Method Profiling</strong> button.</li> + <li>In Android 4.4 and later, choose either trace-based profiling or sample-based profiling + with a specified sampling interval. For earlier versions of Android, only trace-based profiling + is available.</li> + <li>Interact with your application to start the methods that you want to profile.</li> <li>Click the <strong>Stop Method Profiling</strong> button. DDMS stops profiling your diff --git a/docs/html/tools/debugging/debugging-tracing.jd b/docs/html/tools/debugging/debugging-tracing.jd index bd4afbc..fa5b4e1 100644 --- a/docs/html/tools/debugging/debugging-tracing.jd +++ b/docs/html/tools/debugging/debugging-tracing.jd @@ -127,7 +127,7 @@ parent.link=index.html {@link android.os.Debug#startMethodTracing() startMethodTracing()} methods. In the call, you specify a base name for the trace files that the system generates. To stop tracing, call {@link android.os.Debug#stopMethodTracing() stopMethodTracing()}. These methods start and stop method - tracing across the entire virtual machine. For example, you could call + tracing across the entire virtual machine. For example, you could call {@link android.os.Debug#startMethodTracing() startMethodTracing()} in your activity's {@link android.app.Activity#onCreate onCreate()} method, and call {@link android.os.Debug#stopMethodTracing() stopMethodTracing()} in that activity's @@ -157,6 +157,13 @@ parent.link=index.html times are only useful in relation to other profile output, so you can see if changes have made the code faster or slower relative to a previous profiling run.</p> + <p>In Android 4.4 and later, you can use sample-based profiling to profile with less runtime + performance impact. To enable sample profiling, call {@link + android.os.Debug#startMethodTracingSampling(java.lang.String, int, int) + startMethodTracingSampling()} with a specified sampling interval. The system will then gather + samples periodically until tracing is stopped via {@link android.os.Debug#stopMethodTracing() + stopMethodTracing()}.</p> + <h2 id="copyingfiles">Copying Trace Files to a Host Machine</h2> <p>After your application has run and the system has created your trace files @@ -277,7 +284,8 @@ dmtracedump [-ho] [-s sortable] [-d trace-base-name] [-g outfile] <trace-base Traceview logging does not handle threads well, resulting in these two problems: <ol> - <li>If a thread exits during profiling, the thread name is not emitted;</li> + <li>If a thread exits during profiling, the thread name is not emitted (fixed in Android 5.1 + and later);</li> <li>The VM reuses thread IDs. If a thread stops and another starts, they may get the same ID.</li> diff --git a/docs/html/training/testing/ui-testing/index.jd b/docs/html/training/testing/ui-testing/index.jd index 20422f7..d660c60 100644 --- a/docs/html/training/testing/ui-testing/index.jd +++ b/docs/html/training/testing/ui-testing/index.jd @@ -72,5 +72,5 @@ Testing UI for a Single App</a></strong></dt> <dd>Learn how to test UI in a single app by using the Espresso testing framework.</dd> <dt><strong><a href="uiautomator-testing.html"> Testing UI for Multiple Apps</a></strong></dt> - <dd>Learn how to test UI in multiple apps by using the UI Automator testing framework</dd> + <dd>Learn how to test UI in multiple apps by using the UI Automator testing framework.</dd> </dl>
\ No newline at end of file diff --git a/docs/html/training/testing/unit-testing/index.jd b/docs/html/training/testing/unit-testing/index.jd new file mode 100644 index 0000000..a35ba80 --- /dev/null +++ b/docs/html/training/testing/unit-testing/index.jd @@ -0,0 +1,63 @@ +page.title=Building Effective Unit Tests +page.tags=testing,androidjunitrunner,junit,unit test + +trainingnavtop=true +startpage=true + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + <h2> + You should also read + </h2> + <ul> + <li> + <a href="{@docRoot}tools/testing-support-library/index.html">Testing Support Library</a> + </li> + </ul> +</div> +</div> + +<p>Unit tests are the fundamental tests in your app testing strategy. By creating and running unit +tests against your code, you can easily verify that the logic of individual units is correct. +Running unit tests after every build helps you to +quickly catch and fix software regressions introduced by code changes to your app. +</p> + +<p>A unit test generally exercises the functionality of the smallest possible unit of code (which +could be a method, class, or component) in a repeatable way. You should build unit tests when you +need to verify the logic of specific code in your app. For example, if you are unit testing a +class, your test might check that the class is in the right state. Typically, the unit of code +is tested in isolation; your test affects and monitors changes to that unit only. A +<a href="http://en.wikipedia.org/wiki/Mock_object" class="external-link">mocking framework</a> +can be used to isolate your unit from its dependencies.</p> + +<p class="note"><strong>Note:</strong> Unit tests are not suitable for testing +complex UI interaction events. Instead, you should use the UI testing frameworks, as described in +<a href="{@docRoot}training/testing/ui-testing/index.html">Automating UI Tests</a>.</p> + +<p>For testing Android apps, you typically create these types of automated unit tests:</p> + +<ul> +<li><strong>Local tests:</strong> Unit tests that run on your local machine only. These tests are +compiled to run locally on the Java Virtual Machine (JVM) to minimize execution time. Use this +approach to run unit tests that have no dependencies on the Android framework or have dependencies +that can be filled by using mock objects.</li> +<li><strong>Instrumented tests:</strong> Unit tests that run on an Android device or emulator. +These tests have access to instrumentation information, such as the +{@link android.content.Context} for the app under test. Use this approach to run unit tests that +have Android dependencies which cannot be easily filled by using mock objects.</li> +</ul> + +<p>The lessons in this class teach you how to build these types of automated unit tests.</p> + +<h2>Lessons</h2> +<dl> + <dt><strong><a href="local-unit-tests.html"> +Building Local Unit Tests</a></strong></dt> + <dd>Learn how to build unit tests that run on your local machine.</dd> + <dt><strong><a href="instrumented-unit-tests.html"> +Building Instrumented Unit Tests</a></strong></dt> + <dd>Learn how to build unit tests that run on an Android device or emulator.</dd> +</dl>
\ No newline at end of file diff --git a/docs/html/training/testing/unit-testing/instrumented-unit-tests.jd b/docs/html/training/testing/unit-testing/instrumented-unit-tests.jd new file mode 100644 index 0000000..07f0f73 --- /dev/null +++ b/docs/html/training/testing/unit-testing/instrumented-unit-tests.jd @@ -0,0 +1,250 @@ +page.title=Building Instrumented Unit Tests +page.tags=testing,androidjunitrunner,junit,unit test,mock,instrumentation +trainingnavtop=true + +@jd:body + +<!-- This is the training bar --> +<div id="tb-wrapper"> +<div id="tb"> + <h2>Dependencies and Prerequisites</h2> + + <ul> + <li>Android 2.2 (API level 8) or higher</li> + <li><a href="{@docRoot}tools/testing-support-library/index.html"> + Android Testing Support Library</a></li> + </ul> + + <h2>This lesson teaches you to</h2> + + <ol> + <li><a href="#setup">Set Up Your Testing Environment</a></li> + <li><a href="#build">Create a Instrumented Unit Test Class</a></li> + <li><a href="#run">Run Instrumented Unit Tests</a></li> + </ol> + + <h2>Try it out</h2> + + <ul> + <li> +<a href="https://github.com/googlesamples/android-testing/tree/master/unittesting/BasicUnitAndroidTest" +class="external-link">Instrumented Unit Tests Code Samples</a></li> + </ul> +</div> +</div> + +<p> +Instrumented unit tests are unit tests that run on physical devices and emulators, instead of +the Java Virtual Machine (JVM) on your local machine. You should create instrumented unit tests +if your tests need access to instrumentation information (such as the target app's +{@link android.content.Context}) or if they require the real implementation of an Android framework +component (such as a {@link android.os.Parcelable} or {@link android.content.SharedPreferences} +object). Using instrumented unit tests also helps to reduce the effort required to write and +maintain mock code. You are still free to use a mocking framework, if you choose, to simulate any +dependency relationships. Instrumented unit tests can take advantage of the Android framework APIs +and supporting APIs, such as the Android Testing Support Library. +</p> + +<h2 id="setup">Set Up Your Testing Environment</h2> +<p>Before building instrumented unit tests, you must:</p> + + <ul> + <li> + <strong>Install the Android Testing Support Library</strong>. The + <a href="{@docRoot}reference/android/support/test/runner/AndroidJUnitRunner.html"> + {@code AndroidJUnitRunner}</a> API, located under the + {@code com.android.support.test.runner} package, allows you to + create and run instrumented unit tests. To learn how to install the + library, see <a href="{@docRoot}tools/testing-support-library/index.html#setup"> + Testing Support Library Setup</a>. + </li> + + <li> + <strong>Set up your project structure.</strong> In your Gradle project, the source code for + the target app that you want to test is typically placed under the {@code app/src/main/java} + folder. The source code for instrumentatation tests, including your unit tests, must be + placed under the <code>app/src/androidTest/java</code> folder. + To learn more about setting up your project directory, see + <a href="{@docRoot}tools/projects/index.html">Managing Projects</a>. + </li> + + <li> + <strong>Specify your Android testing dependencies</strong>. In order for the + <a href="{@docRoot}tools/building/plugin-for-gradle.html">Android Plug-in for Gradle</a> to + correctly build and run your instrumented unit tests, you must specify the following + libraries in the {@code build.gradle} file of your Android app module: + + <pre> +dependencies { + androidTestCompile 'com.android.support.test:runner:0.2' + androidTestCompile 'com.android.support.test:rules:0.2' + // Set this dependency if you want to use Hamcrest matching + androidTestCompile 'org.hamcrest:hamcrest-library:1.1' +} +</pre> + </li> + </ul> + +<h2 id="build">Create an Instrumented Unit Test Class</h2> +<p> +Your instrumented unit test class should be written as a JUnit 4 test class. To learn more about +creating JUnit 4 test classes and using JUnit 4 assertions and annotations, see +<a href="local-unit-tests.html#build">Create a Local Unit Test Class</a>. +</p> +<p>To create an instrumented JUnit 4 test class, add the {@code @RunWith(AndroidJUnit4.class)} +annotation at the beginning of your test class definition. You also need to specify the +<a href="{@docRoot}reference/android/support/test/runner/AndroidJUnitRunner.html"> +{@code AndroidJUnitRunner}</a> class +provided in the Android Testing Support Library as your default test runner. This step is described +in more detail in <a href="#run">Run Instrumented Unit Tests</a>. +</p> + +<p>The following example shows how you might write an instrumented unit test to test that +the {@link android.os.Parcelable} interface is implemented correctly for the +{@code LogHistory} class:</p> + +<pre> +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.util.Pair; +import org.junit.Test; +import org.junit.runner.RunWith; +import java.util.List; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +@RunWith(AndroidJUnit4.class) +public class LogHistoryAndroidUnitTest { + + public static final String TEST_STRING = "This is a string"; + public static final long TEST_LONG = 12345678L; + private LogHistory mLogHistory; + + @Before + public void createLogHistory() { + mLogHistory = new LogHistory(); + } + + @Test + public void logHistory_ParcelableWriteRead() { + // Set up the Parcelable object to send and receive. + mLogHistory.addEntry(TEST_STRING, TEST_LONG); + + // Write the data. + Parcel parcel = Parcel.obtain(); + mLogHistory.writeToParcel(parcel, mLogHistory.describeContents()); + + // After you're done with writing, you need to reset the parcel for reading. + parcel.setDataPosition(0); + + // Read the data. + LogHistory createdFromParcel = LogHistory.CREATOR.createFromParcel(parcel); + List<Pair<String, Long>> createdFromParcelData = createdFromParcel.getData(); + + // Verify that the received data is correct. + assertThat(createdFromParcelData.size(), is(1)); + assertThat(createdFromParcelData.get(0).first, is(TEST_STRING)); + assertThat(createdFromParcelData.get(0).second, is(TEST_LONG)); + } +} +</pre> + +<h3 id="test-suites">Creating a test suite</h3> +<p> +To organize the execution of your instrumented unit tests, you can group a collection of test +classes in a <em>test suite</em> class and run these tests together. Test suites can be nested; +your test suite can group other test suites and run all their component test classes together. +</p> + +<p> +A test suite is contained in a test package, similar to the main application package. By +convention, the test suite package name usually ends with the {@code .suite} suffix (for example, +{@code com.example.android.testing.mysample.suite}). +</p> + +<p> +To create a test suite for your unit tests, import the JUnit +<a href="http://junit.sourceforge.net/javadoc/org/junit/runner/RunWith.html" +class="external-link">{@code RunWith}</a> and +<a href="http://junit.sourceforge.net/javadoc/org/junit/runners/Suite.html" +class="external-link">{@code Suite}</a> classes. In your test suite, add the +{@code @RunWith(Suite.class)} and the {@code @Suite.SuitClasses()} annotations. In +the {@code @Suite.SuiteClasses()} annotation, list the individual test classes or test +suites as arguments. +</p> + +<p> +The following example shows how you might implement a test suite called {@code UnitTestSuite} +that groups and runs the {@code CalculatorInstrumentationTest} and +{@code CalculatorAddParameterizedTest} test classes together. +</p> + +<pre> +import com.example.android.testing.mysample.CalculatorAddParameterizedTest; +import com.example.android.testing.mysample.CalculatorInstrumentationTest; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +// Runs all unit tests. +@RunWith(Suite.class) +@Suite.SuiteClasses({CalculatorInstrumentationTest.class, + CalculatorAddParameterizedTest.class}) +public class UnitTestSuite {} +</pre> + +<h2 id="run">Run Instrumented Unit Tests</h2> +<p> +The +<a href="https://developer.android.com/tools/building/plugin-for-gradle.html"> + Android Plug-in for Gradle</a> +provides a default directory ({@code src/androidTest/java}) for you to store the instrumented unit +and integration test classes and test suites that you want to run on a device. The plug-in compiles +the test code in that directory and then executes the test app using a test runner class. You must +set the +<a href="{@docRoot}reference/android/support/test/runner/AndroidJUnitRunner.html"> +{@code AndroidJUnitRunner}</a> class provided in the +<a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a> +as your default test runner.</p> +</p> + +<p>To specify +<a href="{@docRoot}reference/android/support/test/runner/AndroidJUnitRunner.html"> +{@code AndroidJUnitRunner}</a> as the default test instrumentation runner, add the following +setting in your {@code build.gradle} file:</p> +<pre> +android { + defaultConfig { + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } +} +</pre> + +<h3 id="run-from-Android-Studio">Running instrumented unit tests from Android Studio</h3> +<p> +To run instrumented unit tests in your Gradle project from Android Studio: +</p> +<ol> +<li>Open the <strong>Build Variants</strong> window by clicking the left-hand tab, then set the +test artifact to <em>Android Instrumentation Tests</em>. +</li> +<li>In the <strong>Project</strong> window, drill down to your unit test class or method, then + right-click and run it using the Android Test configuration. +</li> +</ol> + +<p>Android Studio displays the results of the unit test execution in the <strong>Run</strong> +window.</p> + +<h3 id="run-from-commandline">Running instrumented unit tests from the command-line</h3> + +<p>To run instrumented unit tests in your Gradle project from the command-line, call the + {@code connectedCheck} (or {@code cC}) task:</p> + +<pre> +./gradlew cC +</pre> + +<p>You can find the generated HTML test result reports in the +{@code <path_to_your_project>/app/build/outputs/reports/androidTests/connected/} directory, +and the corresponding XML files in the +{@code <path_to_your_project>/app/build/outputs/androidTest-results/connected/} directory.</p>
\ No newline at end of file diff --git a/docs/html/training/testing/unit-testing/local-unit-tests.jd b/docs/html/training/testing/unit-testing/local-unit-tests.jd new file mode 100644 index 0000000..421709b --- /dev/null +++ b/docs/html/training/testing/unit-testing/local-unit-tests.jd @@ -0,0 +1,302 @@ +page.title=Building Local Unit Tests +page.tags=testing,androidjunitrunner,junit,unit test,mock +trainingnavtop=true + +@jd:body + +<!-- This is the training bar --> +<div id="tb-wrapper"> +<div id="tb"> + <h2>Dependencies and Prerequisites</h2> + + <ul> + <li>Android Plug-in for Gradle 1.1.0 or higher</li> + </ul> + + <h2>This lesson teaches you to</h2> + + <ol> + <li><a href="#setup">Set Up Your Testing Environment</a></li> + <li><a href="#build">Create a Local Unit Test Class</a></li> + <li><a href="#run">Run Local Unit Tests</a></li> + </ol> + + <h2>Try it out</h2> + + <ul> + <li> +<a href="https://github.com/googlesamples/android-testing/tree/master/unittesting/BasicSample" +class="external-link">Local Unit Tests Code Samples</a></li> + </ul> +</div> +</div> + +<p>If your unit test has no dependencies or only has simple dependencies on Android, you should run +your test on a local development machine. This testing approach is efficient because it helps +you avoid the overhead of loading the target app and unit test code onto a physical device or +emulator every time your test is run. Consequently, the execution time for running your unit +test is greatly reduced. With this approach, you normally use a mocking framework, like +<a href="https://code.google.com/p/mockito/" class="external-link">Mockito</a>, to fulfill any +dependency relationships.</p> + +<p><a href="{@docRoot}tools/building/plugin-for-gradle.html">Android Plug-in for Gradle</a> +version 1.1.0 and higher allows you to create a source directory ({@code src/test/java}) in your +project to store JUnit tests that you want to run on a local machine. This feature improves your +project organization by letting you group your unit tests together into a single source set. You +can run the tests from Android Studio or the command-line, and the plugin executes them on the +local Java Virtual Machine (JVM) on your development machine. </p> + +<h2 id="setup">Set Up Your Testing Environment</h2> +<p>Before building local unit tests, you must:</p> + + <ul> + <li> + <strong>Set up your project structure.</strong> In your Gradle project, the source code for + the target app that you want to test is typically placed under the {@code app/src/main/java} + folder. The source code for your local unit tests must be placed under the + <code>app/src/test/java</code> folder. + To learn more about setting up your project directory, see + <a href="#run">Run Local Unit Tests</a> and + <a href="{@docRoot}tools/projects/index.html">Managing Projects</a>. + </li> + + <li> + <strong>Specify your Android testing dependencies</strong>. In order to use JUnit 4 and + Mockito with your local unit tests, specify the following libraries in + the {@code build.gradle} file of your Android app module: + + <pre> +dependencies { + // Unit testing dependencies + testCompile 'junit:junit:4.12' + // Set this dependency if you want to use Mockito + testCompile 'org.mockito:mockito-core:1.10.19' + // Set this dependency if you want to use Hamcrest matching + androidTestCompile 'org.hamcrest:hamcrest-library:1.1' +} +</pre> + </li> + </ul> + +<h2 id="build">Create a Local Unit Test Class</h2> +<p>Your local unit test class should be written as a JUnit 4 test class. +<a href="http://junit.org/" class="external-link">JUnit</a> is the most popular +and widely-used unit testing framework for Java. The latest version of this framework, JUnit 4, +allows you to write tests in a cleaner and more flexible way than its predecessor versions. Unlike +the previous approach to Android unit testing based on JUnit 3, with JUnit 4, you do not need to +extend the {@code junit.framework.TestCase} class. You also do not need to prefix your test method +name with the {@code ‘test’} keyword, or use any classes in the {@code junit.framework} or +{@code junit.extensions} package.</p> + +<p>To create a basic JUnit 4 test class, create a Java class that contains one or more test methods. +A test method begins with the {@code @Test} annotation and contains the code to exercise +and verify a single functionality in the component that you want to test.</p> + +<p>The following example shows how you might implement a local unit test class. The test method +{@code emailValidator_CorrectEmailSimple_ReturnsTrue} verifies that the {@code isValidEmail()} +method in the app under test returns the correct result.</p> + +<pre> +import org.junit.Test; +import java.util.regex.Pattern; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class EmailValidatorTest { + + @Test + public void emailValidator_CorrectEmailSimple_ReturnsTrue() { + assertThat(EmailValidator.isValidEmail("name@email.com"), is(true)); + } + ... +} +</pre> + +<p>To test that components in your app return the expected results, use the +<a href="http://junit.org/javadoc/latest/org/junit/Assert.html" class="external-link"> +junit.Assert</a> methods to perform validation checks (or <em>assertions</em>) to compare the state +of the component under test against some expected value. To make tests more readable, you +can use <a href="https://code.google.com/p/hamcrest/wiki/Tutorial" class="external-link"> +Hamcrest matchers</a> (such as the {@code is()} and {@code equalTo()} methods) to match the +returned result against the expected result.</p> + +<p>In your JUnit 4 test class, you can use annotations to call out sections in your test code for +special processing, such as:</p> + +<ul> +<li> +{@code @Before}: Use this annotation to specify a block of code with test setup operations. This +code block will be invoked before each test. You can have multiple {@code @Before} methods but +the order which these methods are called is not fixed. +</li> +<li> +{@code @After}: This annotation specifies a block of code with test tear-down operations. This +code block will be called after every test method. You can define multiple {@code @After} +operations in your test code. Use this annotation to release any resources from memory. +</li> +<li> +{@code @Test}: Use this annotation to mark a test method. A single test class can contain +multiple test methods, each prefixed with this annotation. +</li> +<li> +{@code @BeforeClass}: Use this annotation to specify static methods to be invoked only once per +test class. This testing step is useful for expensive operations such as connecting to a database. +</li> +<li> +{@code @AfterClass}: Use this annotation to specify static methods to be invoked only after all +tests in the class have been run. This testing step is useful for releasing any resources allocated +in the {@code @BeforeClass} block. +</li> +<li> +{@code @Test(timeout=<milliseconds>)}: Specifies a timeout period for the test. If the +test starts but does not complete within the given timeout period, it automatically fails. You must +specify the timeout period in milliseconds, for example: {@code @Test(timeout=5000)}. +</li> +</ul> + +<h3 id="mocking-dependencies">Mocking Android dependencies</h3> +<p> +By default, the <a href="{@docRoot}tools/building/plugin-for-gradle.html"> +Android Plug-in for Gradle</a> executes your local unit tests against a modified +version of the {@code android.jar} library, which does not contain any actual code. Instead, method +calls to Android classes from your unit test throw an exception. +</p> +<p> +You can use a mocking framework to stub out external dependencies in your code, to easily test that +your component interacts with a dependency in an expected way. By substituting Android dependencies +with mock objects, you can isolate your unit test from the rest of the Android system while +verifying that the correct methods in those dependencies are called. The +<a href="https://code.google.com/p/mockito/" class="external-link">Mockito</a> mocking framework +for Java (version 1.9.5 and higher) offers compatibility with Android unit testing. +With Mockito, you can configure mock objects to return some specific value when invoked.</p> + +<p>To add a mock object to your local unit test using this framework, follow this programming model: +</p> + +<ol> +<li> +Include the Mockito library dependency in your {@code build.gradle} file, as described in +<a href="#setup">Set Up Your Testing Environment</a>. +</li> +<li>At the beginning of your unit test class definition, add the +{@code @RunWith(MockitoJUnitRunner.class)} annotation. This annotation tells the Mockito test +runner to validate that your usage of the framework is correct and simplifies the initialization of +your mock objects. +</li> +<li>To create a mock object for an Android dependency, add the {@code @Mock} annotation before +the field declaration.</li> +<li>To stub the behavior of the dependency, you can specify a condition and return +value when the condition is met by using the {@code when()} and {@code thenReturn()} methods. +</li> +</ol> + +<p> +The following example shows how you might create a unit test that uses a mock +{@link android.content.Context} object. +</p> + +<pre> +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.CoreMatchers.*; +import static org.mockito.Mockito.*; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import android.content.SharedPreferences; + +@RunWith(MockitoJUnitRunner.class) +public class UnitTestSample { + + private static final String FAKE_STRING = "HELLO WORLD"; + + @Mock + Context mMockContext; + + @Test + public void readStringFromContext_LocalizedString() { + // Given a mocked Context injected into the object under test... + when(mMockContext.getString(R.string.hello_word)) + .thenReturn(FAKE_STRING); + ClassUnderTest myObjectUnderTest = new ClassUnderTest(mMockContext); + + // ...when the string is returned from the object under test... + String result = myObjectUnderTest.getHelloWorldString(); + + // ...then the result should be the expected one. + assertThat(result, is(FAKE_STRING)); + } +} +</pre> + +<p> +To learn more about using the Mockito framework, see the +<a href="http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html" +class="external-link">Mockito API reference</a> and the +{@code SharedPreferencesHelperTest} class in the +<a href="https://github.com/googlesamples/android-testing/tree/master/unittesting/BasicSample" +class="external-link">sample code</a>. +</p> + +<h2 id="run">Run Local Unit Tests</h2> +<p> +The Android Plug-in for Gradle provides a default directory ({@code src/test/java}) for you to +store unit test classes that you want to run on a local JVM. The plug-in compiles the test code in +that directory and then executes the test app locally using the default test runner class. +</p> +<p> +As with production code, you can create unit tests for a +<a href="http://developer.android.com/tools/building/configuring-gradle.html#workBuildVariants" +class="external-link">specific flavor or build type</a>. You should keep unit tests in a test +source tree location that corresponds to your production source tree, such as: + +<table> +<tr> +<th>Path to Production Class</th> +<th>Path to Local Unit Test Class</th> +</tr> +<tr> +<td>{@code src/main/java/Foo.java}</td> +<td>{@code src/test/java/FooTest.java}</td> +</tr> +<tr> +<td>{@code src/debug/java/Foo.java}</td> +<td>{@code src/testDebug/java/FooTest.java}</td> +</tr> +<tr> +<td>{@code src/myFlavor/java/Foo.java}</td> +<td>{@code src/testMyFlavor/java/FooTest.java}</td> +</tr> +</table> + +<h3 id="run-from-Android-Studio">Running local unit tests from Android Studio</h3> +<p> +To run local unit tests in your Gradle project from Android Studio: +</p> +<ol> +<li>In the <strong>Project</strong> window, right click on the project and synchronize your project. +</li> +<li>Open the <strong>Build Variants</strong> window by clicking the left-hand tab, then change the +test artifact to <em>Unit Tests</em>. +</li> +<li>In the <strong>Project</strong> window, drill down to your unit test class or method, then +right-click and run it. +</li> +</ol> + +<p>Android Studio displays the results of the unit test execution in the <strong>Run</strong> +window.</p> + +<h3 id="run-from-commandline">Running local unit tests from the command-line</h3> + +<p>To run local unit tests in your Gradle project from the command-line, call the {@code test} task +command with the {@code --continue} option.</p> + +<pre> +./gradlew test --continue +</pre> + +<p>If there are failing tests, the command will display links to HTML reports (one per build +variant). You can find the generated HTML test result reports in the +{@code <path_to_your_project>/app/build/reports/tests/} directory, and the corresponding XML +files in the {@code <path_to_your_project>/app/build/test-results/} directory.</p>
\ No newline at end of file diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index 862663e..089739b 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -1875,6 +1875,24 @@ results." </ul> </li> </ul> + <ul> + <li class="nav-section"> + <div class="nav-section-header"><a href="<?cs var:toroot ?>training/testing/unit-testing/index.html" + description="How to build effective unit tests for Android apps."> + Building Effective Unit Tests + </a></div> + <ul> + <li><a href="<?cs var:toroot ?>training/testing/unit-testing/local-unit-tests.html"> + <span class="en">Building Local Unit Tests</span> + </a> + </li> + <li><a href="<?cs var:toroot ?>training/testing/unit-testing/instrumented-unit-tests.html"> + <span class="en">Building Instrumented Unit Tests</span> + </a> + </li> + </ul> + </li> + </ul> </li> <!-- End best Testing --> diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 216d9b9..c0bc6d6 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -416,7 +416,8 @@ public class AudioRecord * <br>If the audio format is not specified or is incomplete, its sample rate will be the * default output sample rate of the device (see * {@link AudioManager#PROPERTY_OUTPUT_SAMPLE_RATE}), its channel configuration will be - * {@link AudioFormat#CHANNEL_IN_DEFAULT}. + * {@link AudioFormat#CHANNEL_IN_MONO}, and the encoding will be + * {@link AudioFormat#ENCODING_PCM_16BIT}. * <br>If the buffer size is not specified with {@link #setBufferSizeInBytes(int)}, * the minimum buffer size for the source is used. */ @@ -533,7 +534,22 @@ public class AudioRecord */ public AudioRecord build() throws UnsupportedOperationException { if (mFormat == null) { - mFormat = new AudioFormat.Builder().build(); + mFormat = new AudioFormat.Builder() + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .setChannelMask(AudioFormat.CHANNEL_IN_MONO) + .build(); + } else { + if (mFormat.getEncoding() == AudioFormat.ENCODING_INVALID) { + mFormat = new AudioFormat.Builder(mFormat) + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .build(); + } + if (mFormat.getChannelMask() == AudioFormat.CHANNEL_INVALID + && mFormat.getChannelIndexMask() == AudioFormat.CHANNEL_INVALID) { + mFormat = new AudioFormat.Builder(mFormat) + .setChannelMask(AudioFormat.CHANNEL_IN_MONO) + .build(); + } } if (mAttributes == null) { mAttributes = new AudioAttributes.Builder() diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 1953e75..5f5d61e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -35,6 +35,7 @@ import android.database.MatrixCursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.hardware.camera2.utils.ArrayUtils; +import android.media.AudioManager; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -52,9 +53,11 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; + import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; + import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; @@ -1788,7 +1791,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 118; + private static final int SETTINGS_VERSION = 119; private final int mUserId; @@ -1891,6 +1894,20 @@ public class SettingsProvider extends ContentProvider { int currentVersion = oldVersion; + // v119: Reset zen + ringer mode. + if (currentVersion == 118) { + if (userId == UserHandle.USER_OWNER) { + final SettingsState globalSettings = getGlobalSettingsLocked(); + globalSettings.updateSettingLocked(Settings.Global.ZEN_MODE, + Integer.toString(Settings.Global.ZEN_MODE_OFF), + SettingsState.SYSTEM_PACKAGE_NAME); + globalSettings.updateSettingLocked(Settings.Global.MODE_RINGER, + Integer.toString(AudioManager.RINGER_MODE_NORMAL), + SettingsState.SYSTEM_PACKAGE_NAME); + } + currentVersion = 119; + } + // vXXX: Add new settings above this point. // Return the current version. diff --git a/packages/SystemUI/res/drawable/ic_dnd_total_silence.xml b/packages/SystemUI/res/drawable/ic_dnd_total_silence.xml new file mode 100644 index 0000000..4875974 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_dnd_total_silence.xml @@ -0,0 +1,29 @@ +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0" + android:width="24dp" > + + <path + android:fillColor="#FFFFFFFF" + android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0s10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM12.0,20.5c-4.7,0.0 -8.5,-3.8 -8.5,-8.5S7.3,3.5 12.0,3.5s8.5,3.8 8.5,8.5S16.7,20.5 12.0,20.5z"/> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M12.0,6.0c-3.3,0.0 -6.0,2.7 -6.0,6.0c0.0,3.3 2.7,6.0 6.0,6.0s6.0,-2.7 6.0,-6.0C18.0,8.7 15.4,6.0 12.0,6.0zM15.0,13.0L9.0,13.0l0.0,-2.0l6.0,0.0L15.0,13.0z"/> + +</vector> diff --git a/packages/SystemUI/res/drawable/ic_qs_dnd_on.xml b/packages/SystemUI/res/drawable/ic_qs_dnd_on.xml index 7617ec4..f4c20a9 100644 --- a/packages/SystemUI/res/drawable/ic_qs_dnd_on.xml +++ b/packages/SystemUI/res/drawable/ic_qs_dnd_on.xml @@ -1,7 +1,7 @@ <!-- -Copyright (C) 2015 The Android Open Source Project + Copyright (C) 2015 The Android Open Source Project - Licensed under the Apache License, Version 2.0 (the "License"); + 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 diff --git a/packages/SystemUI/res/drawable/ic_qs_dnd_on_total_silence.xml b/packages/SystemUI/res/drawable/ic_qs_dnd_on_total_silence.xml new file mode 100644 index 0000000..fb26c09 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_dnd_on_total_silence.xml @@ -0,0 +1,29 @@ +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="64dp" + android:height="64dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + + <path + android:fillColor="#FFFFFFFF" + android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0s10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM12.0,20.5c-4.7,0.0 -8.5,-3.8 -8.5,-8.5S7.3,3.5 12.0,3.5s8.5,3.8 8.5,8.5S16.7,20.5 12.0,20.5z"/> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M12.0,6.0c-3.3,0.0 -6.0,2.7 -6.0,6.0c0.0,3.3 2.7,6.0 6.0,6.0s6.0,-2.7 6.0,-6.0C18.0,8.7 15.4,6.0 12.0,6.0zM15.0,13.0L9.0,13.0l0.0,-2.0l6.0,0.0L15.0,13.0z"/> + +</vector> diff --git a/packages/SystemUI/res/drawable/stat_sys_dnd.xml b/packages/SystemUI/res/drawable/stat_sys_dnd.xml index 9361bc0..3bf5e98 100644 --- a/packages/SystemUI/res/drawable/stat_sys_dnd.xml +++ b/packages/SystemUI/res/drawable/stat_sys_dnd.xml @@ -1,7 +1,7 @@ <!-- -Copyright (C) 2015 The Android Open Source Project + Copyright (C) 2015 The Android Open Source Project - Licensed under the Apache License, Version 2.0 (the "License"); + 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 diff --git a/packages/SystemUI/res/drawable/stat_sys_dnd_total_silence.xml b/packages/SystemUI/res/drawable/stat_sys_dnd_total_silence.xml new file mode 100644 index 0000000..82e25ca --- /dev/null +++ b/packages/SystemUI/res/drawable/stat_sys_dnd_total_silence.xml @@ -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. +--> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:insetLeft="2.5dp" + android:insetRight="2.5dp"> + <vector + android:width="17dp" + android:height="17dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0s10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM12.0,20.5c-4.7,0.0 -8.5,-3.8 -8.5,-8.5S7.3,3.5 12.0,3.5s8.5,3.8 8.5,8.5S16.7,20.5 12.0,20.5z"/> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M12.0,6.0c-3.3,0.0 -6.0,2.7 -6.0,6.0c0.0,3.3 2.7,6.0 6.0,6.0s6.0,-2.7 6.0,-6.0C18.0,8.7 15.4,6.0 12.0,6.0zM15.0,13.0L9.0,13.0l0.0,-2.0l6.0,0.0L15.0,13.0z"/> + </vector> +</inset> diff --git a/packages/SystemUI/res/layout/volume_zen_footer.xml b/packages/SystemUI/res/layout/volume_zen_footer.xml index b780f7d..998741c 100644 --- a/packages/SystemUI/res/layout/volume_zen_footer.xml +++ b/packages/SystemUI/res/layout/volume_zen_footer.xml @@ -41,8 +41,7 @@ android:layout_width="@dimen/volume_button_size" android:layout_height="@dimen/volume_button_size" android:layout_marginEnd="7dp" - android:scaleType="center" - android:src="@drawable/ic_dnd" /> + android:scaleType="center" /> <LinearLayout android:layout_width="0dp" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 8606a59..a0b9b65 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -213,7 +213,7 @@ <string name="unlock_label">unlock</string> <!-- Click action label for accessibility for the phone button. [CHAR LIMIT=NONE] --> <string name="phone_label">open phone</string> - <!-- Click action label for accessibility for the phone button. [CHAR LIMIT=NONE] --> + <!-- Click action label for accessibility for the voice assist button. This is not shown on-screen and is an accessibility label for the icon which launches the voice assist from the lock screen.[CHAR LIMIT=NONE] --> <string name="voice_assist_label">open voice assist</string> <!-- Click action label for accessibility for the phone button. [CHAR LIMIT=NONE] --> <string name="camera_label">open camera</string> diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index d1f8963..08659e9 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -204,7 +204,8 @@ public class AssistManager { private boolean getVoiceInteractorSupportsAssistGesture() { try { - return mVoiceInteractionManagerService.activeServiceSupportsAssist(); + return mVoiceInteractionManagerService != null + && mVoiceInteractionManagerService.activeServiceSupportsAssist(); } catch (RemoteException e) { Log.w(TAG, "Failed to call activeServiceSupportsAssistGesture", e); return false; @@ -213,7 +214,8 @@ public class AssistManager { public boolean canVoiceAssistBeLaunchedFromKeyguard() { try { - return mVoiceInteractionManagerService.activeServiceSupportsLaunchFromKeyguard(); + return mVoiceInteractionManagerService != null + && mVoiceInteractionManagerService.activeServiceSupportsLaunchFromKeyguard(); } catch (RemoteException e) { Log.w(TAG, "Failed to call activeServiceSupportsLaunchFromKeyguard", e); return false; @@ -231,7 +233,8 @@ public class AssistManager { private boolean isVoiceSessionRunning() { try { - return mVoiceInteractionManagerService.isSessionRunning(); + return mVoiceInteractionManagerService != null + && mVoiceInteractionManagerService.isSessionRunning(); } catch (RemoteException e) { Log.w(TAG, "Failed to call isSessionRunning", e); return false; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 8205798..5f24619 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -112,7 +112,7 @@ public class DndTile extends QSTile<QSTile.BooleanState> { R.string.accessibility_quick_settings_dnd_priority_on); break; case Global.ZEN_MODE_NO_INTERRUPTIONS: - state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on); + state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on_total_silence); state.label = mContext.getString(R.string.quick_settings_dnd_none_label); state.contentDescription = mContext.getString( R.string.accessibility_quick_settings_dnd_none_on); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java index fe7bc97..3eb6b13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -75,7 +75,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken { mInitialTouchY = y; mInitialTouchX = x; setTrackingHeadsUp(false); - ExpandableView child = mStackScroller.getChildAtPosition(x, y); + ExpandableView child = mStackScroller.getChildAtRawPosition(x, y); mTouchingHeadsUpView = false; if (child instanceof ExpandableNotificationRow) { mPickedChild = (ExpandableNotificationRow) child; 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 da65a01..8ccd222 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -619,7 +619,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, R.color.notification_panel_solid_background))); } - mHeadsUpManager = new HeadsUpManager(context, mNotificationPanel.getViewTreeObserver()); + mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow); mHeadsUpManager.setBar(this); mHeadsUpManager.addListener(this); mHeadsUpManager.addListener(mNotificationPanel); @@ -1869,21 +1869,34 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, @Override public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) { if (inPinnedMode) { + // We need to ensure that the touchable region is updated before the window will be + // resized, in order to not catch any touches. A layout will ensure that + // onComputeInternalInsets will be called and after that we can resize the layout. Let's + // make sure that the window stays small for one frame until the touchableRegion is set. + mNotificationPanel.requestLayout(); mStatusBarWindowManager.setHeadsUpShowing(true); mStatusBarWindowManager.setForceStatusBarVisible(true); - } else { - Runnable endRunnable = new Runnable() { + mStatusBarWindowManager.setForceWindowCollapsed(true); + mNotificationPanel.post(new Runnable() { @Override public void run() { - if (!mHeadsUpManager.hasPinnedHeadsUp()) { - mStatusBarWindowManager.setHeadsUpShowing(false); - } + mStatusBarWindowManager.setForceWindowCollapsed(false); } - }; + }); + } else { if (!mNotificationPanel.isFullyCollapsed()) { - endRunnable.run(); + mStatusBarWindowManager.setHeadsUpShowing(false); } else { - mStackScroller.runAfterAnimationFinished(endRunnable); + mHeadsUpManager.setHeadsUpGoingAway(true); + mStackScroller.runAfterAnimationFinished(new Runnable() { + @Override + public void run() { + if (!mHeadsUpManager.hasPinnedHeadsUp()) { + mStatusBarWindowManager.setHeadsUpShowing(false); + mHeadsUpManager.setHeadsUpGoingAway(false); + } + } + }); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 0e8e844..5942b46 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -219,7 +219,8 @@ public class PhoneStatusBarPolicy { if (DndTile.isVisible(mContext) || DndTile.isCombinedIcon(mContext)) { zenVisible = mZen != Global.ZEN_MODE_OFF; - zenIconId = R.drawable.stat_sys_dnd; + zenIconId = mZen == Global.ZEN_MODE_NO_INTERRUPTIONS + ? R.drawable.stat_sys_dnd_total_silence : R.drawable.stat_sys_dnd; zenDescription = mContext.getString(R.string.quick_settings_dnd_label); } else if (mZen == Global.ZEN_MODE_NO_INTERRUPTIONS) { zenVisible = true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index e6edbea..acf2f57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -378,8 +378,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, if (previousAnimator != null) { if (animate || alpha == mCurrentHeadsUpAlpha) { previousAnimator.cancel(); + } else { + animEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim, TAG_HUN_END_ALPHA); } - animEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim, TAG_HUN_START_ALPHA); } if (alpha != mCurrentHeadsUpAlpha && alpha != animEndValue) { if (animate) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java index e7e4384..422d868 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java @@ -129,8 +129,9 @@ public class StatusBarWindowManager { } private void applyHeight(State state) { - boolean expanded = state.isKeyguardShowingAndNotOccluded() || state.statusBarExpanded - || state.keyguardFadingAway || state.bouncerShowing || state.headsUpShowing; + boolean expanded = !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded() + || state.statusBarExpanded || state.keyguardFadingAway || state.bouncerShowing + || state.headsUpShowing); if (expanded) { mLpChanged.height = ViewGroup.LayoutParams.MATCH_PARENT; } else { @@ -256,6 +257,16 @@ public class StatusBarWindowManager { apply(mCurrentState); } + /** + * Force the window to be collapsed, even if it should theoretically be expanded. + * Used for when a heads-up comes in but we still need to wait for the touchable regions to + * be computed. + */ + public void setForceWindowCollapsed(boolean force) { + mCurrentState.forceCollapsed = force; + apply(mCurrentState); + } + private static class State { boolean keyguardShowing; boolean keyguardOccluded; @@ -267,6 +278,7 @@ public class StatusBarWindowManager { boolean qsExpanded; boolean headsUpShowing; boolean forceStatusBarVisible; + boolean forceCollapsed; /** * The {@link BaseStatusBar} state from the status bar. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index 0db9221..98822a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -25,6 +25,7 @@ import android.provider.Settings; import android.util.ArrayMap; import android.util.Log; import android.util.Pools; +import android.view.View; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; @@ -78,6 +79,8 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } }; + private final View mStatusBarWindowView; + private final int mStatusBarHeight; private PhoneStatusBar mBar; private int mSnoozeLengthMs; private ContentObserver mSettingsObserver; @@ -92,8 +95,11 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL private boolean mIsExpanded; private boolean mHasPinnedNotification; private int[] mTmpTwoArray = new int[2]; + private boolean mHeadsUpGoingAway; + private boolean mWaitingOnCollapseWhenGoingAway; + private boolean mIsObserving; - public HeadsUpManager(final Context context, ViewTreeObserver observer) { + public HeadsUpManager(final Context context, View statusBarWindowView) { Resources resources = context.getResources(); mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay); mSnoozedPackages = new ArrayMap<>(); @@ -119,7 +125,24 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL context.getContentResolver().registerContentObserver( Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false, mSettingsObserver); - observer.addOnComputeInternalInsetsListener(this); + mStatusBarWindowView = statusBarWindowView; + mStatusBarHeight = resources.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + } + + private void updateTouchableRegionListener() { + boolean shouldObserve = mHasPinnedNotification || mHeadsUpGoingAway + || mWaitingOnCollapseWhenGoingAway; + if (shouldObserve == mIsObserving) { + return; + } + if (shouldObserve) { + mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this); + mStatusBarWindowView.requestLayout(); + } else { + mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + } + mIsObserving = shouldObserve; } public void setBar(PhoneStatusBar bar) { @@ -207,6 +230,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL return; } mHasPinnedNotification = hasPinnedNotification; + updateTouchableRegionListener(); for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpPinnedModeChanged(hasPinnedNotification); } @@ -326,7 +350,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { - if (!mIsExpanded && mHasPinnedNotification) { + if (mHasPinnedNotification) { int minX = Integer.MAX_VALUE; int maxX = 0; int minY = Integer.MAX_VALUE; @@ -344,6 +368,9 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); info.touchableRegion.set(minX, minY, maxX, maxY); + } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) { + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); } } @@ -419,6 +446,10 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL mIsExpanded = isExpanded; if (isExpanded) { unpinAll(); + // make sure our state is sane + mWaitingOnCollapseWhenGoingAway = false; + mHeadsUpGoingAway = false; + updateTouchableRegionListener(); } } } @@ -443,6 +474,40 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL return aEntry.compareTo(bEntry); } + /** + * Set that we are exiting the headsUp pinned mode, but some notifications might still be + * animating out. This is used to keep the touchable regions in a sane state. + */ + public void setHeadsUpGoingAway(boolean headsUpGoingAway) { + if (headsUpGoingAway != mHeadsUpGoingAway) { + mHeadsUpGoingAway = headsUpGoingAway; + if (!headsUpGoingAway) { + waitForStatusBarLayout(); + } + updateTouchableRegionListener(); + } + } + + /** + * We need to wait on the whole panel to collapse, before we can remove the touchable region + * listener. + */ + private void waitForStatusBarLayout() { + mWaitingOnCollapseWhenGoingAway = true; + mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, + int oldTop, int oldRight, int oldBottom) { + if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) { + mStatusBarWindowView.removeOnLayoutChangeListener(this); + mWaitingOnCollapseWhenGoingAway = false; + updateTouchableRegionListener(); + } + } + }); + } + /** * This represents a notification and how long it is in a heads up mode. It also manages its diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java index ccb2b5a..66c4993 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java @@ -21,6 +21,7 @@ import android.provider.Settings.Global; import android.service.notification.ZenModeConfig; import android.util.AttributeSet; import android.view.View; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -38,6 +39,7 @@ public class ZenFooter extends LinearLayout { private final Context mContext; private final SpTexts mSpTexts; + private ImageView mIcon; private TextView mSummaryLine1; private TextView mSummaryLine2; private TextView mEndNowButton; @@ -55,6 +57,7 @@ public class ZenFooter extends LinearLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); + mIcon = (ImageView) findViewById(R.id.volume_zen_icon); mSummaryLine1 = (TextView) findViewById(R.id.volume_zen_summary_line_1); mSummaryLine2 = (TextView) findViewById(R.id.volume_zen_summary_line_2); mEndNowButton = (TextView) findViewById(R.id.volume_zen_end_now); @@ -115,6 +118,7 @@ public class ZenFooter extends LinearLayout { } public void update() { + mIcon.setImageResource(isZenNone() ? R.drawable.ic_dnd_total_silence : R.drawable.ic_dnd); final String line1 = isZenPriority() ? mContext.getString(R.string.interruption_level_priority) : isZenAlarms() ? mContext.getString(R.string.interruption_level_alarms) diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java index 3b61f9d..70a5821 100644 --- a/rs/java/android/renderscript/Allocation.java +++ b/rs/java/android/renderscript/Allocation.java @@ -1422,6 +1422,8 @@ public class Allocation extends BaseObj { } /** + * @hide + * * This is only intended to be used by auto-generated code reflected from * the RenderScript script files and should not be used by developers. * diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp index a49fb76..1833a1c 100644 --- a/rs/jni/android_renderscript_RenderScript.cpp +++ b/rs/jni/android_renderscript_RenderScript.cpp @@ -43,9 +43,6 @@ //#define LOG_API ALOGE static constexpr bool kLogApi = false; -static constexpr size_t kMaxNumberArgsAndBindings = 1000; -static constexpr size_t kMaxNumberClosuresInScriptGroup = 1000000; -static constexpr size_t kMaxNumberKernelArguments = 256; using namespace android; @@ -371,7 +368,7 @@ nClosureCreate(JNIEnv *_env, jobject _this, jlong con, jlong kernelID, goto exit; } - if (numValues > kMaxNumberArgsAndBindings) { + if (numValues > RS_CLOSURE_MAX_NUMBER_ARGS_AND_BINDINGS) { ALOGE("Too many arguments or globals in closure creation"); goto exit; } @@ -456,7 +453,7 @@ nInvokeClosureCreate(JNIEnv *_env, jobject _this, jlong con, jlong invokeID, numValues = (size_t) fieldIDs_length; - if (numValues > kMaxNumberArgsAndBindings) { + if (numValues > RS_CLOSURE_MAX_NUMBER_ARGS_AND_BINDINGS) { ALOGE("Too many arguments or globals in closure creation"); goto exit; } @@ -521,7 +518,7 @@ nScriptGroup2Create(JNIEnv *_env, jobject _this, jlong con, jstring name, RsClosure* closures; - if (numClosures > (jsize) kMaxNumberClosuresInScriptGroup) { + if (numClosures > (jsize) RS_SCRIPT_GROUP_MAX_NUMBER_CLOSURES) { ALOGE("Too many closures in script group"); goto exit; } @@ -1867,7 +1864,7 @@ nScriptForEach(JNIEnv *_env, jobject _this, jlong con, jlong script, jint slot, if (ains != nullptr) { in_len = _env->GetArrayLength(ains); - if (in_len > (jint)kMaxNumberKernelArguments) { + if (in_len > (jint)RS_KERNEL_MAX_ARGUMENTS) { ALOGE("Too many arguments in kernel launch."); // TODO (b/20758983): Report back to Java and throw an exception return; diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index b7bc0f0..76465d4 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -142,6 +142,7 @@ public class DeviceIdleController extends SystemService { private PendingIntent mAlarmIntent; private Intent mIdleIntent; private Display mCurDisplay; + private boolean mIdleDisabled; private boolean mScreenOn; private boolean mCharging; private boolean mSigMotionActive; @@ -187,10 +188,16 @@ public class DeviceIdleController extends SystemService { private final ArrayMap<String, Integer> mPowerSaveWhitelistUserApps = new ArrayMap<>(); /** - * UIDs that have been white-listed to opt out of power save restrictions. + * App IDs that have been white-listed to opt out of power save restrictions. */ private final SparseBooleanArray mPowerSaveWhitelistAppIds = new SparseBooleanArray(); + /** + * Current app IDs that are in the complete power save white list. This array can + * be shared with others because it will not be modified once set. + */ + private int[] mPowerSaveWhitelistAppIdArray = new int[0]; + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { @@ -381,6 +388,8 @@ public class DeviceIdleController extends SystemService { filter.addAction(ACTION_STEP_IDLE_STATE); getContext().registerReceiver(mReceiver, filter); + mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAppIdArray); + mDisplayManager.registerDisplayListener(mDisplayListener, null); updateDisplayLocked(); } @@ -445,12 +454,7 @@ public class DeviceIdleController extends SystemService { public int[] getAppIdWhitelistInternal() { synchronized (this) { - int size = mPowerSaveWhitelistAppIds.size(); - int[] appids = new int[size]; - for (int i = 0; i < size; i++) { - appids[i] = mPowerSaveWhitelistAppIds.keyAt(i); - } - return appids; + return mPowerSaveWhitelistAppIdArray; } } @@ -499,7 +503,7 @@ public class DeviceIdleController extends SystemService { } void becomeInactiveIfAppropriateLocked() { - if (!mScreenOn && !mCharging && mState == STATE_ACTIVE) { + if (!mScreenOn && !mCharging && !mIdleDisabled && mState == STATE_ACTIVE) { // Screen has turned off; we are now going to become inactive and start // waiting to see if we will ultimately go idle. mState = STATE_INACTIVE; @@ -625,6 +629,15 @@ public class DeviceIdleController extends SystemService { for (int i=0; i<mPowerSaveWhitelistUserApps.size(); i++) { mPowerSaveWhitelistAppIds.put(mPowerSaveWhitelistUserApps.valueAt(i), true); } + int size = mPowerSaveWhitelistAppIds.size(); + int[] appids = new int[size]; + for (int i = 0; i < size; i++) { + appids[i] = mPowerSaveWhitelistAppIds.keyAt(i); + } + mPowerSaveWhitelistAppIdArray = appids; + if (mLocalPowerManager != null) { + mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAppIdArray); + } } private void reportPowerSaveWhitelistChangedLocked() { @@ -763,6 +776,10 @@ public class DeviceIdleController extends SystemService { pw.println("Commands:"); pw.println(" step"); pw.println(" Immediately step to next state, without waiting for alarm."); + pw.println(" disable"); + pw.println(" Completely disable device idle mode."); + pw.println(" enable"); + pw.println(" Re-enable device idle mode after it had previously been disabled."); pw.println(" whitelist"); pw.println(" Add (prefix with +) or remove (prefix with -) packages."); } @@ -782,12 +799,32 @@ public class DeviceIdleController extends SystemService { if ("-h".equals(arg)) { dumpHelp(pw); return; + } else if ("-a".equals(arg)) { + // Ignore, we always dump all. } else if ("step".equals(arg)) { synchronized (this) { stepIdleStateLocked(); pw.print("Stepped to: "); pw.println(stateToString(mState)); } return; + } else if ("disable".equals(arg)) { + synchronized (this) { + if (!mIdleDisabled) { + mIdleDisabled = true; + becomeActiveLocked("disabled"); + pw.println("Idle mode disabled"); + } + } + return; + } else if ("enable".equals(arg)) { + synchronized (this) { + if (mIdleDisabled) { + mIdleDisabled = false; + becomeInactiveIfAppropriateLocked(); + pw.println("Idle mode enabled"); + } + } + return; } else if ("whitelist".equals(arg)) { i++; while (i < args.length) { @@ -853,6 +890,7 @@ public class DeviceIdleController extends SystemService { } pw.print(" mSigMotionSensor="); pw.println(mSigMotionSensor); pw.print(" mCurDisplay="); pw.println(mCurDisplay); + pw.print(" mIdleDisabled="); pw.println(mIdleDisabled); pw.print(" mScreenOn="); pw.println(mScreenOn); pw.print(" mCharging="); pw.println(mCharging); pw.print(" mSigMotionActive="); pw.println(mSigMotionActive); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 10855e2..76ee3bc 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -19956,7 +19956,7 @@ public final class ActivityManagerService extends ActivityManagerNative public ComponentName getHomeActivityForUser(int userId) { synchronized (ActivityManagerService.this) { ActivityRecord homeActivity = mStackSupervisor.getHomeActivityForUser(userId); - return homeActivity.realActivity; + return homeActivity == null ? null : homeActivity.realActivity; } } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index aa365ea..2149b7a 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.REMOTE_AUDIO_PLAYBACK; import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; +import static android.os.Process.FIRST_APPLICATION_UID; import android.Manifest; import android.app.ActivityManager; @@ -5024,6 +5025,10 @@ public class AudioService extends IAudioService.Stub { } for (int j = packages.size() - 1; j >= 0; j--) { PackageInfo pkg = packages.get(j); + // Skip system processes + if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) { + continue; + } if (homeActivityName != null && pkg.packageName.equals(homeActivityName.getPackageName()) && pkg.applicationInfo.isSystemApp()) { diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 65949bf..8086461 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -765,8 +765,9 @@ public class MediaSessionService extends SystemService implements Monitor { // If we don't have a media button receiver to fall back on // include non-playing sessions for dispatching UserRecord ur = mUserRecords.get(ActivityManager.getCurrentUser()); - boolean useNotPlayingSessions = ur.mLastMediaButtonReceiver == null - && ur.mRestoredMediaButtonReceiver == null; + boolean useNotPlayingSessions = (ur == null) || + (ur.mLastMediaButtonReceiver == null + && ur.mRestoredMediaButtonReceiver == null); MediaSessionRecord session = mPriorityStack .getDefaultMediaButtonSession(mCurrentUserId, useNotPlayingSessions); if (isVoiceKey(keyEvent.getKeyCode())) { diff --git a/services/core/java/com/android/server/pm/BasePermission.java b/services/core/java/com/android/server/pm/BasePermission.java index 30f8b37..18407c9 100644 --- a/services/core/java/com/android/server/pm/BasePermission.java +++ b/services/core/java/com/android/server/pm/BasePermission.java @@ -20,8 +20,6 @@ import android.content.pm.PackageParser; import android.content.pm.PermissionInfo; import android.os.UserHandle; -import com.android.internal.util.ArrayUtils; - final class BasePermission { final static int TYPE_NORMAL = 0; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index a120c1f..477af72 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -206,6 +206,7 @@ import com.android.server.ServiceThread; import com.android.server.SystemConfig; import com.android.server.Watchdog; import com.android.server.pm.Settings.DatabaseVersion; +import com.android.server.pm.PermissionsState.PermissionState; import com.android.server.storage.DeviceStorageMonitorInternal; import org.xmlpull.v1.XmlPullParser; @@ -304,6 +305,8 @@ public class PackageManagerService extends IPackageManager.Stub { static final int REMOVE_CHATTY = 1<<16; + private static final int[] EMPTY_INT_ARRAY = new int[0]; + /** * Timeout (in milliseconds) after which the watchdog should declare that * our handler thread is wedged. The usual default for such things is one @@ -3139,18 +3142,26 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private static void enforceOnlySystemUpdatesPermissionPolicyFlags(int flagMask, int flagValues) { + if (((flagMask & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0 + || (flagValues & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) + && getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only the system can modify policy flags"); + } + } + @Override - public void grantPermission(String packageName, String name, int userId) { + public void grantRuntimePermission(String packageName, String name, int userId) { if (!sUserManager.exists(userId)) { return; } mContext.enforceCallingOrSelfPermission( android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, - "grantPermission"); + "grantRuntimePermission"); enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, - "grantPermission"); + "grantRuntimePermission"); boolean gidsChanged = false; final SettingBase sb; @@ -3197,17 +3208,17 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - public void revokePermission(String packageName, String name, int userId) { + public void revokeRuntimePermission(String packageName, String name, int userId) { if (!sUserManager.exists(userId)) { return; } mContext.enforceCallingOrSelfPermission( android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, - "revokePermission"); + "revokeRuntimePermission"); enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, - "revokePermission"); + "revokeRuntimePermission"); final SettingBase sb; @@ -3236,7 +3247,7 @@ public class PackageManagerService extends IPackageManager.Stub { return; } - // Critical, after this call all should never have the permission. + // Critical, after this call app should never have the permission. mSettings.writeRuntimePermissionsForUserLPr(userId, true); } @@ -3244,6 +3255,86 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + public int getPermissionFlags(String name, String packageName, int userId) { + if (!sUserManager.exists(userId)) { + return 0; + } + + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, + "getPermissionFlags"); + + enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, + "getPermissionFlags"); + + synchronized (mPackages) { + final PackageParser.Package pkg = mPackages.get(packageName); + if (pkg == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + + final BasePermission bp = mSettings.mPermissions.get(name); + if (bp == null) { + throw new IllegalArgumentException("Unknown permission: " + name); + } + + SettingBase sb = (SettingBase) pkg.mExtras; + if (sb == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + + PermissionsState permissionsState = sb.getPermissionsState(); + return permissionsState.getPermissionFlags(name, userId); + } + } + + @Override + public void updatePermissionFlags(String name, String packageName, int flagMask, + int flagValues, int userId) { + if (!sUserManager.exists(userId)) { + return; + } + + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, + "updatePermissionFlags"); + + enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, + "updatePermissionFlags"); + + enforceOnlySystemUpdatesPermissionPolicyFlags(flagMask, flagValues); + + synchronized (mPackages) { + final PackageParser.Package pkg = mPackages.get(packageName); + if (pkg == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + + final BasePermission bp = mSettings.mPermissions.get(name); + if (bp == null) { + throw new IllegalArgumentException("Unknown permission: " + name); + } + + SettingBase sb = (SettingBase) pkg.mExtras; + if (sb == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + + PermissionsState permissionsState = sb.getPermissionsState(); + + if (permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues)) { + // Install and runtime permissions are stored in different places, + // so figure out what permission changed and persist the change. + if (permissionsState.getInstallPermissionState(name) != null) { + scheduleWriteSettingsLocked(); + } else if (permissionsState.getRuntimePermissionState(name, userId) != null) { + mSettings.writeRuntimePermissionsForUserLPr(userId, false); + } + } + } + } + + @Override public boolean isProtectedBroadcast(String actionName) { synchronized (mPackages) { return mProtectedBroadcasts.contains(actionName); @@ -7530,8 +7621,8 @@ public class PackageManagerService extends IPackageManager.Stub { final int[] currentUserIds = UserManagerService.getInstance().getUserIds(); - int[] upgradeUserIds = PermissionsState.USERS_NONE; - int[] changedRuntimePermissionUserIds = PermissionsState.USERS_NONE; + int[] upgradeUserIds = EMPTY_INT_ARRAY; + int[] changedRuntimePermissionUserIds = EMPTY_INT_ARRAY; boolean changedInstallPermission = false; @@ -7657,11 +7748,18 @@ public class PackageManagerService extends IPackageManager.Stub { // Grant previously granted runtime permissions. for (int userId : UserManagerService.getInstance().getUserIds()) { if (origPermissions.hasRuntimePermission(bp.name, userId)) { + PermissionState permissionState = origPermissions + .getRuntimePermissionState(bp.name, userId); + final int flags = permissionState.getFlags(); if (permissionsState.grantRuntimePermission(bp, userId) == PermissionsState.PERMISSION_OPERATION_FAILURE) { // If we cannot put the permission as it was, we have to write. changedRuntimePermissionUserIds = ArrayUtils.appendInt( changedRuntimePermissionUserIds, userId); + } else { + // Propagate the permission flags. + permissionsState.updatePermissionFlags(bp, userId, + flags, flags); } } } @@ -7669,13 +7767,28 @@ public class PackageManagerService extends IPackageManager.Stub { case GRANT_UPGRADE: { // Grant runtime permissions for a previously held install permission. - permissionsState.revokeInstallPermission(bp); - for (int userId : upgradeUserIds) { - if (permissionsState.grantRuntimePermission(bp, userId) != - PermissionsState.PERMISSION_OPERATION_FAILURE) { - // If we granted the permission, we have to write. - changedRuntimePermissionUserIds = ArrayUtils.appendInt( - changedRuntimePermissionUserIds, userId); + PermissionState permissionState = origPermissions + .getInstallPermissionState(bp.name); + final int flags = permissionState != null ? permissionState.getFlags() : 0; + + origPermissions.revokeInstallPermission(bp); + // We will be transferring the permission flags, so clear them. + origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL, + PackageManager.MASK_PERMISSION_FLAGS, 0); + + // If the permission is not to be promoted to runtime we ignore it and + // also its other flags as they are not applicable to install permissions. + if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) { + for (int userId : upgradeUserIds) { + if (permissionsState.grantRuntimePermission(bp, userId) != + PermissionsState.PERMISSION_OPERATION_FAILURE) { + // Transfer the permission flags. + permissionsState.updatePermissionFlags(bp, userId, + flags, flags); + // If we granted the permission, we have to write. + changedRuntimePermissionUserIds = ArrayUtils.appendInt( + changedRuntimePermissionUserIds, userId); + } } } } break; @@ -7692,6 +7805,9 @@ public class PackageManagerService extends IPackageManager.Stub { } else { if (permissionsState.revokeInstallPermission(bp) != PermissionsState.PERMISSION_OPERATION_FAILURE) { + // Also drop the permission flags. + permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL, + PackageManager.MASK_PERMISSION_FLAGS, 0); changedInstallPermission = true; Slog.i(TAG, "Un-granting permission " + perm + " from package " + pkg.packageName @@ -9101,7 +9217,9 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { result = mSettings.updateIntentFilterVerificationStatusLPw(packageName, status, userId); } - scheduleWritePackageRestrictionsLocked(userId); + if (result) { + scheduleWritePackageRestrictionsLocked(userId); + } return result; } @@ -9138,9 +9256,11 @@ public class PackageManagerService extends IPackageManager.Stub { public boolean setDefaultBrowserPackageName(String packageName, int userId) { synchronized (mPackages) { boolean result = mSettings.setDefaultBrowserPackageNameLPr(packageName, userId); - result |= updateIntentVerificationStatus(packageName, - PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, - UserHandle.myUserId()); + if (packageName != null) { + result |= updateIntentVerificationStatus(packageName, + PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, + UserHandle.myUserId()); + } return result; } } @@ -11854,6 +11974,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (deletedPs != null) { if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) { clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL); + clearDefaultBrowserIfNeeded(packageName); if (outInfo != null) { mSettings.mKeySetManagerService.removeAppKeySetDataLPw(packageName); outInfo.removedAppId = mSettings.removePackageLPw(packageName); @@ -12388,7 +12509,7 @@ public class PackageManagerService extends IPackageManager.Stub { succeded = deleteApplicationCacheFilesLI(packageName, userId); } clearExternalStorageDataSync(packageName, userId, false); - if(observer != null) { + if (observer != null) { try { observer.onRemoveCompleted(packageName, succeded); } catch (RemoteException e) { @@ -12757,6 +12878,17 @@ public class PackageManagerService extends IPackageManager.Stub { } } + + void clearDefaultBrowserIfNeeded(String packageName) { + for (int oneUserId : sUserManager.getUserIds()) { + String defaultBrowserPackageName = getDefaultBrowserPackageName(oneUserId); + if (TextUtils.isEmpty(defaultBrowserPackageName)) continue; + if (packageName.equals(defaultBrowserPackageName)) { + setDefaultBrowserPackageName(null, oneUserId); + } + } + } + @Override public void resetPreferredActivities(int userId) { /* TODO: Actually use userId. Why is it being passed in? */ diff --git a/services/core/java/com/android/server/pm/PermissionsState.java b/services/core/java/com/android/server/pm/PermissionsState.java index 3749957..171a50d 100644 --- a/services/core/java/com/android/server/pm/PermissionsState.java +++ b/services/core/java/com/android/server/pm/PermissionsState.java @@ -20,10 +20,13 @@ import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.SparseArray; import com.android.internal.util.ArrayUtils; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Set; /** @@ -55,15 +58,8 @@ public final class PermissionsState { /** The permission operation failed. */ public static final int PERMISSION_OPERATION_FAILURE = 3; - public static final int[] USERS_ALL = {UserHandle.USER_ALL}; - - public static final int[] USERS_NONE = {}; - private static final int[] NO_GIDS = {}; - private static final int FLAG_INSTALL_PERMISSIONS = 1 << 0; - private static final int FLAG_RUNTIME_PERMISSIONS = 1 << 1; - private ArrayMap<String, PermissionData> mPermissions; private int[] mGlobalGids = NO_GIDS; @@ -147,14 +143,16 @@ public final class PermissionsState { } /** - * Grant a runtime permission. + * Grant a runtime permission for a given device user. * * @param permission The permission to grant. + * @param userId The device user id. * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link * #PERMISSION_OPERATION_FAILURE}. */ public int grantRuntimePermission(BasePermission permission, int userId) { + enforceValidUserId(userId); if (userId == UserHandle.USER_ALL) { return PERMISSION_OPERATION_FAILURE; } @@ -162,15 +160,18 @@ public final class PermissionsState { } /** - * Revoke a runtime permission for a given device user. + * Revoke a runtime permission for a given device user. * * @param permission The permission to revoke. * @param userId The device user id. * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link * #PERMISSION_OPERATION_FAILURE}. + * + * @see android.content.pm.PackageManager.PermissionFlags */ public int revokeRuntimePermission(BasePermission permission, int userId) { + enforceValidUserId(userId); if (userId == UserHandle.USER_ALL) { return PERMISSION_OPERATION_FAILURE; } @@ -178,17 +179,6 @@ public final class PermissionsState { } /** - * Gets whether this state has a given permission, regardless if - * it is install time or runtime one. - * - * @param name The permission name. - * @return Whether this state has the permission. - */ - public boolean hasPermission(String name) { - return mPermissions != null && mPermissions.get(name) != null; - } - - /** * Gets whether this state has a given runtime permission for a * given device user id. * @@ -197,6 +187,7 @@ public final class PermissionsState { * @return Whether this state has the permission. */ public boolean hasRuntimePermission(String name, int userId) { + enforceValidUserId(userId); return !hasInstallPermission(name) && hasPermission(name, userId); } @@ -211,36 +202,6 @@ public final class PermissionsState { } /** - * Revokes a permission for all users regardless if it is an install or - * a runtime permission. - * - * @param permission The permission to revoke. - * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, - * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link - * #PERMISSION_OPERATION_FAILURE}. - */ - public int revokePermission(BasePermission permission) { - if (!hasPermission(permission.name)) { - return PERMISSION_OPERATION_FAILURE; - } - - int result = PERMISSION_OPERATION_SUCCESS; - - PermissionData permissionData = mPermissions.get(permission.name); - for (int userId : permissionData.getUserIds()) { - if (revokePermission(permission, userId) - == PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) { - result = PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED; - break; - } - } - - mPermissions.remove(permission.name); - - return result; - } - - /** * Gets whether the state has a given permission for the specified * user, regardless if this is an install or a runtime permission. * @@ -256,49 +217,133 @@ public final class PermissionsState { } PermissionData permissionData = mPermissions.get(name); - return permissionData != null && permissionData.hasUserId(userId); + return permissionData != null && permissionData.isGranted(userId); } /** - * Gets all permissions regardless if they are install or runtime. + * Gets all permissions for a given device user id regardless if they + * are install time or runtime permissions. * + * @param userId The device user id. * @return The permissions or an empty set. */ - public Set<String> getPermissions() { - if (mPermissions != null) { - return mPermissions.keySet(); + public Set<String> getPermissions(int userId) { + enforceValidUserId(userId); + + if (mPermissions == null) { + return Collections.emptySet(); } - return Collections.emptySet(); + Set<String> permissions = new ArraySet<>(); + + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + String permission = mPermissions.keyAt(i); + + if (hasInstallPermission(permission)) { + permissions.add(permission); + } + + if (userId != UserHandle.USER_ALL) { + if (hasRuntimePermission(permission, userId)) { + permissions.add(permission); + } + } + } + + return permissions; } /** - * Gets all permissions for a given device user id regardless if they - * are install time or runtime permissions. + * Gets the state for an install permission or null if no such. * + * @param name The permission name. + * @return The permission state. + */ + public PermissionState getInstallPermissionState(String name) { + return getPermissionState(name, UserHandle.USER_ALL); + } + + /** + * Gets the state for a runtime permission or null if no such. + * + * @param name The permission name. * @param userId The device user id. - * @return The permissions or an empty set. + * @return The permission state. */ - public Set<String> getPermissions(int userId) { - return getPermissionsInternal(FLAG_INSTALL_PERMISSIONS | FLAG_RUNTIME_PERMISSIONS, userId); + public PermissionState getRuntimePermissionState(String name, int userId) { + enforceValidUserId(userId); + return getPermissionState(name, userId); } /** - * Gets all runtime permissions. + * Gets all install permission states. * - * @return The permissions or an empty set. + * @return The permission states or an empty set. */ - public Set<String> getRuntimePermissions(int userId) { - return getPermissionsInternal(FLAG_RUNTIME_PERMISSIONS, userId); + public List<PermissionState> getInstallPermissionStates() { + return getPermissionStatesInternal(UserHandle.USER_ALL); } /** - * Gets all install permissions. + * Gets all runtime permission states. * - * @return The permissions or an empty set. + * @return The permission states or an empty set. + */ + public List<PermissionState> getRuntimePermissionStates(int userId) { + enforceValidUserId(userId); + return getPermissionStatesInternal(userId); + } + + /** + * Gets the flags for a permission regardless if it is install or + * runtime permission. + * + * @param name The permission name. + * @return The permission state or null if no such. */ - public Set<String> getInstallPermissions() { - return getPermissionsInternal(FLAG_INSTALL_PERMISSIONS, UserHandle.USER_ALL); + public int getPermissionFlags(String name, int userId) { + PermissionState installPermState = getInstallPermissionState(name); + if (installPermState != null) { + return installPermState.getFlags(); + } + PermissionState runtimePermState = getRuntimePermissionState(name, userId); + if (runtimePermState != null) { + return runtimePermState.getFlags(); + } + return 0; + } + + /** + * Update the flags associated with a given permission. + * @param permission The permission whose flags to update. + * @param userId The user for which to update. + * @param flagMask Mask for which flags to change. + * @param flagValues New values for the mask flags. + * @return Whether the permission flags changed. + */ + public boolean updatePermissionFlags(BasePermission permission, int userId, + int flagMask, int flagValues) { + enforceValidUserId(userId); + + final boolean mayChangeFlags = flagValues != 0 || flagMask != 0; + + if (mPermissions == null) { + if (!mayChangeFlags) { + return false; + } + ensurePermissionData(permission); + } + + PermissionData permissionData = mPermissions.get(permission.name); + if (permissionData == null) { + if (!mayChangeFlags) { + return false; + } + permissionData = ensurePermissionData(permission); + } + + return permissionData.updateFlags(userId, flagMask, flagValues); } /** @@ -357,36 +402,37 @@ public final class PermissionsState { mPermissions = null; } - private Set<String> getPermissionsInternal(int flags, int userId) { - enforceValidUserId(userId); - + private PermissionState getPermissionState(String name, int userId) { if (mPermissions == null) { - return Collections.emptySet(); + return null; } + PermissionData permissionData = mPermissions.get(name); + if (permissionData == null) { + return null; + } + return permissionData.getPermissionState(userId); + } - if (userId == UserHandle.USER_ALL) { - flags = FLAG_INSTALL_PERMISSIONS; + private List<PermissionState> getPermissionStatesInternal(int userId) { + enforceValidUserId(userId); + + if (mPermissions == null) { + return Collections.emptyList(); } - Set<String> permissions = new ArraySet<>(); + List<PermissionState> permissionStates = new ArrayList<>(); final int permissionCount = mPermissions.size(); for (int i = 0; i < permissionCount; i++) { - String permission = mPermissions.keyAt(i); + PermissionData permissionData = mPermissions.valueAt(i); - if ((flags & FLAG_INSTALL_PERMISSIONS) != 0) { - if (hasInstallPermission(permission)) { - permissions.add(permission); - } - } - if ((flags & FLAG_RUNTIME_PERMISSIONS) != 0) { - if (hasRuntimePermission(permission, userId)) { - permissions.add(permission); - } + PermissionState permissionState = permissionData.getPermissionState(userId); + if (permissionState != null) { + permissionStates.add(permissionState); } } - return permissions; + return permissionStates; } private int grantPermission(BasePermission permission, int userId) { @@ -397,17 +443,9 @@ public final class PermissionsState { final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId)); final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS; - if (mPermissions == null) { - mPermissions = new ArrayMap<>(); - } - - PermissionData permissionData = mPermissions.get(permission.name); - if (permissionData == null) { - permissionData = new PermissionData(permission); - mPermissions.put(permission.name, permissionData); - } + PermissionData permissionData = ensurePermissionData(permission); - if (!permissionData.addUserId(userId)) { + if (!permissionData.grant(userId)) { return PERMISSION_OPERATION_FAILURE; } @@ -431,16 +469,12 @@ public final class PermissionsState { PermissionData permissionData = mPermissions.get(permission.name); - if (!permissionData.removeUserId(userId)) { + if (!permissionData.revoke(userId)) { return PERMISSION_OPERATION_FAILURE; } - if (permissionData.getUserIds() == USERS_NONE) { - mPermissions.remove(permission.name); - } - - if (mPermissions.isEmpty()) { - mPermissions = null; + if (permissionData.isDefault()) { + ensureNoPermissionData(permission.name); } if (hasGids) { @@ -468,9 +502,31 @@ public final class PermissionsState { } } + private PermissionData ensurePermissionData(BasePermission permission) { + if (mPermissions == null) { + mPermissions = new ArrayMap<>(); + } + PermissionData permissionData = mPermissions.get(permission.name); + if (permissionData == null) { + permissionData = new PermissionData(permission); + mPermissions.put(permission.name, permissionData); + } + return permissionData; + } + + private void ensureNoPermissionData(String name) { + if (mPermissions == null) { + return; + } + mPermissions.remove(name); + if (mPermissions.isEmpty()) { + mPermissions = null; + } + } + private static final class PermissionData { private final BasePermission mPerm; - private int[] mUserIds = USERS_NONE; + private SparseArray<PermissionState> mUserStates = new SparseArray<>(); public PermissionData(BasePermission perm) { mPerm = perm; @@ -478,11 +534,11 @@ public final class PermissionsState { public PermissionData(PermissionData other) { this(other.mPerm); - - if (other.mUserIds == USERS_ALL || other.mUserIds == USERS_NONE) { - mUserIds = other.mUserIds; - } else { - mUserIds = Arrays.copyOf(other.mUserIds, other.mUserIds.length); + final int otherStateCount = other.mUserStates.size(); + for (int i = 0; i < otherStateCount; i++) { + final int otherUserId = other.mUserStates.keyAt(i); + PermissionState otherState = other.mUserStates.valueAt(i); + mUserStates.put(otherUserId, new PermissionState(otherState)); } } @@ -490,53 +546,146 @@ public final class PermissionsState { return mPerm.computeGids(userId); } - public int[] getUserIds() { - return mUserIds; - } - - public boolean hasUserId(int userId) { - if (mUserIds == USERS_ALL) { - return true; + public boolean isGranted(int userId) { + if (isInstallPermission()) { + userId = UserHandle.USER_ALL; } - if (userId != UserHandle.USER_ALL) { - return ArrayUtils.contains(mUserIds, userId); + PermissionState userState = mUserStates.get(userId); + if (userState == null) { + return false; } - return false; + return userState.mGranted; } - public boolean addUserId(int userId) { - if (hasUserId(userId)) { + public boolean grant(int userId) { + if (!isCompatibleUserId(userId)) { return false; } - if (userId == UserHandle.USER_ALL) { - mUserIds = USERS_ALL; - return true; + if (isGranted(userId)) { + return false; } - mUserIds = ArrayUtils.appendInt(mUserIds, userId); + PermissionState userState = mUserStates.get(userId); + if (userState == null) { + userState = new PermissionState(mPerm.name); + mUserStates.put(userId, userState); + } + + userState.mGranted = true; return true; } - public boolean removeUserId(int userId) { - if (!hasUserId(userId)) { + public boolean revoke(int userId) { + if (!isCompatibleUserId(userId)) { return false; } - if (mUserIds == USERS_ALL) { - mUserIds = UserManagerService.getInstance().getUserIds(); + if (!isGranted(userId)) { + return false; } - mUserIds = ArrayUtils.removeInt(mUserIds, userId); + PermissionState userState = mUserStates.get(userId); + userState.mGranted = false; - if (mUserIds.length == 0) { - mUserIds = USERS_NONE; + if (userState.isDefault()) { + mUserStates.remove(userId); } return true; } + + public PermissionState getPermissionState(int userId) { + return mUserStates.get(userId); + } + + public int getFlags(int userId) { + PermissionState userState = mUserStates.get(userId); + if (userState != null) { + return userState.mFlags; + } + return 0; + } + + public boolean isDefault() { + return mUserStates.size() <= 0; + } + + public static boolean isInstallPermissionKey(int userId) { + return userId == UserHandle.USER_ALL; + } + + public boolean updateFlags(int userId, int flagMask, int flagValues) { + if (isInstallPermission()) { + userId = UserHandle.USER_ALL; + } + + if (!isCompatibleUserId(userId)) { + return false; + } + + final int newFlags = flagValues & flagMask; + + PermissionState userState = mUserStates.get(userId); + if (userState != null) { + final int oldFlags = userState.mFlags; + userState.mFlags = (userState.mFlags & ~flagMask) | newFlags; + if (userState.isDefault()) { + mUserStates.remove(userId); + } + return userState.mFlags != oldFlags; + } else if (newFlags != 0) { + userState = new PermissionState(mPerm.name); + userState.mFlags = newFlags; + mUserStates.put(userId, userState); + return true; + } + + return false; + } + + private boolean isCompatibleUserId(int userId) { + return isDefault() || !(isInstallPermission() ^ isInstallPermissionKey(userId)); + } + + private boolean isInstallPermission() { + return mUserStates.size() == 1 + && mUserStates.get(UserHandle.USER_ALL) != null; + } + } + + public static final class PermissionState { + private final String mName; + private boolean mGranted; + private int mFlags; + + public PermissionState(String name) { + mName = name; + } + + public PermissionState(PermissionState other) { + mName = other.mName; + mGranted = other.mGranted; + mFlags = other.mFlags; + } + + public boolean isDefault() { + return !mGranted && mFlags == 0; + } + + public String getName() { + return mName; + } + + public boolean isGranted() { + return mGranted; + } + + public int getFlags() { + return mFlags; + } } } diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java index 0c7f79d..c35258a 100644 --- a/services/core/java/com/android/server/pm/SettingBase.java +++ b/services/core/java/com/android/server/pm/SettingBase.java @@ -21,11 +21,13 @@ import android.content.pm.ApplicationInfo; import java.util.Arrays; abstract class SettingBase { + private static final int[] USERS_NONE = new int[0]; + int pkgFlags; int pkgPrivateFlags; protected final PermissionsState mPermissionsState; - private int[] mPermissionsUpdatedForUserIds = PermissionsState.USERS_NONE; + private int[] mPermissionsUpdatedForUserIds = USERS_NONE; SettingBase(int pkgFlags, int pkgPrivateFlags) { setFlags(pkgFlags); @@ -53,7 +55,7 @@ abstract class SettingBase { return; } - if (userIds == PermissionsState.USERS_NONE || userIds == PermissionsState.USERS_ALL) { + if (userIds == USERS_NONE) { mPermissionsUpdatedForUserIds = userIds; } else { mPermissionsUpdatedForUserIds = Arrays.copyOf(userIds, userIds.length); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index fd70ce1..2e9656a 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -60,6 +60,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.backup.PreferredActivityBackupHelper; import com.android.server.pm.PackageManagerService.DumpState; +import com.android.server.pm.PermissionsState.PermissionState; import java.io.FileNotFoundException; import java.util.Collection; @@ -178,6 +179,8 @@ final class Settings { private static final String ATTR_CODE = "code"; private static final String ATTR_NOT_LAUNCHED = "nl"; private static final String ATTR_ENABLED = "enabled"; + private static final String ATTR_GRANTED = "granted"; + private static final String ATTR_FLAGS = "flags"; private static final String ATTR_ENABLED_CALLER = "enabledCaller"; private static final String ATTR_STOPPED = "stopped"; // Legacy, here for reading older versions of the package-restrictions. @@ -820,14 +823,20 @@ final class Settings { } if (!used) { + PermissionsState permissionsState = sus.getPermissionsState(); + // Try to revoke as an install permission which is for all users. - if (sus.getPermissionsState().revokeInstallPermission(bp) == + // The package is gone - no need to keep flags for applying policy. + permissionsState.updatePermissionFlags(bp, userId, + PackageManager.MASK_PERMISSION_FLAGS, 0); + + if (permissionsState.revokeInstallPermission(bp) == PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) { return UserHandle.USER_ALL; } // Try to revoke as an install permission which is per user. - if (sus.getPermissionsState().revokeRuntimePermission(bp, userId) == + if (permissionsState.revokeRuntimePermission(bp, userId) == PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) { return userId; } @@ -1724,10 +1733,32 @@ final class Settings { continue; } - if (permissionsState.grantInstallPermission(bp) == - PermissionsState.PERMISSION_OPERATION_FAILURE) { - Slog.w(PackageManagerService.TAG, "Permission already added: " + name); - XmlUtils.skipCurrentTag(parser); + String grantedStr = parser.getAttributeValue(null, ATTR_GRANTED); + final boolean granted = grantedStr == null + || Boolean.parseBoolean(grantedStr); + + String flagsStr = parser.getAttributeValue(null, ATTR_FLAGS); + final int flags = (flagsStr != null) + ? Integer.parseInt(flagsStr, 16) : 0; + + if (granted) { + if (permissionsState.grantInstallPermission(bp) == + PermissionsState.PERMISSION_OPERATION_FAILURE) { + Slog.w(PackageManagerService.TAG, "Permission already added: " + name); + XmlUtils.skipCurrentTag(parser); + } else { + permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL, + PackageManager.MASK_PERMISSION_FLAGS, flags); + } + } else { + if (permissionsState.revokeInstallPermission(bp) == + PermissionsState.PERMISSION_OPERATION_FAILURE) { + Slog.w(PackageManagerService.TAG, "Permission already added: " + name); + XmlUtils.skipCurrentTag(parser); + } else { + permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL, + PackageManager.MASK_PERMISSION_FLAGS, flags); + } } } else { Slog.w(PackageManagerService.TAG, "Unknown element under <permissions>: " @@ -1737,17 +1768,19 @@ final class Settings { } } - void writePermissionsLPr(XmlSerializer serializer, Set<String> permissions) + void writePermissionsLPr(XmlSerializer serializer, List<PermissionState> permissionStates) throws IOException { - if (permissions.isEmpty()) { + if (permissionStates.isEmpty()) { return; } serializer.startTag(null, TAG_PERMISSIONS); - for (String permission : permissions) { + for (PermissionState permissionState : permissionStates) { serializer.startTag(null, TAG_ITEM); - serializer.attribute(null, ATTR_NAME, permission); + serializer.attribute(null, ATTR_NAME, permissionState.getName()); + serializer.attribute(null, ATTR_GRANTED, String.valueOf(permissionState.isGranted())); + serializer.attribute(null, ATTR_FLAGS, Integer.toHexString(permissionState.getFlags())); serializer.endTag(null, TAG_ITEM); } @@ -1945,7 +1978,8 @@ final class Settings { serializer.attribute(null, "userId", Integer.toString(usr.userId)); usr.signatures.writeXml(serializer, "sigs", mPastSignatures); - writePermissionsLPr(serializer, usr.getPermissionsState().getInstallPermissions()); + writePermissionsLPr(serializer, usr.getPermissionsState() + .getInstallPermissionStates()); serializer.endTag(null, "shared-user"); } @@ -2120,7 +2154,8 @@ final class Settings { // If this is a shared user, the permissions will be written there. if (pkg.sharedUser == null) { - writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissions()); + writePermissionsLPr(serializer, pkg.getPermissionsState() + .getInstallPermissionStates()); } serializer.endTag(null, "updated-package"); @@ -2175,9 +2210,9 @@ final class Settings { serializer.attribute(null, "volumeUuid", pkg.volumeUuid); } pkg.signatures.writeXml(serializer, "sigs", mPastSignatures); - if ((pkg.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) { - writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissions()); - } + + writePermissionsLPr(serializer, pkg.getPermissionsState() + .getInstallPermissionStates()); writeSigningKeySetLPr(serializer, pkg.keySetData); writeUpgradeKeySetsLPr(serializer, pkg.keySetData); @@ -3922,7 +3957,7 @@ final class Settings { PermissionsState permissionsState = ps.getPermissionsState(); dumpGidsLPr(pw, prefix + " ", permissionsState.computeGids(user.id)); dumpRuntimePermissionsLPr(pw, prefix + " ", permissionsState - .getRuntimePermissions(user.id)); + .getRuntimePermissionStates(user.id)); } ArraySet<String> cmp = ps.getDisabledComponents(user.id); @@ -4071,7 +4106,8 @@ final class Settings { for (int userId : UserManagerService.getInstance().getUserIds()) { final int[] gids = permissionsState.computeGids(userId); - Set<String> permissions = permissionsState.getRuntimePermissions(userId); + List<PermissionState> permissions = permissionsState + .getRuntimePermissionStates(userId); if (!ArrayUtils.isEmpty(gids) || !permissions.isEmpty()) { pw.print(prefix); pw.print("User "); pw.print(userId); pw.println(": "); dumpGidsLPr(pw, prefix + " ", gids); @@ -4120,22 +4156,29 @@ final class Settings { } } - void dumpRuntimePermissionsLPr(PrintWriter pw, String prefix, Set<String> permissions) { - if (!permissions.isEmpty()) { + void dumpRuntimePermissionsLPr(PrintWriter pw, String prefix, + List<PermissionState> permissionStates) { + if (!permissionStates.isEmpty()) { pw.print(prefix); pw.println("runtime permissions:"); - for (String permission : permissions) { - pw.print(prefix); pw.print(" "); pw.println(permission); + for (PermissionState permissionState : permissionStates) { + pw.print(prefix); pw.print(" "); pw.print(permissionState.getName()); + pw.print(", granted="); pw.print(permissionState.isGranted()); + pw.print(", flags=0x"); pw.println(Integer.toHexString( + permissionState.getFlags())); } } } void dumpInstallPermissionsLPr(PrintWriter pw, String prefix, PermissionsState permissionsState) { - Set<String> permissions = permissionsState.getInstallPermissions(); - if (!permissions.isEmpty()) { + List<PermissionState> permissionStates = permissionsState.getInstallPermissionStates(); + if (!permissionStates.isEmpty()) { pw.print(prefix); pw.println("install permissions:"); - for (String permission : permissions) { - pw.print(prefix); pw.print(" "); pw.println(permission); + for (PermissionState permissionState : permissionStates) { + pw.print(prefix); pw.print(" "); pw.print(permissionState.getName()); + pw.print(", granted="); pw.print(permissionState.isGranted()); + pw.print(", flags=0x"); pw.println(Integer.toHexString( + permissionState.getFlags())); } } } @@ -4207,8 +4250,8 @@ final class Settings { private void writePermissionsSync(int userId) { AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId)); - ArrayMap<String, Set<String>> permissionsForPackage = new ArrayMap<>(); - ArrayMap<String, Set<String>> permissionsForSharedUser = new ArrayMap<>(); + ArrayMap<String, List<PermissionState>> permissionsForPackage = new ArrayMap<>(); + ArrayMap<String, List<PermissionState>> permissionsForSharedUser = new ArrayMap<>(); synchronized (mLock) { mWriteScheduled.delete(userId); @@ -4219,9 +4262,10 @@ final class Settings { PackageSetting packageSetting = mPackages.valueAt(i); if (packageSetting.sharedUser == null) { PermissionsState permissionsState = packageSetting.getPermissionsState(); - Set<String> permissions = permissionsState.getRuntimePermissions(userId); - if (!permissions.isEmpty()) { - permissionsForPackage.put(packageName, permissions); + List<PermissionState> permissionsStates = permissionsState + .getRuntimePermissionStates(userId); + if (!permissionsStates.isEmpty()) { + permissionsForPackage.put(packageName, permissionsStates); } } } @@ -4231,9 +4275,10 @@ final class Settings { String sharedUserName = mSharedUsers.keyAt(i); SharedUserSetting sharedUser = mSharedUsers.valueAt(i); PermissionsState permissionsState = sharedUser.getPermissionsState(); - Set<String> permissions = permissionsState.getRuntimePermissions(userId); - if (!permissions.isEmpty()) { - permissionsForSharedUser.put(sharedUserName, permissions); + List<PermissionState> permissionsStates = permissionsState + .getRuntimePermissionStates(userId); + if (!permissionsStates.isEmpty()) { + permissionsForSharedUser.put(sharedUserName, permissionsStates); } } } @@ -4252,20 +4297,20 @@ final class Settings { final int packageCount = permissionsForPackage.size(); for (int i = 0; i < packageCount; i++) { String packageName = permissionsForPackage.keyAt(i); - Set<String> permissions = permissionsForPackage.valueAt(i); + List<PermissionState> permissionStates = permissionsForPackage.valueAt(i); serializer.startTag(null, TAG_PACKAGE); serializer.attribute(null, ATTR_NAME, packageName); - writePermissions(serializer, permissions); + writePermissions(serializer, permissionStates); serializer.endTag(null, TAG_PACKAGE); } final int sharedUserCount = permissionsForSharedUser.size(); for (int i = 0; i < sharedUserCount; i++) { String packageName = permissionsForSharedUser.keyAt(i); - Set<String> permissions = permissionsForSharedUser.valueAt(i); + List<PermissionState> permissionStates = permissionsForSharedUser.valueAt(i); serializer.startTag(null, TAG_SHARED_USER); serializer.attribute(null, ATTR_NAME, packageName); - writePermissions(serializer, permissions); + writePermissions(serializer, permissionStates); serializer.endTag(null, TAG_SHARED_USER); } @@ -4290,20 +4335,23 @@ final class Settings { mHandler.removeMessages(userId); for (SettingBase sb : mPackages.values()) { - revokeRuntimePermissions(sb, userId); + revokeRuntimePermissionsAndClearFlags(sb, userId); } for (SettingBase sb : mSharedUsers.values()) { - revokeRuntimePermissions(sb, userId); + revokeRuntimePermissionsAndClearFlags(sb, userId); } } - private void revokeRuntimePermissions(SettingBase sb, int userId) { + private void revokeRuntimePermissionsAndClearFlags(SettingBase sb, int userId) { PermissionsState permissionsState = sb.getPermissionsState(); - for (String permission : permissionsState.getRuntimePermissions(userId)) { - BasePermission bp = mPermissions.get(permission); + for (PermissionState permissionState + : permissionsState.getRuntimePermissionStates(userId)) { + BasePermission bp = mPermissions.get(permissionState.getName()); if (bp != null) { permissionsState.revokeRuntimePermission(bp, userId); + permissionsState.updatePermissionFlags(bp, userId, + PackageManager.MASK_PERMISSION_FLAGS, 0); } } } @@ -4391,20 +4439,47 @@ final class Settings { continue; } - if (permissionsState.grantRuntimePermission(bp, userId) == - PermissionsState.PERMISSION_OPERATION_FAILURE) { - Slog.w(PackageManagerService.TAG, "Duplicate permission:" + name); + String grantedStr = parser.getAttributeValue(null, ATTR_GRANTED); + final boolean granted = grantedStr == null + || Boolean.parseBoolean(grantedStr); + + String flagsStr = parser.getAttributeValue(null, ATTR_FLAGS); + final int flags = (flagsStr != null) + ? Integer.parseInt(flagsStr, 16) : 0; + + if (granted) { + if (permissionsState.grantRuntimePermission(bp, userId) == + PermissionsState.PERMISSION_OPERATION_FAILURE) { + Slog.w(PackageManagerService.TAG, "Duplicate permission:" + name); + } else { + permissionsState.updatePermissionFlags(bp, userId, + PackageManager.MASK_PERMISSION_FLAGS, flags); + + } + } else { + if (permissionsState.revokeRuntimePermission(bp, userId) == + PermissionsState.PERMISSION_OPERATION_FAILURE) { + Slog.w(PackageManagerService.TAG, "Duplicate permission:" + name); + } else { + permissionsState.updatePermissionFlags(bp, userId, + PackageManager.MASK_PERMISSION_FLAGS, flags); + } } + } break; } } } - private void writePermissions(XmlSerializer serializer, Set<String> permissions) - throws IOException { - for (String permission : permissions) { + private void writePermissions(XmlSerializer serializer, + List<PermissionState> permissionStates) throws IOException { + for (PermissionState permissionState : permissionStates) { serializer.startTag(null, TAG_ITEM); - serializer.attribute(null, ATTR_NAME, permission); + serializer.attribute(null, ATTR_NAME,permissionState.getName()); + serializer.attribute(null, ATTR_GRANTED, + String.valueOf(permissionState.isGranted())); + serializer.attribute(null, ATTR_FLAGS, + Integer.toHexString(permissionState.getFlags())); serializer.endTag(null, TAG_ITEM); } } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index f790f75..decca16 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -69,6 +69,7 @@ import android.view.WindowManagerPolicy; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import libcore.util.Objects; @@ -423,6 +424,9 @@ public final class PowerManagerService extends SystemService // True if we are currently in device idle mode. private boolean mDeviceIdleMode; + // Set of app ids that we will always respect the wake locks for. + int[] mDeviceIdleWhitelist = new int[0]; + // True if theater mode is enabled private boolean mTheaterModeEnabled; @@ -758,6 +762,7 @@ public final class PowerManagerService extends SystemService throw new IllegalArgumentException("Wake lock is already dead."); } mWakeLocks.add(wakeLock); + setWakeLockDisabledStateLocked(wakeLock); notifyAcquire = true; } @@ -894,7 +899,7 @@ public final class PowerManagerService extends SystemService } private void notifyWakeLockAcquiredLocked(WakeLock wakeLock) { - if (mSystemReady) { + if (mSystemReady && !wakeLock.mDisabled) { wakeLock.mNotifiedAcquired = true; mNotifier.onWakeLockAcquired(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource, @@ -1388,7 +1393,10 @@ public final class PowerManagerService extends SystemService final WakeLock wakeLock = mWakeLocks.get(i); switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { case PowerManager.PARTIAL_WAKE_LOCK: - mWakeLockSummary |= WAKE_LOCK_CPU; + if (!wakeLock.mDisabled) { + // We only respect this if the wake lock is not disabled. + mWakeLockSummary |= WAKE_LOCK_CPU; + } break; case PowerManager.FULL_WAKE_LOCK: mWakeLockSummary |= WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_BUTTON_BRIGHT; @@ -2248,12 +2256,12 @@ public final class PowerManagerService extends SystemService } } - private void setStayOnSettingInternal(int val) { + void setStayOnSettingInternal(int val) { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.STAY_ON_WHILE_PLUGGED_IN, val); } - private void setMaximumScreenOffTimeoutFromDeviceAdminInternal(int timeMs) { + void setMaximumScreenOffTimeoutFromDeviceAdminInternal(int timeMs) { synchronized (mLock) { mMaximumScreenOffTimeoutFromDeviceAdmin = timeMs; mDirty |= DIRTY_SETTINGS; @@ -2261,6 +2269,69 @@ public final class PowerManagerService extends SystemService } } + void setDeviceIdleModeInternal(boolean enabled) { + synchronized (mLock) { + if (mDeviceIdleMode != enabled) { + mDeviceIdleMode = enabled; + updateWakeLockDisabledStatesLocked(); + } + } + } + + void setDeviceIdleWhitelistInternal(int[] appids) { + synchronized (mLock) { + mDeviceIdleWhitelist = appids; + if (mDeviceIdleMode) { + updateWakeLockDisabledStatesLocked(); + } + } + } + + private void updateWakeLockDisabledStatesLocked() { + boolean changed = false; + final int numWakeLocks = mWakeLocks.size(); + for (int i = 0; i < numWakeLocks; i++) { + final WakeLock wakeLock = mWakeLocks.get(i); + if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) + == PowerManager.PARTIAL_WAKE_LOCK) { + if (setWakeLockDisabledStateLocked(wakeLock)) { + changed = true; + if (wakeLock.mDisabled) { + // This wake lock is no longer being respected. + notifyWakeLockReleasedLocked(wakeLock); + } else { + notifyWakeLockAcquiredLocked(wakeLock); + } + } + } + } + if (changed) { + mDirty |= DIRTY_WAKE_LOCKS; + updatePowerStateLocked(); + } + } + + private boolean setWakeLockDisabledStateLocked(WakeLock wakeLock) { + if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) + == PowerManager.PARTIAL_WAKE_LOCK) { + boolean disabled = false; + if (mDeviceIdleMode) { + final int appid = UserHandle.getAppId(wakeLock.mOwnerUid); + // If we are in idle mode, we will ignore all partial wake locks that are + // for application uids that are not whitelisted. + if (appid >= Process.FIRST_APPLICATION_UID && + Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0) { + disabled = true; + } + } + if (wakeLock.mDisabled != disabled) { + wakeLock.mDisabled = disabled; + return true; + } + } + return false; + } + private boolean isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() { return mMaximumScreenOffTimeoutFromDeviceAdmin >= 0 && mMaximumScreenOffTimeoutFromDeviceAdmin < Integer.MAX_VALUE; @@ -2459,6 +2530,8 @@ public final class PowerManagerService extends SystemService pw.println(" mSandmanSummoned=" + mSandmanSummoned); pw.println(" mLowPowerModeEnabled=" + mLowPowerModeEnabled); pw.println(" mBatteryLevelLow=" + mBatteryLevelLow); + pw.println(" mDeviceIdleMode=" + mDeviceIdleMode); + pw.println(" mDeviceIdleWhitelist=" + Arrays.toString(mDeviceIdleWhitelist)); pw.println(" mLastWakeTime=" + TimeUtils.formatUptime(mLastWakeTime)); pw.println(" mLastSleepTime=" + TimeUtils.formatUptime(mLastSleepTime)); pw.println(" mLastUserActivityTime=" + TimeUtils.formatUptime(mLastUserActivityTime)); @@ -2671,6 +2744,7 @@ public final class PowerManagerService extends SystemService public final int mOwnerUid; public final int mOwnerPid; public boolean mNotifiedAcquired; + public boolean mDisabled; public WakeLock(IBinder lock, int flags, String tag, String packageName, WorkSource workSource, String historyTag, int ownerUid, int ownerPid) { @@ -2729,7 +2803,7 @@ public final class PowerManagerService extends SystemService @Override public String toString() { return getLockLevelString() - + " '" + mTag + "'" + getLockFlagsString() + + " '" + mTag + "'" + getLockFlagsString() + (mDisabled ? " DISABLED" : "") + " (uid=" + mOwnerUid + ", pid=" + mOwnerPid + ", ws=" + mWorkSource + ")"; } @@ -3340,9 +3414,12 @@ public final class PowerManagerService extends SystemService @Override public void setDeviceIdleMode(boolean enabled) { - synchronized (mLock) { - mDeviceIdleMode = enabled; - } + setDeviceIdleModeInternal(enabled); + } + + @Override + public void setDeviceIdleWhitelist(int[] appids) { + setDeviceIdleWhitelistInternal(appids); } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 67c198f..c6816b0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -6344,10 +6344,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); long ident = Binder.clearCallingIdentity(); try { + PackageManager packageManager = mContext.getPackageManager(); if (granted) { - mContext.getPackageManager().grantPermission(packageName, permission, user); + packageManager.grantRuntimePermission(packageName, permission, user); + packageManager.updatePermissionFlags(permission, packageName, + PackageManager.FLAG_PERMISSION_POLICY_FIXED, + PackageManager.FLAG_PERMISSION_POLICY_FIXED, user); } else { - mContext.getPackageManager().revokePermission(packageName, permission, user); + packageManager.revokeRuntimePermission(packageName, + permission, user); + packageManager.updatePermissionFlags(permission, packageName, + PackageManager.FLAG_PERMISSION_POLICY_FIXED, + PackageManager.FLAG_PERMISSION_POLICY_FIXED, user); } return true; } catch (SecurityException se) { diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index 9efea0d..bf1ea4d 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -44,14 +44,12 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.VerificationParams; import android.content.pm.VerifierDeviceIdentity; -import android.content.pm.PackageManager.MoveCallback; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Handler; -import android.os.ResultReceiver; import android.os.UserHandle; import android.os.storage.VolumeInfo; @@ -195,13 +193,28 @@ public class MockPackageManager extends PackageManager { /** @hide */ @Override - public void grantPermission(String packageName, String permissionName, UserHandle user) { + public void grantRuntimePermission(String packageName, String permissionName, + UserHandle user) { + throw new UnsupportedOperationException(); + } + + /** @hide */ + @Override + public void revokeRuntimePermission(String packageName, String permissionName, + UserHandle user) { + throw new UnsupportedOperationException(); + } + + /** @hide */ + @Override + public int getPermissionFlags(String permissionName, String packageName, UserHandle user) { throw new UnsupportedOperationException(); } /** @hide */ @Override - public void revokePermission(String packageName, String permissionName, UserHandle user) { + public void updatePermissionFlags(String permissionName, String packageName, + int flagMask, int flagValues, UserHandle user) { throw new UnsupportedOperationException(); } |
