diff options
157 files changed, 8158 insertions, 884 deletions
diff --git a/Android.mk b/Android.mk index e39ff3bbb3c0..08f34688fb51 100644 --- a/Android.mk +++ b/Android.mk @@ -149,6 +149,7 @@ LOCAL_SRC_FILES += \ core/java/android/content/pm/IPackageMoveObserver.aidl \ core/java/android/content/pm/IPackageStatsObserver.aidl \ core/java/android/content/pm/IOnPermissionsChangeListener.aidl \ + core/java/android/content/pm/IShortcutService.aidl \ core/java/android/database/IContentObserver.aidl \ ../av/camera/aidl/android/hardware/ICameraService.aidl \ ../av/camera/aidl/android/hardware/ICameraServiceListener.aidl \ @@ -241,6 +242,7 @@ LOCAL_SRC_FILES += \ core/java/android/service/notification/IStatusBarNotificationHolder.aidl \ core/java/android/service/notification/IConditionListener.aidl \ core/java/android/service/notification/IConditionProvider.aidl \ + core/java/android/service/vr/IVrListener.aidl \ core/java/android/print/ILayoutResultCallback.aidl \ core/java/android/print/IPrinterDiscoveryObserver.aidl \ core/java/android/print/IPrintDocumentAdapter.aidl \ @@ -651,6 +653,7 @@ aidl_files := \ frameworks/base/core/java/android/content/pm/ProviderInfo.aidl \ frameworks/base/core/java/android/content/pm/PackageStats.aidl \ frameworks/base/core/java/android/content/pm/PermissionGroupInfo.aidl \ + frameworks/base/core/java/android/content/pm/ShortcutInfo.aidl \ frameworks/base/core/java/android/content/pm/LabeledIntent.aidl \ frameworks/base/core/java/android/content/ComponentName.aidl \ frameworks/base/core/java/android/content/SyncStats.aidl \ diff --git a/api/current.txt b/api/current.txt index 6bda86b80f04..29fb03ecb3a0 100644 --- a/api/current.txt +++ b/api/current.txt @@ -38,6 +38,7 @@ package android { field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT"; field public static final java.lang.String BIND_VOICE_INTERACTION = "android.permission.BIND_VOICE_INTERACTION"; field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE"; + field public static final java.lang.String BIND_VR_LISTENER_SERVICE = "android.permission.BIND_VR_LISTENER_SERVICE"; field public static final java.lang.String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER"; field public static final java.lang.String BLUETOOTH = "android.permission.BLUETOOTH"; field public static final java.lang.String BLUETOOTH_ADMIN = "android.permission.BLUETOOTH_ADMIN"; @@ -114,6 +115,7 @@ package android { field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES"; field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE"; field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS"; + field public static final java.lang.String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION"; field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM"; field public static final java.lang.String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH"; field public static final java.lang.String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE"; @@ -3604,7 +3606,7 @@ package android.app { method public deprecated void setTitleColor(int); method public void setVisible(boolean); method public final void setVolumeControlStream(int); - method public void setVrMode(boolean); + method public void setVrModeEnabled(boolean, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean shouldShowRequestPermissionRationale(java.lang.String); method public boolean shouldUpRecreateTask(android.content.Intent); method public boolean showAssist(android.os.Bundle); @@ -8138,6 +8140,7 @@ package android.content { field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions"; field public static final java.lang.String SEARCH_SERVICE = "search"; field public static final java.lang.String SENSOR_SERVICE = "sensor"; + field public static final java.lang.String SHORTCUT_SERVICE = "shortcut"; field public static final java.lang.String STORAGE_SERVICE = "storage"; field public static final java.lang.String TELECOM_SERVICE = "telecom"; field public static final java.lang.String TELEPHONY_SERVICE = "phone"; @@ -9443,13 +9446,19 @@ package android.content.pm { public class LauncherApps { method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle); method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle); + method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo, android.os.UserHandle); + method public int getShortcutIconResId(android.content.pm.ShortcutInfo, android.os.UserHandle); + method public java.util.List<android.content.pm.ShortcutInfo> getShortcutInfo(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle); + method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle); method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle); method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle); + method public void pinShortcuts(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle); method public void registerCallback(android.content.pm.LauncherApps.Callback); method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler); method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle); method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); + method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle, android.os.UserHandle); method public void unregisterCallback(android.content.pm.LauncherApps.Callback); } @@ -9462,6 +9471,18 @@ package android.content.pm { method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle); method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean); method public void onPackagesUnsuspended(java.lang.String[], android.os.UserHandle); + method public void onShortcutsChanged(java.lang.String, java.util.List<android.content.pm.ShortcutInfo>, android.os.UserHandle); + } + + public static class LauncherApps.ShortcutQuery { + ctor public LauncherApps.ShortcutQuery(); + method public void setActivity(android.content.ComponentName); + method public void setChangedSince(long); + method public void setPackage(java.lang.String); + method public void setQueryFlags(int); + field public static final int FLAG_GET_DYNAMIC = 1; // 0x1 + field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4 + field public static final int FLAG_GET_PINNED = 2; // 0x2 } public class PackageInfo implements android.os.Parcelable { @@ -9961,6 +9982,56 @@ package android.content.pm { field public java.lang.String permission; } + public class ShortcutInfo implements android.os.Parcelable { + method public int describeContents(); + method public android.content.ComponentName getActivityComponent(); + method public android.os.PersistableBundle getExtras(); + method public java.lang.String getId(); + method public android.content.Intent getIntent(); + method public long getLastChangedTimestamp(); + method public java.lang.String getPackageName(); + method public java.lang.String getTitle(); + method public int getWeight(); + method public boolean hasIconFile(); + method public boolean hasIconResource(); + method public boolean isDynamic(); + method public boolean isPinned(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1 + field public static final int CLONE_REMOVE_FOR_LAUNCHER = 3; // 0x3 + field public static final int CLONE_REMOVE_NON_KEY_INFO = 4; // 0x4 + field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR; + field public static final int FLAG_DYNAMIC = 1; // 0x1 + field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8 + field public static final int FLAG_HAS_ICON_RES = 4; // 0x4 + field public static final int FLAG_PINNED = 2; // 0x2 + } + + public static class ShortcutInfo.Builder { + ctor public ShortcutInfo.Builder(android.content.Context); + method public android.content.pm.ShortcutInfo build(); + method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName); + method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle); + method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); + method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); + method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setWeight(int); + } + + public class ShortcutManager { + method public boolean addDynamicShortcut(android.content.pm.ShortcutInfo); + method public void deleteAllDynamicShortcuts(); + method public void deleteDynamicShortcut(java.lang.String); + method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts(); + method public int getMaxDynamicShortcutCount(); + method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts(); + method public long getRateLimitResetTime(); + method public int getRemainingCallCount(); + method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>); + method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>); + } + public class Signature implements android.os.Parcelable { ctor public Signature(byte[]); ctor public Signature(java.lang.String); @@ -19720,7 +19791,7 @@ package android.media { method public void adjustVolume(int, int); method public void dispatchMediaKeyEvent(android.view.KeyEvent); method public int generateAudioSessionId(); - method public android.media.AudioRecordConfiguration[] getActiveRecordConfigurations(); + method public android.media.AudioRecordingConfiguration[] getActiveRecordingConfigurations(); method public android.media.AudioDeviceInfo[] getDevices(int); method public int getMode(); method public java.lang.String getParameters(java.lang.String); @@ -19866,7 +19937,7 @@ package android.media { public static abstract class AudioManager.AudioRecordingCallback { ctor public AudioManager.AudioRecordingCallback(); - method public void onRecordConfigChanged(android.media.AudioRecordConfiguration[]); + method public void onRecordConfigChanged(android.media.AudioRecordingConfiguration[]); } public static abstract interface AudioManager.OnAudioFocusChangeListener { @@ -19940,7 +20011,7 @@ package android.media { method public abstract void onRoutingChanged(android.media.AudioRecord); } - public final class AudioRecordConfiguration implements android.os.Parcelable { + public final class AudioRecordingConfiguration implements android.os.Parcelable { method public int describeContents(); method public android.media.AudioDeviceInfo getAudioDevice(); method public int getClientAudioSessionId(); @@ -19948,7 +20019,7 @@ package android.media { method public android.media.AudioFormat getClientFormat(); method public android.media.AudioFormat getFormat(); method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.media.AudioRecordConfiguration> CREATOR; + field public static final android.os.Parcelable.Creator<android.media.AudioRecordingConfiguration> CREATOR; } public abstract interface AudioRouting { @@ -29005,6 +29076,7 @@ package android.os { ctor public PersistableBundle(); ctor public PersistableBundle(int); ctor public PersistableBundle(android.os.PersistableBundle); + ctor public PersistableBundle(android.os.Bundle); method public java.lang.Object clone(); method public int describeContents(); method public android.os.PersistableBundle getPersistableBundle(java.lang.String); @@ -32171,6 +32243,7 @@ package android.provider { field public static final java.lang.String ACTION_VOICE_CONTROL_BATTERY_SAVER_MODE = "android.settings.VOICE_CONTROL_BATTERY_SAVER_MODE"; field public static final java.lang.String ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE = "android.settings.VOICE_CONTROL_DO_NOT_DISTURB_MODE"; field public static final java.lang.String ACTION_VOICE_INPUT_SETTINGS = "android.settings.VOICE_INPUT_SETTINGS"; + field public static final java.lang.String ACTION_VR_LISTENER_SETTINGS = "android.settings.VR_LISTENER_SETTINGS"; field public static final java.lang.String ACTION_WIFI_IP_SETTINGS = "android.settings.WIFI_IP_SETTINGS"; field public static final java.lang.String ACTION_WIFI_SETTINGS = "android.settings.WIFI_SETTINGS"; field public static final java.lang.String ACTION_WIRELESS_SETTINGS = "android.settings.WIRELESS_SETTINGS"; @@ -32208,6 +32281,7 @@ package android.provider { field public static final java.lang.String AUTO_TIME = "auto_time"; field public static final java.lang.String AUTO_TIME_ZONE = "auto_time_zone"; field public static final java.lang.String BLUETOOTH_ON = "bluetooth_on"; + field public static final java.lang.String BOOT_COUNT = "boot_count"; field public static final java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync"; field public static final android.net.Uri CONTENT_URI; field public static final java.lang.String DATA_ROAMING = "data_roaming"; @@ -34422,8 +34496,9 @@ package android.service.media { package android.service.notification { public class Condition implements android.os.Parcelable { - ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int); + ctor public Condition(android.net.Uri, java.lang.String, int); ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int, int, int); + ctor public Condition(android.os.Parcel); method public android.service.notification.Condition copy(); method public int describeContents(); method public static boolean isValidId(android.net.Uri, java.lang.String); @@ -34454,6 +34529,7 @@ package android.service.notification { method public final void notifyConditions(android.service.notification.Condition...); method public android.os.IBinder onBind(android.content.Intent); method public abstract void onConnected(); + method public void onRequestConditions(int); method public abstract void onSubscribe(android.net.Uri); method public abstract void onUnsubscribe(android.net.Uri); field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId"; @@ -34488,7 +34564,6 @@ package android.service.notification { method public static void requestRebind(android.content.ComponentName) throws android.os.RemoteException; method public final void requestUnbind() throws android.os.RemoteException; method public final void setNotificationsShown(java.lang.String[]); - field public static final java.lang.String CATEGORY_VR_NOTIFICATIONS = "android.intent.category.vr.notifications"; field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4 field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1 @@ -34783,6 +34858,17 @@ package android.service.voice { } +package android.service.vr { + + public abstract class VrListenerService extends android.app.Service { + ctor public VrListenerService(); + method public static final boolean isVrModePackageEnabled(android.content.Context, android.content.ComponentName); + method public android.os.IBinder onBind(android.content.Intent); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.vr.VrListenerService"; + } + +} + package android.service.wallpaper { public abstract class WallpaperService extends android.app.Service { @@ -57390,7 +57476,23 @@ package java.util { public abstract interface Comparator { method public abstract int compare(T, T); + method public static java.util.Comparator<T> comparing(java.util.function.Function<? super T, ? extends U>, java.util.Comparator<? super U>); + method public static java.util.Comparator<T> comparing(java.util.function.Function<? super T, ? extends U>); + method public static java.util.Comparator<T> comparingDouble(java.util.function.ToDoubleFunction<? super T>); + method public static java.util.Comparator<T> comparingInt(java.util.function.ToIntFunction<? super T>); + method public static java.util.Comparator<T> comparingLong(java.util.function.ToLongFunction<? super T>); method public abstract boolean equals(java.lang.Object); + method public static java.util.Comparator<T> naturalOrder(); + method public static java.util.Comparator<T> nullsFirst(java.util.Comparator<? super T>); + method public static java.util.Comparator<T> nullsLast(java.util.Comparator<? super T>); + method public static java.util.Comparator<T> reverseOrder(); + method public default java.util.Comparator<T> reversed(); + method public default java.util.Comparator<T> thenComparing(java.util.Comparator<? super T>); + method public default java.util.Comparator<T> thenComparing(java.util.function.Function<? super T, ? extends U>, java.util.Comparator<? super U>); + method public default java.util.Comparator<T> thenComparing(java.util.function.Function<? super T, ? extends U>); + method public default java.util.Comparator<T> thenComparingDouble(java.util.function.ToDoubleFunction<? super T>); + method public default java.util.Comparator<T> thenComparingInt(java.util.function.ToIntFunction<? super T>); + method public default java.util.Comparator<T> thenComparingLong(java.util.function.ToLongFunction<? super T>); } public class ConcurrentModificationException extends java.lang.RuntimeException { diff --git a/api/removed.txt b/api/removed.txt index 115224ce49a6..ba9751d68a3d 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -201,14 +201,6 @@ package android.provider { } -package android.service.notification { - - public abstract class ConditionProviderService extends android.app.Service { - method public void onRequestConditions(int); - } - -} - package android.test.mock { public deprecated class MockPackageManager extends android.content.pm.PackageManager { diff --git a/api/system-current.txt b/api/system-current.txt index a73d734d9017..f92ddb9d5d3d 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -52,6 +52,7 @@ package android { field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT"; field public static final java.lang.String BIND_VOICE_INTERACTION = "android.permission.BIND_VOICE_INTERACTION"; field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE"; + field public static final java.lang.String BIND_VR_LISTENER_SERVICE = "android.permission.BIND_VR_LISTENER_SERVICE"; field public static final java.lang.String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER"; field public static final java.lang.String BLUETOOTH = "android.permission.BLUETOOTH"; field public static final java.lang.String BLUETOOTH_ADMIN = "android.permission.BLUETOOTH_ADMIN"; @@ -193,6 +194,7 @@ package android { field public static final java.lang.String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS"; field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE"; field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS"; + field public static final java.lang.String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION"; field public static final java.lang.String SERIAL_PORT = "android.permission.SERIAL_PORT"; field public static final java.lang.String SET_ACTIVITY_WATCHER = "android.permission.SET_ACTIVITY_WATCHER"; field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM"; @@ -3721,7 +3723,7 @@ package android.app { method public deprecated void setTitleColor(int); method public void setVisible(boolean); method public final void setVolumeControlStream(int); - method public void setVrMode(boolean); + method public void setVrModeEnabled(boolean, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean shouldShowRequestPermissionRationale(java.lang.String); method public boolean shouldUpRecreateTask(android.content.Intent); method public boolean showAssist(android.os.Bundle); @@ -8444,6 +8446,7 @@ package android.content { field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions"; field public static final java.lang.String SEARCH_SERVICE = "search"; field public static final java.lang.String SENSOR_SERVICE = "sensor"; + field public static final java.lang.String SHORTCUT_SERVICE = "shortcut"; field public static final java.lang.String STORAGE_SERVICE = "storage"; field public static final java.lang.String TELECOM_SERVICE = "telecom"; field public static final java.lang.String TELEPHONY_SERVICE = "phone"; @@ -9777,13 +9780,19 @@ package android.content.pm { public class LauncherApps { method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle); method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle); + method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo, android.os.UserHandle); + method public int getShortcutIconResId(android.content.pm.ShortcutInfo, android.os.UserHandle); + method public java.util.List<android.content.pm.ShortcutInfo> getShortcutInfo(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle); + method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle); method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle); method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle); + method public void pinShortcuts(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle); method public void registerCallback(android.content.pm.LauncherApps.Callback); method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler); method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle); method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); + method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle, android.os.UserHandle); method public void unregisterCallback(android.content.pm.LauncherApps.Callback); } @@ -9796,6 +9805,18 @@ package android.content.pm { method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle); method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean); method public void onPackagesUnsuspended(java.lang.String[], android.os.UserHandle); + method public void onShortcutsChanged(java.lang.String, java.util.List<android.content.pm.ShortcutInfo>, android.os.UserHandle); + } + + public static class LauncherApps.ShortcutQuery { + ctor public LauncherApps.ShortcutQuery(); + method public void setActivity(android.content.ComponentName); + method public void setChangedSince(long); + method public void setPackage(java.lang.String); + method public void setQueryFlags(int); + field public static final int FLAG_GET_DYNAMIC = 1; // 0x1 + field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4 + field public static final int FLAG_GET_PINNED = 2; // 0x2 } public class PackageInfo implements android.os.Parcelable { @@ -10355,6 +10376,56 @@ package android.content.pm { field public java.lang.String permission; } + public class ShortcutInfo implements android.os.Parcelable { + method public int describeContents(); + method public android.content.ComponentName getActivityComponent(); + method public android.os.PersistableBundle getExtras(); + method public java.lang.String getId(); + method public android.content.Intent getIntent(); + method public long getLastChangedTimestamp(); + method public java.lang.String getPackageName(); + method public java.lang.String getTitle(); + method public int getWeight(); + method public boolean hasIconFile(); + method public boolean hasIconResource(); + method public boolean isDynamic(); + method public boolean isPinned(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1 + field public static final int CLONE_REMOVE_FOR_LAUNCHER = 3; // 0x3 + field public static final int CLONE_REMOVE_NON_KEY_INFO = 4; // 0x4 + field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR; + field public static final int FLAG_DYNAMIC = 1; // 0x1 + field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8 + field public static final int FLAG_HAS_ICON_RES = 4; // 0x4 + field public static final int FLAG_PINNED = 2; // 0x2 + } + + public static class ShortcutInfo.Builder { + ctor public ShortcutInfo.Builder(android.content.Context); + method public android.content.pm.ShortcutInfo build(); + method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName); + method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle); + method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); + method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); + method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setWeight(int); + } + + public class ShortcutManager { + method public boolean addDynamicShortcut(android.content.pm.ShortcutInfo); + method public void deleteAllDynamicShortcuts(); + method public void deleteDynamicShortcut(java.lang.String); + method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts(); + method public int getMaxDynamicShortcutCount(); + method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts(); + method public long getRateLimitResetTime(); + method public int getRemainingCallCount(); + method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>); + method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>); + } + public class Signature implements android.os.Parcelable { ctor public Signature(byte[]); ctor public Signature(java.lang.String); @@ -21201,7 +21272,7 @@ package android.media { method public void adjustVolume(int, int); method public void dispatchMediaKeyEvent(android.view.KeyEvent); method public int generateAudioSessionId(); - method public android.media.AudioRecordConfiguration[] getActiveRecordConfigurations(); + method public android.media.AudioRecordingConfiguration[] getActiveRecordingConfigurations(); method public android.media.AudioDeviceInfo[] getDevices(int); method public int getMode(); method public java.lang.String getParameters(java.lang.String); @@ -21355,7 +21426,7 @@ package android.media { public static abstract class AudioManager.AudioRecordingCallback { ctor public AudioManager.AudioRecordingCallback(); - method public void onRecordConfigChanged(android.media.AudioRecordConfiguration[]); + method public void onRecordConfigChanged(android.media.AudioRecordingConfiguration[]); } public static abstract interface AudioManager.OnAudioFocusChangeListener { @@ -21432,7 +21503,7 @@ package android.media { method public abstract void onRoutingChanged(android.media.AudioRecord); } - public final class AudioRecordConfiguration implements android.os.Parcelable { + public final class AudioRecordingConfiguration implements android.os.Parcelable { method public int describeContents(); method public android.media.AudioDeviceInfo getAudioDevice(); method public int getClientAudioSessionId(); @@ -21440,7 +21511,7 @@ package android.media { method public android.media.AudioFormat getClientFormat(); method public android.media.AudioFormat getFormat(); method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.media.AudioRecordConfiguration> CREATOR; + field public static final android.os.Parcelable.Creator<android.media.AudioRecordingConfiguration> CREATOR; } public abstract interface AudioRouting { @@ -31290,6 +31361,7 @@ package android.os { ctor public PersistableBundle(); ctor public PersistableBundle(int); ctor public PersistableBundle(android.os.PersistableBundle); + ctor public PersistableBundle(android.os.Bundle); method public java.lang.Object clone(); method public int describeContents(); method public android.os.PersistableBundle getPersistableBundle(java.lang.String); @@ -34658,6 +34730,7 @@ package android.provider { field public static final java.lang.String ACTION_VOICE_CONTROL_BATTERY_SAVER_MODE = "android.settings.VOICE_CONTROL_BATTERY_SAVER_MODE"; field public static final java.lang.String ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE = "android.settings.VOICE_CONTROL_DO_NOT_DISTURB_MODE"; field public static final java.lang.String ACTION_VOICE_INPUT_SETTINGS = "android.settings.VOICE_INPUT_SETTINGS"; + field public static final java.lang.String ACTION_VR_LISTENER_SETTINGS = "android.settings.VR_LISTENER_SETTINGS"; field public static final java.lang.String ACTION_WIFI_IP_SETTINGS = "android.settings.WIFI_IP_SETTINGS"; field public static final java.lang.String ACTION_WIFI_SETTINGS = "android.settings.WIFI_SETTINGS"; field public static final java.lang.String ACTION_WIRELESS_SETTINGS = "android.settings.WIRELESS_SETTINGS"; @@ -34695,6 +34768,7 @@ package android.provider { field public static final java.lang.String AUTO_TIME = "auto_time"; field public static final java.lang.String AUTO_TIME_ZONE = "auto_time_zone"; field public static final java.lang.String BLUETOOTH_ON = "bluetooth_on"; + field public static final java.lang.String BOOT_COUNT = "boot_count"; field public static final java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync"; field public static final android.net.Uri CONTENT_URI; field public static final java.lang.String DATA_ROAMING = "data_roaming"; @@ -36912,8 +36986,9 @@ package android.service.media { package android.service.notification { public class Condition implements android.os.Parcelable { - ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int); + ctor public Condition(android.net.Uri, java.lang.String, int); ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int, int, int); + ctor public Condition(android.os.Parcel); method public android.service.notification.Condition copy(); method public int describeContents(); method public static boolean isValidId(android.net.Uri, java.lang.String); @@ -36954,37 +37029,6 @@ package android.service.notification { field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService"; } - public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService { - ctor public NotificationAssistantService(); - method public final void adjustImportance(java.lang.String, android.service.notification.NotificationAssistantService.Adjustment); - method public final android.os.IBinder onBind(android.content.Intent); - method public void onNotificationActionClick(java.lang.String, long, int); - method public void onNotificationClick(java.lang.String, long); - method public abstract android.service.notification.NotificationAssistantService.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean); - method public void onNotificationRemoved(java.lang.String, long, int); - method public void onNotificationVisibilityChanged(java.lang.String, long, boolean); - field public static final int REASON_APP_CANCEL = 8; // 0x8 - field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9 - field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2 - field public static final int REASON_DELEGATE_CANCEL_ALL = 3; // 0x3 - field public static final int REASON_DELEGATE_CLICK = 1; // 0x1 - field public static final int REASON_DELEGATE_ERROR = 4; // 0x4 - field public static final int REASON_GROUP_OPTIMIZATION = 13; // 0xd - field public static final int REASON_GROUP_SUMMARY_CANCELED = 12; // 0xc - field public static final int REASON_LISTENER_CANCEL = 10; // 0xa - field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb - field public static final int REASON_PACKAGE_BANNED = 7; // 0x7 - field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5 - field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe - field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf - field public static final int REASON_USER_STOPPED = 6; // 0x6 - field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; - } - - public class NotificationAssistantService.Adjustment { - ctor public NotificationAssistantService.Adjustment(int, java.lang.CharSequence, android.net.Uri); - } - public abstract class NotificationListenerService extends android.app.Service { ctor public NotificationListenerService(); method public final void cancelAllNotifications(); @@ -37015,7 +37059,6 @@ package android.service.notification { method public final void setNotificationsShown(java.lang.String[]); method public final void setOnNotificationPostedTrim(int); method public void unregisterAsSystemService() throws android.os.RemoteException; - field public static final java.lang.String CATEGORY_VR_NOTIFICATIONS = "android.intent.category.vr.notifications"; field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4 field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1 @@ -37055,6 +37098,37 @@ package android.service.notification { field public static final android.os.Parcelable.Creator<android.service.notification.NotificationListenerService.RankingMap> CREATOR; } + public abstract class NotificationRankerService extends android.service.notification.NotificationListenerService { + ctor public NotificationRankerService(); + method public final void adjustImportance(java.lang.String, android.service.notification.NotificationRankerService.Adjustment); + method public final android.os.IBinder onBind(android.content.Intent); + method public void onNotificationActionClick(java.lang.String, long, int); + method public void onNotificationClick(java.lang.String, long); + method public abstract android.service.notification.NotificationRankerService.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean); + method public void onNotificationRemoved(java.lang.String, long, int); + method public void onNotificationVisibilityChanged(java.lang.String, long, boolean); + field public static final int REASON_APP_CANCEL = 8; // 0x8 + field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9 + field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2 + field public static final int REASON_DELEGATE_CANCEL_ALL = 3; // 0x3 + field public static final int REASON_DELEGATE_CLICK = 1; // 0x1 + field public static final int REASON_DELEGATE_ERROR = 4; // 0x4 + field public static final int REASON_GROUP_OPTIMIZATION = 13; // 0xd + field public static final int REASON_GROUP_SUMMARY_CANCELED = 12; // 0xc + field public static final int REASON_LISTENER_CANCEL = 10; // 0xa + field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb + field public static final int REASON_PACKAGE_BANNED = 7; // 0x7 + field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5 + field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe + field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf + field public static final int REASON_USER_STOPPED = 6; // 0x6 + field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationRankerService"; + } + + public class NotificationRankerService.Adjustment { + ctor public NotificationRankerService.Adjustment(int, java.lang.CharSequence, android.net.Uri); + } + public class StatusBarNotification implements android.os.Parcelable { ctor public StatusBarNotification(java.lang.String, java.lang.String, int, java.lang.String, int, int, int, android.app.Notification, android.os.UserHandle, long); ctor public StatusBarNotification(android.os.Parcel); @@ -37368,6 +37442,17 @@ package android.service.voice { } +package android.service.vr { + + public abstract class VrListenerService extends android.app.Service { + ctor public VrListenerService(); + method public static final boolean isVrModePackageEnabled(android.content.Context, android.content.ComponentName); + method public android.os.IBinder onBind(android.content.Intent); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.vr.VrListenerService"; + } + +} + package android.service.wallpaper { public abstract class WallpaperService extends android.app.Service { @@ -60494,7 +60579,23 @@ package java.util { public abstract interface Comparator { method public abstract int compare(T, T); + method public static java.util.Comparator<T> comparing(java.util.function.Function<? super T, ? extends U>, java.util.Comparator<? super U>); + method public static java.util.Comparator<T> comparing(java.util.function.Function<? super T, ? extends U>); + method public static java.util.Comparator<T> comparingDouble(java.util.function.ToDoubleFunction<? super T>); + method public static java.util.Comparator<T> comparingInt(java.util.function.ToIntFunction<? super T>); + method public static java.util.Comparator<T> comparingLong(java.util.function.ToLongFunction<? super T>); method public abstract boolean equals(java.lang.Object); + method public static java.util.Comparator<T> naturalOrder(); + method public static java.util.Comparator<T> nullsFirst(java.util.Comparator<? super T>); + method public static java.util.Comparator<T> nullsLast(java.util.Comparator<? super T>); + method public static java.util.Comparator<T> reverseOrder(); + method public default java.util.Comparator<T> reversed(); + method public default java.util.Comparator<T> thenComparing(java.util.Comparator<? super T>); + method public default java.util.Comparator<T> thenComparing(java.util.function.Function<? super T, ? extends U>, java.util.Comparator<? super U>); + method public default java.util.Comparator<T> thenComparing(java.util.function.Function<? super T, ? extends U>); + method public default java.util.Comparator<T> thenComparingDouble(java.util.function.ToDoubleFunction<? super T>); + method public default java.util.Comparator<T> thenComparingInt(java.util.function.ToIntFunction<? super T>); + method public default java.util.Comparator<T> thenComparingLong(java.util.function.ToLongFunction<? super T>); } public class ConcurrentModificationException extends java.lang.RuntimeException { diff --git a/api/test-current.txt b/api/test-current.txt index 6693c7a85f38..b5c4a472bcd9 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -38,6 +38,7 @@ package android { field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT"; field public static final java.lang.String BIND_VOICE_INTERACTION = "android.permission.BIND_VOICE_INTERACTION"; field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE"; + field public static final java.lang.String BIND_VR_LISTENER_SERVICE = "android.permission.BIND_VR_LISTENER_SERVICE"; field public static final java.lang.String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER"; field public static final java.lang.String BLUETOOTH = "android.permission.BLUETOOTH"; field public static final java.lang.String BLUETOOTH_ADMIN = "android.permission.BLUETOOTH_ADMIN"; @@ -114,6 +115,7 @@ package android { field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES"; field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE"; field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS"; + field public static final java.lang.String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION"; field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM"; field public static final java.lang.String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH"; field public static final java.lang.String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE"; @@ -3604,7 +3606,7 @@ package android.app { method public deprecated void setTitleColor(int); method public void setVisible(boolean); method public final void setVolumeControlStream(int); - method public void setVrMode(boolean); + method public void setVrModeEnabled(boolean, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean shouldShowRequestPermissionRationale(java.lang.String); method public boolean shouldUpRecreateTask(android.content.Intent); method public boolean showAssist(android.os.Bundle); @@ -8144,6 +8146,7 @@ package android.content { field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions"; field public static final java.lang.String SEARCH_SERVICE = "search"; field public static final java.lang.String SENSOR_SERVICE = "sensor"; + field public static final java.lang.String SHORTCUT_SERVICE = "shortcut"; field public static final java.lang.String STORAGE_SERVICE = "storage"; field public static final java.lang.String TELECOM_SERVICE = "telecom"; field public static final java.lang.String TELEPHONY_SERVICE = "phone"; @@ -9452,13 +9455,19 @@ package android.content.pm { public class LauncherApps { method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle); method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int, android.os.UserHandle); + method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo, android.os.UserHandle); + method public int getShortcutIconResId(android.content.pm.ShortcutInfo, android.os.UserHandle); + method public java.util.List<android.content.pm.ShortcutInfo> getShortcutInfo(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle); + method public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(android.content.pm.LauncherApps.ShortcutQuery, android.os.UserHandle); method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle); method public boolean isPackageEnabled(java.lang.String, android.os.UserHandle); + method public void pinShortcuts(java.lang.String, java.util.List<java.lang.String>, android.os.UserHandle); method public void registerCallback(android.content.pm.LauncherApps.Callback); method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler); method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle); method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); + method public void startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle, android.os.UserHandle); method public void unregisterCallback(android.content.pm.LauncherApps.Callback); } @@ -9471,6 +9480,18 @@ package android.content.pm { method public void onPackagesSuspended(java.lang.String[], android.os.UserHandle); method public abstract void onPackagesUnavailable(java.lang.String[], android.os.UserHandle, boolean); method public void onPackagesUnsuspended(java.lang.String[], android.os.UserHandle); + method public void onShortcutsChanged(java.lang.String, java.util.List<android.content.pm.ShortcutInfo>, android.os.UserHandle); + } + + public static class LauncherApps.ShortcutQuery { + ctor public LauncherApps.ShortcutQuery(); + method public void setActivity(android.content.ComponentName); + method public void setChangedSince(long); + method public void setPackage(java.lang.String); + method public void setQueryFlags(int); + field public static final int FLAG_GET_DYNAMIC = 1; // 0x1 + field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4 + field public static final int FLAG_GET_PINNED = 2; // 0x2 } public class PackageInfo implements android.os.Parcelable { @@ -9971,6 +9992,56 @@ package android.content.pm { field public java.lang.String permission; } + public class ShortcutInfo implements android.os.Parcelable { + method public int describeContents(); + method public android.content.ComponentName getActivityComponent(); + method public android.os.PersistableBundle getExtras(); + method public java.lang.String getId(); + method public android.content.Intent getIntent(); + method public long getLastChangedTimestamp(); + method public java.lang.String getPackageName(); + method public java.lang.String getTitle(); + method public int getWeight(); + method public boolean hasIconFile(); + method public boolean hasIconResource(); + method public boolean isDynamic(); + method public boolean isPinned(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1 + field public static final int CLONE_REMOVE_FOR_LAUNCHER = 3; // 0x3 + field public static final int CLONE_REMOVE_NON_KEY_INFO = 4; // 0x4 + field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR; + field public static final int FLAG_DYNAMIC = 1; // 0x1 + field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8 + field public static final int FLAG_HAS_ICON_RES = 4; // 0x4 + field public static final int FLAG_PINNED = 2; // 0x2 + } + + public static class ShortcutInfo.Builder { + ctor public ShortcutInfo.Builder(android.content.Context); + method public android.content.pm.ShortcutInfo build(); + method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName); + method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle); + method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); + method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); + method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String); + method public android.content.pm.ShortcutInfo.Builder setWeight(int); + } + + public class ShortcutManager { + method public boolean addDynamicShortcut(android.content.pm.ShortcutInfo); + method public void deleteAllDynamicShortcuts(); + method public void deleteDynamicShortcut(java.lang.String); + method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts(); + method public int getMaxDynamicShortcutCount(); + method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts(); + method public long getRateLimitResetTime(); + method public int getRemainingCallCount(); + method public boolean setDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>); + method public boolean updateShortcuts(java.util.List<android.content.pm.ShortcutInfo>); + } + public class Signature implements android.os.Parcelable { ctor public Signature(byte[]); ctor public Signature(java.lang.String); @@ -19731,7 +19802,7 @@ package android.media { method public void adjustVolume(int, int); method public void dispatchMediaKeyEvent(android.view.KeyEvent); method public int generateAudioSessionId(); - method public android.media.AudioRecordConfiguration[] getActiveRecordConfigurations(); + method public android.media.AudioRecordingConfiguration[] getActiveRecordingConfigurations(); method public android.media.AudioDeviceInfo[] getDevices(int); method public int getMode(); method public java.lang.String getParameters(java.lang.String); @@ -19877,7 +19948,7 @@ package android.media { public static abstract class AudioManager.AudioRecordingCallback { ctor public AudioManager.AudioRecordingCallback(); - method public void onRecordConfigChanged(android.media.AudioRecordConfiguration[]); + method public void onRecordConfigChanged(android.media.AudioRecordingConfiguration[]); } public static abstract interface AudioManager.OnAudioFocusChangeListener { @@ -19951,7 +20022,7 @@ package android.media { method public abstract void onRoutingChanged(android.media.AudioRecord); } - public final class AudioRecordConfiguration implements android.os.Parcelable { + public final class AudioRecordingConfiguration implements android.os.Parcelable { method public int describeContents(); method public android.media.AudioDeviceInfo getAudioDevice(); method public int getClientAudioSessionId(); @@ -19959,7 +20030,7 @@ package android.media { method public android.media.AudioFormat getClientFormat(); method public android.media.AudioFormat getFormat(); method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.media.AudioRecordConfiguration> CREATOR; + field public static final android.os.Parcelable.Creator<android.media.AudioRecordingConfiguration> CREATOR; } public abstract interface AudioRouting { @@ -29016,6 +29087,7 @@ package android.os { ctor public PersistableBundle(); ctor public PersistableBundle(int); ctor public PersistableBundle(android.os.PersistableBundle); + ctor public PersistableBundle(android.os.Bundle); method public java.lang.Object clone(); method public int describeContents(); method public android.os.PersistableBundle getPersistableBundle(java.lang.String); @@ -32186,6 +32258,7 @@ package android.provider { field public static final java.lang.String ACTION_VOICE_CONTROL_BATTERY_SAVER_MODE = "android.settings.VOICE_CONTROL_BATTERY_SAVER_MODE"; field public static final java.lang.String ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE = "android.settings.VOICE_CONTROL_DO_NOT_DISTURB_MODE"; field public static final java.lang.String ACTION_VOICE_INPUT_SETTINGS = "android.settings.VOICE_INPUT_SETTINGS"; + field public static final java.lang.String ACTION_VR_LISTENER_SETTINGS = "android.settings.VR_LISTENER_SETTINGS"; field public static final java.lang.String ACTION_WIFI_IP_SETTINGS = "android.settings.WIFI_IP_SETTINGS"; field public static final java.lang.String ACTION_WIFI_SETTINGS = "android.settings.WIFI_SETTINGS"; field public static final java.lang.String ACTION_WIRELESS_SETTINGS = "android.settings.WIRELESS_SETTINGS"; @@ -32223,6 +32296,7 @@ package android.provider { field public static final java.lang.String AUTO_TIME = "auto_time"; field public static final java.lang.String AUTO_TIME_ZONE = "auto_time_zone"; field public static final java.lang.String BLUETOOTH_ON = "bluetooth_on"; + field public static final java.lang.String BOOT_COUNT = "boot_count"; field public static final java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync"; field public static final android.net.Uri CONTENT_URI; field public static final java.lang.String DATA_ROAMING = "data_roaming"; @@ -34439,8 +34513,9 @@ package android.service.media { package android.service.notification { public class Condition implements android.os.Parcelable { - ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int); + ctor public Condition(android.net.Uri, java.lang.String, int); ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int, int, int); + ctor public Condition(android.os.Parcel); method public android.service.notification.Condition copy(); method public int describeContents(); method public static boolean isValidId(android.net.Uri, java.lang.String); @@ -34471,6 +34546,7 @@ package android.service.notification { method public final void notifyConditions(android.service.notification.Condition...); method public android.os.IBinder onBind(android.content.Intent); method public abstract void onConnected(); + method public void onRequestConditions(int); method public abstract void onSubscribe(android.net.Uri); method public abstract void onUnsubscribe(android.net.Uri); field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId"; @@ -34505,7 +34581,6 @@ package android.service.notification { method public static void requestRebind(android.content.ComponentName) throws android.os.RemoteException; method public final void requestUnbind() throws android.os.RemoteException; method public final void setNotificationsShown(java.lang.String[]); - field public static final java.lang.String CATEGORY_VR_NOTIFICATIONS = "android.intent.category.vr.notifications"; field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4 field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1 @@ -34800,6 +34875,17 @@ package android.service.voice { } +package android.service.vr { + + public abstract class VrListenerService extends android.app.Service { + ctor public VrListenerService(); + method public static final boolean isVrModePackageEnabled(android.content.Context, android.content.ComponentName); + method public android.os.IBinder onBind(android.content.Intent); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.vr.VrListenerService"; + } + +} + package android.service.wallpaper { public abstract class WallpaperService extends android.app.Service { @@ -57409,7 +57495,23 @@ package java.util { public abstract interface Comparator { method public abstract int compare(T, T); + method public static java.util.Comparator<T> comparing(java.util.function.Function<? super T, ? extends U>, java.util.Comparator<? super U>); + method public static java.util.Comparator<T> comparing(java.util.function.Function<? super T, ? extends U>); + method public static java.util.Comparator<T> comparingDouble(java.util.function.ToDoubleFunction<? super T>); + method public static java.util.Comparator<T> comparingInt(java.util.function.ToIntFunction<? super T>); + method public static java.util.Comparator<T> comparingLong(java.util.function.ToLongFunction<? super T>); method public abstract boolean equals(java.lang.Object); + method public static java.util.Comparator<T> naturalOrder(); + method public static java.util.Comparator<T> nullsFirst(java.util.Comparator<? super T>); + method public static java.util.Comparator<T> nullsLast(java.util.Comparator<? super T>); + method public static java.util.Comparator<T> reverseOrder(); + method public default java.util.Comparator<T> reversed(); + method public default java.util.Comparator<T> thenComparing(java.util.Comparator<? super T>); + method public default java.util.Comparator<T> thenComparing(java.util.function.Function<? super T, ? extends U>, java.util.Comparator<? super U>); + method public default java.util.Comparator<T> thenComparing(java.util.function.Function<? super T, ? extends U>); + method public default java.util.Comparator<T> thenComparingDouble(java.util.function.ToDoubleFunction<? super T>); + method public default java.util.Comparator<T> thenComparingInt(java.util.function.ToIntFunction<? super T>); + method public default java.util.Comparator<T> thenComparingLong(java.util.function.ToLongFunction<? super T>); } public class ConcurrentModificationException extends java.lang.RuntimeException { diff --git a/api/test-removed.txt b/api/test-removed.txt index 115224ce49a6..ba9751d68a3d 100644 --- a/api/test-removed.txt +++ b/api/test-removed.txt @@ -201,14 +201,6 @@ package android.provider { } -package android.service.notification { - - public abstract class ConditionProviderService extends android.app.Service { - method public void onRequestConditions(int); - } - -} - package android.test.mock { public deprecated class MockPackageManager extends android.content.pm.PackageManager { diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 8f361ce1a779..ea53e59b017d 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -68,8 +68,7 @@ static const int ANIM_ENTRY_NAME_MAX = 256; // --------------------------------------------------------------------------- -BootAnimation::BootAnimation() : Thread(false), mZip(NULL) -{ +BootAnimation::BootAnimation() : Thread(false), mZip(NULL), mClockEnabled(true) { mSession = new SurfaceComposerClient(); } @@ -450,6 +449,69 @@ bool BootAnimation::readFile(const char* name, String8& outString) return true; } +// The time glyphs are stored in a single image of height 64 pixels. Each digit is 40 pixels wide, +// and the colon character is half that at 20 pixels. The glyph order is '0123456789:'. +// We render 24 hour time. +void BootAnimation::drawTime(const Texture& clockTex, const int yPos) { + static constexpr char TIME_FORMAT[] = "%H:%M"; + static constexpr int TIME_LENGTH = sizeof(TIME_FORMAT); + + static constexpr int DIGIT_HEIGHT = 64; + static constexpr int DIGIT_WIDTH = 40; + static constexpr int COLON_WIDTH = DIGIT_WIDTH / 2; + static constexpr int TIME_WIDTH = (DIGIT_WIDTH * 4) + COLON_WIDTH; + + if (clockTex.h < DIGIT_HEIGHT || clockTex.w < (10 * DIGIT_WIDTH + COLON_WIDTH)) { + ALOGE("Clock texture is too small; abandoning boot animation clock"); + mClockEnabled = false; + return; + } + + time_t rawtime; + time(&rawtime); + struct tm* timeInfo = localtime(&rawtime); + + char timeBuff[TIME_LENGTH]; + size_t length = strftime(timeBuff, TIME_LENGTH, TIME_FORMAT, timeInfo); + + if (length != TIME_LENGTH - 1) { + ALOGE("Couldn't format time; abandoning boot animation clock"); + mClockEnabled = false; + return; + } + + glEnable(GL_BLEND); // Allow us to draw on top of the animation + glBindTexture(GL_TEXTURE_2D, clockTex.name); + + int xPos = (mWidth - TIME_WIDTH) / 2; + int cropRect[4] = { 0, DIGIT_HEIGHT, DIGIT_WIDTH, -DIGIT_HEIGHT }; + + for (int i = 0; i < TIME_LENGTH - 1; i++) { + char c = timeBuff[i]; + int width = DIGIT_WIDTH; + int pos = c - '0'; // Position in the character list + if (pos < 0 || pos > 10) { + continue; + } + if (c == ':') { + width = COLON_WIDTH; + } + + // Crop the texture to only the pixels in the current glyph + int left = pos * DIGIT_WIDTH; + cropRect[0] = left; + cropRect[2] = width; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect); + + glDrawTexiOES(xPos, yPos, 0, width, DIGIT_HEIGHT); + + xPos += width; + } + + glDisable(GL_BLEND); // Return to the animation's default behaviour + glBindTexture(GL_TEXTURE_2D, 0); +} + bool BootAnimation::movie() { String8 desString; @@ -477,7 +539,12 @@ bool BootAnimation::movie() if (endl == NULL) break; String8 line(s, endl - s); const char* l = line.string(); - int fps, width, height, count, pause; + int fps = 0; + int width = 0; + int height = 0; + int count = 0; + int pause = 0; + int clockPosY = -1; char path[ANIM_ENTRY_NAME_MAX]; char color[7] = "000000"; // default to black if unspecified @@ -487,14 +554,15 @@ bool BootAnimation::movie() animation.width = width; animation.height = height; animation.fps = fps; - } - else if (sscanf(l, " %c %d %d %s #%6s", &pathType, &count, &pause, path, color) >= 4) { - // ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s", pathType, count, pause, path, color); + } else if (sscanf(l, " %c %d %d %s #%6s %d", + &pathType, &count, &pause, path, color, &clockPosY) >= 4) { + // ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPosY=%d", pathType, count, pause, path, color, clockPosY); Animation::Part part; part.playUntilComplete = pathType == 'c'; part.count = count; part.pause = pause; part.path = path; + part.clockPosY = clockPosY; part.audioFile = NULL; if (!parseColor(color, part.backgroundColor)) { ALOGE("> invalid color '#%s'", color); @@ -556,6 +624,8 @@ bool BootAnimation::movie() mZip->endIteration(cookie); + // Blend required to draw time on top of animation frames. + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glShadeModel(GL_FLAT); glDisable(GL_DITHER); glDisable(GL_SCISSOR_TEST); @@ -569,6 +639,12 @@ bool BootAnimation::movie() glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + bool clockTextureInitialized = false; + if (mClockEnabled) { + clockTextureInitialized = (initTexture(&mClock, mAssets, "images/clock64.png") == NO_ERROR); + mClockEnabled = clockTextureInitialized; + } + const int xc = (mWidth - animation.width) / 2; const int yc = ((mHeight - animation.height) / 2); nsecs_t frameDuration = s2ns(1) / animation.fps; @@ -629,6 +705,10 @@ bool BootAnimation::movie() // which is equivalent to mHeight - (yc + animation.height) glDrawTexiOES(xc, mHeight - (yc + animation.height), 0, animation.width, animation.height); + if (mClockEnabled && part.clockPosY >= 0) { + drawTime(mClock, part.clockPosY); + } + eglSwapBuffers(mDisplay, mSurface); nsecs_t now = systemTime(); @@ -665,6 +745,10 @@ bool BootAnimation::movie() } } + if (clockTextureInitialized) { + glDeleteTextures(1, &mClock.name); + } + return false; } diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index f968b255d37a..83e2b3804fe8 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -67,8 +67,10 @@ private: } }; struct Part { - int count; - int pause; + int count; // The number of times this part should repeat, 0 for infinite + int pause; // The number of frames to pause for at the end of this part + int clockPosY; // The y position of the clock, in pixels, from the bottom of the + // display (the clock is centred horizontally). -1 to disable the clock String8 path; SortedVector<Frame> frames; bool playUntilComplete; @@ -86,6 +88,7 @@ private: bool android(); bool readFile(const char* name, String8& outString); bool movie(); + void drawTime(const Texture& clockTex, const int yPos); void checkExit(); @@ -93,6 +96,7 @@ private: sp<AudioPlayer> mAudioPlayer; AssetManager mAssets; Texture mAndroid[2]; + Texture mClock; int mWidth; int mHeight; EGLDisplay mDisplay; @@ -101,6 +105,7 @@ private: sp<SurfaceControl> mFlingerSurfaceControl; sp<Surface> mFlingerSurface; ZipFileRO *mZip; + bool mClockEnabled; }; // --------------------------------------------------------------------------- diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index b87e9fa2d879..6b67b959064e 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -6177,14 +6177,24 @@ public class Activity extends ContextThemeWrapper /** * Enable or disable virtual reality (VR) mode. * - * <p>VR mode is a hint to Android system services to switch to modes optimized for - * high-performance stereoscopic rendering.</p> + * <p>VR mode is a hint to Android system services to switch to a mode optimized for + * high-performance stereoscopic rendering. This mode will be enabled while this Activity has + * focus.</p> * * @param enabled {@code true} to enable this mode. + * @param requestedComponent the name of the component to use as a + * {@link android.service.vr.VrListenerService} while VR mode is enabled. + * + * @throws android.content.pm.PackageManager.NameNotFoundException; */ - public void setVrMode(boolean enabled) { + public void setVrModeEnabled(boolean enabled, @NonNull ComponentName requestedComponent) + throws PackageManager.NameNotFoundException { try { - ActivityManagerNative.getDefault().setVrMode(mToken, enabled); + if (ActivityManagerNative.getDefault().setVrMode(mToken, enabled, requestedComponent) + != 0) { + throw new PackageManager.NameNotFoundException( + requestedComponent.flattenToString()); + } } catch (RemoteException e) { // pass } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index a4e5b9049e9d..2f6907e3f31e 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1575,6 +1575,7 @@ public class ActivityManager { Parcelable.PARCELABLE_WRITE_RETURN_VALUE); dest.writeInt(numActivities); dest.writeInt(numRunning); + dest.writeInt(isDockable ? 1 : 0); } public void readFromParcel(Parcel source) { @@ -1590,6 +1591,7 @@ public class ActivityManager { description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); numActivities = source.readInt(); numRunning = source.readInt(); + isDockable = source.readInt() != 0; } public static final Creator<RunningTaskInfo> CREATOR = new Creator<RunningTaskInfo>() { @@ -3371,6 +3373,15 @@ public class ActivityManager { } } + /** {@hide} */ + public boolean isVrModePackageEnabled(ComponentName component) { + try { + return ActivityManagerNative.getDefault().isVrModePackageEnabled(component); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Perform a system dump of various state associated with the given application * package name. This call blocks while the dump is being performed, so should diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index ff7f70dbd838..6fbb430ec8be 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -2902,8 +2902,18 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.enforceInterface(IActivityManager.descriptor); final IBinder token = data.readStrongBinder(); final boolean enable = data.readInt() == 1; - setVrMode(token, enable); + final ComponentName packageName = ComponentName.CREATOR.createFromParcel(data); + int res = setVrMode(token, enable, packageName); reply.writeNoException(); + reply.writeInt(res); + return true; + } + case IS_VR_PACKAGE_ENABLED_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + final ComponentName packageName = ComponentName.CREATOR.createFromParcel(data); + boolean res = isVrModePackageEnabled(packageName); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); return true; } case IS_APP_FOREGROUND_TRANSACTION: { @@ -6247,16 +6257,34 @@ class ActivityManagerProxy implements IActivityManager return res; } - public void setVrMode(IBinder token, boolean enabled) throws RemoteException { + public int setVrMode(IBinder token, boolean enabled, ComponentName packageName) + throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(token); data.writeInt(enabled ? 1 : 0); + packageName.writeToParcel(data, 0); mRemote.transact(SET_VR_MODE_TRANSACTION, data, reply, 0); reply.readException(); + int res = reply.readInt(); + data.recycle(); + reply.recycle(); + return res; + } + + public boolean isVrModePackageEnabled(ComponentName packageName) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + packageName.writeToParcel(data, 0); + mRemote.transact(IS_VR_PACKAGE_ENABLED_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); data.recycle(); reply.recycle(); + return res == 1; } @Override diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index 1f1f318be5c1..cd4ace669b6c 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -42,7 +42,7 @@ public final class AutomaticZenRule implements Parcelable { * @param name The name of the rule. * @param owner The Condition Provider service that owns this rule. * @param conditionId A representation of the state that should cause the Condition Provider - * service to apply the interruption filter. + * service to apply the given interruption filter. * @param interruptionFilter The interruption filter defines which notifications are allowed to * interrupt the user (e.g. via sound & vibration) while this rule * is active. diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 70bff800383d..eadf4978688c 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -33,6 +33,7 @@ import android.content.UriPermission; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.IPackageDataObserver; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.content.pm.ProviderInfo; import android.content.pm.UserInfo; @@ -599,7 +600,10 @@ public interface IActivityManager extends IInterface { public void enterPictureInPicture(IBinder token) throws RemoteException; - public void setVrMode(IBinder token, boolean enabled) throws RemoteException; + public int setVrMode(IBinder token, boolean enabled, ComponentName packageName) + throws RemoteException; + + public boolean isVrModePackageEnabled(ComponentName packageName) throws RemoteException; public boolean isAppForeground(int uid) throws RemoteException; @@ -993,4 +997,5 @@ public interface IActivityManager extends IInterface { int SET_LENIENT_BACKGROUND_CHECK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+368; int GET_MEMORY_TRIM_LEVEL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+369; int RESIZE_PINNED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 370; + int IS_VR_PACKAGE_ENABLED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 371; } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 569792438f23..2c270fc8a95c 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -61,7 +61,7 @@ interface INotificationManager StatusBarNotification[] getActiveNotifications(String callingPkg); StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count); - void registerListener(in INotificationListener listener, in ComponentName component, int userid); + void registerListener(in INotificationListener listener, in ComponentName component, int userid, boolean asRanker); void unregisterListener(in INotificationListener listener, int userid); void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id); @@ -80,7 +80,7 @@ interface INotificationManager void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim); void setInterruptionFilter(String pkg, int interruptionFilter); - void setImportanceFromAssistant(in INotificationListener token, String key, int importance, CharSequence explanation); + void setImportanceFromRankerService(in INotificationListener token, String key, int importance, CharSequence explanation); ComponentName getEffectsSuppressor(); boolean matchesCallFilter(in Bundle extras); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 2c34371a12c1..d8f0ac518151 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2994,7 +2994,6 @@ public class Notification implements Parcelable contentView.setViewVisibility(R.id.chronometer, View.GONE); contentView.setViewVisibility(R.id.header_sub_text, View.GONE); contentView.setViewVisibility(R.id.header_content_info, View.GONE); - contentView.setViewVisibility(R.id.number_of_children, View.GONE); contentView.setViewVisibility(R.id.sub_text_divider, View.GONE); contentView.setViewVisibility(R.id.content_info_divider, View.GONE); contentView.setViewVisibility(R.id.time_divider, View.GONE); @@ -3095,7 +3094,6 @@ public class Notification implements Parcelable private void bindNotificationHeader(RemoteViews contentView) { bindSmallIcon(contentView); - bindChildCountColor(contentView); bindHeaderAppName(contentView); bindHeaderSubText(contentView); bindContentInfo(contentView); @@ -3104,10 +3102,6 @@ public class Notification implements Parcelable bindProfileBadge(contentView); } - private void bindChildCountColor(RemoteViews contentView) { - contentView.setTextColor(R.id.number_of_children, resolveColor()); - } - private void bindContentInfo(RemoteViews contentView) { boolean visible = false; if (mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) { diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index b1c5fd8c6963..3a5dd30bfda7 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -37,7 +37,9 @@ import android.content.Context; import android.content.IRestrictionsManager; import android.content.RestrictionsManager; import android.content.pm.ILauncherApps; +import android.content.pm.IShortcutService; import android.content.pm.LauncherApps; +import android.content.pm.ShortcutManager; import android.content.res.Resources; import android.hardware.ConsumerIrManager; import android.hardware.ISerialManager; @@ -748,6 +750,15 @@ final class SystemServiceRegistry { Log.i(TAG, "Creating new instance of SoundTriggerManager object."); return new SoundTriggerManager(ctx, ISoundTriggerService.Stub.asInterface(b)); }}); + + registerService(Context.SHORTCUT_SERVICE, ShortcutManager.class, + new CachedServiceFetcher<ShortcutManager>() { + @Override + public ShortcutManager createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(Context.SHORTCUT_SERVICE); + return new ShortcutManager(ctx, + IShortcutService.Stub.asInterface(b)); + }}); } /** diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index b935b256b880..f96ddf0d183a 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2680,6 +2680,7 @@ public abstract class Context { RADIO_SERVICE, HARDWARE_PROPERTIES_SERVICE, //@hide: SOUND_TRIGGER_SERVICE, + SHORTCUT_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -3576,6 +3577,14 @@ public abstract class Context { public static final String HARDWARE_PROPERTIES_SERVICE = "hardwareproperties"; /** + * TODO Javadoc + * + * @see #getSystemService + * @see android.content.pm.ShortcutManager + */ + public static final String SHORTCUT_SERVICE = "shortcut"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index cc266c552760..da3c873c043e 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -22,6 +22,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IOnAppsChangedListener; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutInfo; import android.graphics.Rect; import android.os.Bundle; import android.os.UserHandle; @@ -42,4 +43,13 @@ interface ILauncherApps { boolean isPackageEnabled(String packageName, in UserHandle user); boolean isActivityEnabled(in ComponentName component, in UserHandle user); ApplicationInfo getApplicationInfo(String packageName, int flags, in UserHandle user); + + ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName, + in ComponentName componentName, int flags, in UserHandle user); + ParceledListSlice getShortcutInfo(String callingPackage, String packageName, in List<String> ids, + in UserHandle user); + void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds, + in UserHandle user); + void startShortcut(String callingPackage, in ShortcutInfo shortcut, in Rect sourceBounds, + in Bundle startActivityOptions, in UserHandle user); } diff --git a/core/java/android/content/pm/IOnAppsChangedListener.aidl b/core/java/android/content/pm/IOnAppsChangedListener.aidl index 130369661e05..e6525af311ed 100644 --- a/core/java/android/content/pm/IOnAppsChangedListener.aidl +++ b/core/java/android/content/pm/IOnAppsChangedListener.aidl @@ -16,6 +16,7 @@ package android.content.pm; +import android.content.pm.ParceledListSlice; import android.os.UserHandle; /** @@ -29,4 +30,5 @@ oneway interface IOnAppsChangedListener { void onPackagesUnavailable(in UserHandle user, in String[] packageNames, boolean replacing); void onPackagesSuspended(in UserHandle user, in String[] packageNames); void onPackagesUnsuspended(in UserHandle user, in String[] packageNames); + void onShortcutChanged(in UserHandle user, String packageName, in ParceledListSlice shortcuts); } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index b4e9f60700a3..c6844471d71e 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -120,6 +120,8 @@ interface IPackageManager { int checkUidSignatures(int uid1, int uid2); + List<String> getAllPackages(); + String[] getPackagesForUid(int uid); String getNameForUid(int uid); @@ -380,6 +382,13 @@ interface IPackageManager { */ void clearApplicationUserData(in String packageName, IPackageDataObserver observer, int userId); + /** + * Clear the profile data of an application. + * @param packageName The package name of the application whose profile data + * need to be deleted + */ + void clearApplicationProfileData(in String packageName); + /** * Get package statistics including the code, data and cache size for * an already installed package diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl new file mode 100644 index 000000000000..23e671d82f12 --- /dev/null +++ b/core/java/android/content/pm/IShortcutService.aidl @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.pm; + +import android.content.pm.ParceledListSlice; +import android.content.pm.ShortcutInfo; + +/** + * {@hide} + */ +interface IShortcutService { + + boolean setDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList, + int userId); + + ParceledListSlice getDynamicShortcuts(String packageName, int userId); + + boolean addDynamicShortcut(String packageName, in ShortcutInfo shortcutInfo, int userId); + + void deleteDynamicShortcut(String packageName, in String shortcutId, int userId); + + void deleteAllDynamicShortcuts(String packageName, int userId); + + ParceledListSlice getPinnedShortcuts(String packageName, int userId); + + boolean updateShortcuts(String packageName, in ParceledListSlice shortcuts, int userId); + + int getMaxDynamicShortcutCount(String packageName, int userId); + + int getRemainingCallCount(String packageName, int userId); + + long getRateLimitResetTime(String packageName, int userId); + + void resetThrottling(); // system only API for developer opsions +}
\ No newline at end of file diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index e443d5073c54..f05ecd39a44d 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -16,23 +16,29 @@ package android.content.pm; +import android.Manifest.permission; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.ILauncherApps; -import android.content.pm.IOnAppsChangedListener; import android.content.pm.PackageManager.ApplicationInfoFlags; -import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -148,6 +154,91 @@ public class LauncherApps { */ public void onPackagesUnsuspended(String[] packageNames, UserHandle user) { } + + /** + * Indicates that one or more shortcuts (which may be dynamic and/or pinned) + * have been added, updated or removed. + * + * @param packageName The name of the package that has the shortcuts. + * @param shortcuts all shortcuts from the package (dynamic and/or pinned). + * @param user The UserHandle of the profile that generated the change. + */ + public void onShortcutsChanged(@NonNull String packageName, + @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) { + } + } + + /** + * Represents a query passed to {@link #getShortcuts(ShortcutQuery, UserHandle)}. + */ + public static class ShortcutQuery { + /** + * Include dynamic shortcuts in the result. + */ + public static final int FLAG_GET_DYNAMIC = 1 << 0; + + /** + * Include pinned shortcuts in the result. + */ + public static final int FLAG_GET_PINNED = 1 << 1; + + /** + * Requests "key" fields only. + */ + public static final int FLAG_GET_KEY_FIELDS_ONLY = 1 << 2; + + /** @hide */ + @IntDef(flag = true, + value = { + FLAG_GET_DYNAMIC, + FLAG_GET_PINNED, + FLAG_GET_KEY_FIELDS_ONLY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface QueryFlags {} + + long mChangedSince; + + @Nullable + String mPackage; + + @Nullable + ComponentName mActivity; + + @QueryFlags + int mQueryFlags; + + public ShortcutQuery() { + } + + /** + * If non-zero, returns only shortcuts that have been added or updated since the timestamp, + * which is a milliseconds since the Epoch. + */ + public void setChangedSince(long changedSince) { + mChangedSince = changedSince; + } + + /** + * If non-null, returns only shortcuts from the package. + */ + public void setPackage(@Nullable String packageName) { + mPackage = packageName; + } + + /** + * If non-null, returns only shortcuts associated with the activity. + */ + public void setActivity(@Nullable ComponentName activity) { + mActivity = activity; + } + + /** + * Set query options. + */ + public void setQueryFlags(@QueryFlags int queryFlags) { + mQueryFlags = queryFlags; + } } /** @hide */ @@ -302,6 +393,125 @@ public class LauncherApps { } } + /** + * Returns the IDs of {@link ShortcutInfo}s that match {@code query}. + * + * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission. + * + * @param query result includes shortcuts matching this query. + * @param user The UserHandle of the profile. + * + * @return the IDs of {@link ShortcutInfo}s that match the query. + */ + @RequiresPermission(permission.BIND_APPWIDGET) + @Nullable + public List<ShortcutInfo> getShortcuts(@NonNull ShortcutQuery query, + @NonNull UserHandle user) { + try { + return mService.getShortcuts(mContext.getPackageName(), + query.mChangedSince, query.mPackage, query.mActivity, query.mQueryFlags, user) + .getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns {@link ShortcutInfo}s with the given IDs from a package. + * + * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission. + * + * @param packageName The target package. + * @param ids IDs of the shortcuts to retrieve. + * @param user The UserHandle of the profile. + * + * @return list of {@link ShortcutInfo} associated with the package. + */ + @RequiresPermission(permission.BIND_APPWIDGET) + @Nullable + public List<ShortcutInfo> getShortcutInfo(@NonNull String packageName, + @NonNull List<String> ids, @NonNull UserHandle user) { + try { + return mService.getShortcutInfo(mContext.getPackageName(), packageName, ids, user) + .getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + + /** + * Pin shortcuts on a package. + * + * <p>This API is <b>NOT</b> cumulative; this will replace all pinned shortcuts for the package. + * However, different launchers may have different set of pinned shortcuts. + * + * <p>Callers must have the {@link permission#BIND_APPWIDGET} permission. + * + * @param packageName The target package name. + * @param shortcutIds The IDs of the shortcut to be pinned. + * @param user The UserHandle of the profile. + */ + @RequiresPermission(permission.BIND_APPWIDGET) + public void pinShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds, + @NonNull UserHandle user) { + try { + mService.pinShortcuts(mContext.getPackageName(), packageName, shortcutIds, user); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return the icon resource ID, if {@code shortcut} has one + * (i.e. when {@link ShortcutInfo#hasIconResource()} returns {@code true}). + * + * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission. + * + * @param shortcut The target shortcut. + * @param user The UserHandle of the profile. + */ + @RequiresPermission(permission.BIND_APPWIDGET) + public int getShortcutIconResId(@NonNull ShortcutInfo shortcut, @NonNull UserHandle user) { + throw new RuntimeException("not implemented yet"); + } + + /** + * Return the icon as {@link ParcelFileDescriptor}, when it's stored as a file + * (i.e. when {@link ShortcutInfo#hasIconFile()} returns {@code true}). + * + * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission. + * + * @param shortcut The target shortcut. + * @param user The UserHandle of the profile. + */ + @RequiresPermission(permission.BIND_APPWIDGET) + public ParcelFileDescriptor getShortcutIconFd( + @NonNull ShortcutInfo shortcut, @NonNull UserHandle user) { + throw new RuntimeException("not implemented yet"); + } + + /** + * Launches a shortcut. + * + * <p>Callers mut have the {@link permission#BIND_APPWIDGET} permission. + * + * @param shortcut The target shortcut. + * @param sourceBounds The Rect containing the source bounds of the clicked icon. + * @param startActivityOptions Options to pass to startActivity. + * @param user The UserHandle of the profile. + */ + @RequiresPermission(permission.BIND_APPWIDGET) + public void startShortcut(@NonNull ShortcutInfo shortcut, + @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions, + @NonNull UserHandle user) { + try { + mService.startShortcut(mContext.getPackageName(), shortcut, sourceBounds, + startActivityOptions, user); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** * Registers a callback for changes to packages in current and managed profiles. @@ -474,6 +684,20 @@ public class LauncherApps { } } } + + @Override + public void onShortcutChanged(UserHandle user, String packageName, + ParceledListSlice shortcuts) { + if (DEBUG) { + Log.d(TAG, "onShortcutChanged " + user.getIdentifier() + "," + packageName); + } + final List<ShortcutInfo> list = shortcuts.getList(); + synchronized (LauncherApps.this) { + for (CallbackMessageHandler callback : mCallbacks) { + callback.postOnShortcutChanged(packageName, user, list); + } + } + } }; private static class CallbackMessageHandler extends Handler { @@ -484,6 +708,7 @@ public class LauncherApps { private static final int MSG_UNAVAILABLE = 5; private static final int MSG_SUSPENDED = 6; private static final int MSG_UNSUSPENDED = 7; + private static final int MSG_SHORTCUT_CHANGED = 8; private LauncherApps.Callback mCallback; @@ -492,6 +717,7 @@ public class LauncherApps { String packageName; boolean replacing; UserHandle user; + List<ShortcutInfo> shortcuts; } public CallbackMessageHandler(Looper looper, LauncherApps.Callback callback) { @@ -527,6 +753,9 @@ public class LauncherApps { case MSG_UNSUSPENDED: mCallback.onPackagesUnsuspended(info.packageNames, info.user); break; + case MSG_SHORTCUT_CHANGED: + mCallback.onShortcutsChanged(info.packageName, info.shortcuts, info.user); + break; } } @@ -582,5 +811,14 @@ public class LauncherApps { info.user = user; obtainMessage(MSG_UNSUSPENDED, info).sendToTarget(); } + + public void postOnShortcutChanged(String packageName, UserHandle user, + List<ShortcutInfo> shortcuts) { + CallbackInfo info = new CallbackInfo(); + info.packageName = packageName; + info.user = user; + info.shortcuts = shortcuts; + obtainMessage(MSG_SHORTCUT_CHANGED, info).sendToTarget(); + } } } diff --git a/libs/hwui/tests/unit/CrashHandlerInjector.cpp b/core/java/android/content/pm/ShortcutInfo.aidl index b1c678d1c6fa..08e18731aa9b 100644 --- a/libs/hwui/tests/unit/CrashHandlerInjector.cpp +++ b/core/java/android/content/pm/ShortcutInfo.aidl @@ -13,29 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package android.content.pm; -#include "tests/common/TestUtils.h" - -#include <gtest/gtest.h> -#include <cstdio> - -using namespace android::uirenderer; - -static void gunitCrashHandler() { - auto testinfo = ::testing::UnitTest::GetInstance()->current_test_info(); - printf("[ FAILED ] %s.%s\n", testinfo->test_case_name(), - testinfo->name()); - printf("[ FATAL! ] RenderThread crashed, aborting tests!\n"); - fflush(stdout); -} - -static void hookError() { - TestUtils::setRenderThreadCrashHandler(gunitCrashHandler); -} - -class HookErrorInit { -public: - HookErrorInit() { hookError(); } -}; - -static HookErrorInit sInit; +parcelable ShortcutInfo; diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java new file mode 100644 index 000000000000..65205636c98e --- /dev/null +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -0,0 +1,680 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.pm; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Icon; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PersistableBundle; +import android.os.UserHandle; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * TODO Enhance javadoc + * + * Represents a shortcut form an application. + * + * Notes... + * - If an {@link Icon} is of a resource, then we'll just persist the package name and resource ID. + * + * Otherwise, the bitmap will be fetched when it's registered to ShortcutManager, then *shrunk* + * if necessary, and persisted. + * + * We will disallow byte[] icons, because they can easily go over binder size limit. + * + * TODO Move save/load to this class + */ +public class ShortcutInfo implements Parcelable { + /* @hide */ + public static final int FLAG_DYNAMIC = 1 << 0; + + /* @hide */ + public static final int FLAG_PINNED = 1 << 1; + + /* @hide */ + public static final int FLAG_HAS_ICON_RES = 1 << 2; + + /* @hide */ + public static final int FLAG_HAS_ICON_FILE = 1 << 3; + + /** @hide */ + @IntDef(flag = true, + value = { + FLAG_DYNAMIC, + FLAG_PINNED, + FLAG_HAS_ICON_RES, + FLAG_HAS_ICON_FILE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ShortcutFlags {} + + // Cloning options. + + /* @hide */ + private static final int CLONE_REMOVE_ICON = 1 << 0; + + /* @hide */ + private static final int CLONE_REMOVE_INTENT = 1 << 1; + + /* @hide */ + public static final int CLONE_REMOVE_NON_KEY_INFO = 1 << 2; + + /* @hide */ + public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON; + + /* @hide */ + public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT; + + /** @hide */ + @IntDef(flag = true, + value = { + CLONE_REMOVE_ICON, + CLONE_REMOVE_INTENT, + CLONE_REMOVE_NON_KEY_INFO, + CLONE_REMOVE_FOR_CREATOR, + CLONE_REMOVE_FOR_LAUNCHER + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CloneFlags {} + + private final String mId; + + @NonNull + private final String mPackageName; + + @Nullable + private ComponentName mActivityComponent; + + @Nullable + private Icon mIcon; + + @NonNull + private String mTitle; + + @NonNull + private Intent mIntent; + + // Internal use only. + @NonNull + private PersistableBundle mIntentPersistableExtras; + + private int mWeight; + + @Nullable + private PersistableBundle mExtras; + + private long mLastChangedTimestamp; + + // Internal use only. + @ShortcutFlags + private int mFlags; + + // Internal use only. + private int mIconResourceId; + + // Internal use only. + @Nullable + private String mBitmapPath; + + private ShortcutInfo(Builder b) { + mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"); + + // Note we can't do other null checks here because SM.updateShortcuts() takes partial + // information. + mPackageName = b.mContext.getPackageName(); + mActivityComponent = b.mActivityComponent; + mIcon = b.mIcon; + mTitle = b.mTitle; + mIntent = b.mIntent; + mWeight = b.mWeight; + mExtras = b.mExtras; + updateTimestamp(); + } + + /** + * Throws if any of the mandatory fields is not set. + * + * @hide + */ + public void enforceMandatoryFields() { + Preconditions.checkStringNotEmpty(mTitle, "Shortcut title must be provided"); + Preconditions.checkNotNull(mIntent, "Shortcut Intent must be provided"); + } + + /** + * Copy constructor. + */ + private ShortcutInfo(ShortcutInfo source, @CloneFlags int cloneFlags) { + mId = source.mId; + mPackageName = source.mPackageName; + mActivityComponent = source.mActivityComponent; + mFlags = source.mFlags; + mLastChangedTimestamp = source.mLastChangedTimestamp; + + if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) { + if ((cloneFlags & CLONE_REMOVE_ICON) == 0) { + mIcon = source.mIcon; + } + + mTitle = source.mTitle; + if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) { + mIntent = source.mIntent; + mIntentPersistableExtras = source.mIntentPersistableExtras; + } + mWeight = source.mWeight; + mExtras = source.mExtras; + mIconResourceId = source.mIconResourceId; + mBitmapPath = source.mBitmapPath; + } + } + + /** + * Copy a {@link ShortcutInfo}, optionally removing fields. + * @hide + */ + public ShortcutInfo clone(@CloneFlags int cloneFlags) { + return new ShortcutInfo(this, cloneFlags); + } + + /** + * Copy non-null/zero fields from another {@link ShortcutInfo}. Only "public" information + * will be overwritten. The timestamp will be updated. + * + * - Flags will not change + * - mBitmapPath will not change + * - Current time will be set to timestamp + * + * @hide + */ + public void copyNonNullFieldsFrom(ShortcutInfo source) { + Preconditions.checkState(mId == source.mId, "ID must match"); + Preconditions.checkState(mPackageName.equals(source.mPackageName), + "Package namae must match"); + + if (source.mActivityComponent != null) { + mActivityComponent = source.mActivityComponent; + } + + if (source.mIcon != null) { + mIcon = source.mIcon; + } + if (source.mTitle != null) { + mTitle = source.mTitle; + } + if (source.mIntent != null) { + mIntent = source.mIntent; + mIntentPersistableExtras = source.mIntentPersistableExtras; + } + if (source.mWeight != 0) { + mWeight = source.mWeight; + } + if (source.mExtras != null) { + mExtras = source.mExtras; + } + + updateTimestamp(); + } + + /** + * Builder class for {@link ShortcutInfo} objects. + */ + public static class Builder { + private final Context mContext; + + private String mId; + + private ComponentName mActivityComponent; + + private Icon mIcon; + + private String mTitle; + + private Intent mIntent; + + private int mWeight; + + private PersistableBundle mExtras; + + /** Constructor. */ + public Builder(Context context) { + mContext = context; + } + + /** + * Sets the ID of the shortcut. This is a mandatory field. + */ + @NonNull + public Builder setId(@NonNull String id) { + mId = Preconditions.checkStringNotEmpty(id, "id"); + return this; + } + + /** + * Optionally sets the target activity. + */ + @NonNull + public Builder setActivityComponent(@NonNull ComponentName activityComponent) { + mActivityComponent = Preconditions.checkNotNull(activityComponent, "activityComponent"); + return this; + } + + /** + * Optionally sets an icon. + * + * - Tint is not supported TODO Either check and throw, or support it. + * - URI icons will be converted into Bitmap icons at the registration time. + * + * TODO Only allow Bitmap, Resource and URI types. byte[] type can easily go over + * binder size limit. + */ + @NonNull + public Builder setIcon(Icon icon) { + mIcon = icon; + return this; + } + + /** + * Sets the title of a shortcut. This is a mandatory field. + */ + @NonNull + public Builder setTitle(@NonNull String title) { + mTitle = Preconditions.checkStringNotEmpty(title, "title"); + return this; + } + + /** + * Sets the intent of a shortcut. This is a mandatory field. The extras must only contain + * persistable information. (See {@link PersistableBundle}). + */ + @NonNull + public Builder setIntent(@NonNull Intent intent) { + mIntent = Preconditions.checkNotNull(intent, "intent"); + return this; + } + + /** + * Optionally sets the weight of a shortcut, which will be used by Launcher for sorting. + * The larger the weight, the more "important" a shortcut is. + */ + @NonNull + public Builder setWeight(int weight) { + mWeight = weight; + return this; + } + + /** + * Optional values that application can set. + * TODO: reserve keys starting with "android." + */ + @NonNull + public Builder setExtras(@NonNull PersistableBundle extras) { + mExtras = extras; + return this; + } + + /** + * Creates a {@link ShortcutInfo} instance. + */ + @NonNull + public ShortcutInfo build() { + return new ShortcutInfo(this); + } + } + + /** + * Return the ID of the shortcut. + */ + @NonNull + public String getId() { + return mId; + } + + /** + * Return the ID of the shortcut. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * Return the target activity, which may be null, in which case the shortcut is not associated + * with a specific activity. + */ + @Nullable + public ComponentName getActivityComponent() { + return mActivityComponent; + } + + /** + * Icon. + * + * For performance reasons, this will <b>NOT</b> be available when an instance is returned + * by {@link ShortcutManager} or {@link LauncherApps}. A launcher application needs to use + * other APIs in LauncherApps to fetch the bitmap. TODO Add a precondition for it. + * + * @hide + */ + @Nullable + public Icon getIcon() { + return mIcon; + } + + /** + * Return the shortcut title. + */ + @NonNull + public String getTitle() { + return mTitle; + } + + /** + * Return the intent. + * TODO Set mIntentPersistableExtras and before returning. + */ + @NonNull + public Intent getIntent() { + return mIntent; + } + + /** @hide */ + @Nullable + public PersistableBundle getIntentPersistableExtras() { + return mIntentPersistableExtras; + } + + /** + * Return the weight of a shortcut, which will be used by Launcher for sorting. + * The larger the weight, the more "important" a shortcut is. + */ + public int getWeight() { + return mWeight; + } + + /** + * Optional values that application can set. + */ + @Nullable + public PersistableBundle getExtras() { + return mExtras; + } + + /** + * Last time when any of the fields was updated. + */ + public long getLastChangedTimestamp() { + return mLastChangedTimestamp; + } + + /** @hide */ + @ShortcutFlags + public int getFlags() { + return mFlags; + } + + /** @hide*/ + public void setFlags(@ShortcutFlags int flags) { + mFlags = flags; + } + + /** @hide*/ + public void addFlags(@ShortcutFlags int flags) { + mFlags |= flags; + } + + /** @hide*/ + public void clearFlags(@ShortcutFlags int flags) { + mFlags &= ~flags; + } + + /** @hide*/ + public boolean hasFlags(@ShortcutFlags int flags) { + return (mFlags & flags) == flags; + } + + /** Return whether a shortcut is dynamic. */ + public boolean isDynamic() { + return hasFlags(FLAG_DYNAMIC); + } + + /** Return whether a shortcut is pinned. */ + public boolean isPinned() { + return hasFlags(FLAG_PINNED); + } + + /** + * Return whether a shortcut's icon is a resource in the owning package. + * + * @see LauncherApps#getShortcutIconResId(ShortcutInfo, UserHandle) + */ + public boolean hasIconResource() { + return hasFlags(FLAG_HAS_ICON_RES); + } + + /** + * Return whether a shortcut's icon is stored as a file. + * + * @see LauncherApps#getShortcutIconFd(ShortcutInfo, UserHandle) + */ + public boolean hasIconFile() { + return hasFlags(FLAG_HAS_ICON_FILE); + } + + /** @hide */ + public void updateTimestamp() { + mLastChangedTimestamp = System.currentTimeMillis(); + } + + /** @hide */ + // VisibleForTesting + public void setTimestamp(long value) { + mLastChangedTimestamp = value; + } + + /** @hide */ + public void setIcon(Icon icon) { + mIcon = icon; + } + + /** @hide */ + public void setTitle(String title) { + mTitle = title; + } + + /** @hide */ + public void setIntent(Intent intent) { + mIntent = intent; + } + + /** @hide */ + public void setIntentPersistableExtras(PersistableBundle intentPersistableExtras) { + mIntentPersistableExtras = intentPersistableExtras; + } + + /** @hide */ + public void setWeight(int weight) { + mWeight = weight; + } + + /** @hide */ + public void setExtras(PersistableBundle extras) { + mExtras = extras; + } + + /** @hide */ + public int getIconResourceId() { + return mIconResourceId; + } + + /** @hide */ + public String getBitmapPath() { + return mBitmapPath; + } + + /** @hide */ + public void setBitmapPath(String bitmapPath) { + mBitmapPath = bitmapPath; + } + + private ShortcutInfo(Parcel source) { + final ClassLoader cl = getClass().getClassLoader(); + + mId = source.readString(); + mPackageName = source.readString(); + mActivityComponent = source.readParcelable(cl); + mIcon = source.readParcelable(cl); + mTitle = source.readString(); + mIntent = source.readParcelable(cl); + mIntentPersistableExtras = source.readParcelable(cl); + mWeight = source.readInt(); + mExtras = source.readParcelable(cl); + mLastChangedTimestamp = source.readLong(); + mFlags = source.readInt(); + mIconResourceId = source.readInt(); + mBitmapPath = source.readString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mId); + dest.writeString(mPackageName); + dest.writeParcelable(mActivityComponent, flags); + dest.writeParcelable(mIcon, flags); + dest.writeString(mTitle); + dest.writeParcelable(mIntent, flags); + dest.writeParcelable(mIntentPersistableExtras, flags); + dest.writeInt(mWeight); + dest.writeParcelable(mExtras, flags); + dest.writeLong(mLastChangedTimestamp); + dest.writeInt(mFlags); + dest.writeInt(mIconResourceId); + dest.writeString(mBitmapPath); + } + + public static final Creator<ShortcutInfo> CREATOR = + new Creator<ShortcutInfo>() { + public ShortcutInfo createFromParcel(Parcel source) { + return new ShortcutInfo(source); + } + public ShortcutInfo[] newArray(int size) { + return new ShortcutInfo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + /** + * Return a string representation, intended for logging. Some fields will be retracted. + */ + @Override + public String toString() { + return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false); + } + + /** @hide */ + public String toInsecureString() { + return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true); + } + + private String toStringInner(boolean secure, boolean includeInternalData) { + final StringBuilder sb = new StringBuilder(); + sb.append("ShortcutInfo {"); + + sb.append("id="); + sb.append(secure ? "***" : mId); + + sb.append(", packageName="); + sb.append(mPackageName); + + if (isDynamic()) { + sb.append(", dynamic"); + } + if (isPinned()) { + sb.append(", pinned"); + } + + sb.append(", activity="); + sb.append(mActivityComponent); + + sb.append(", title="); + sb.append(secure ? "***" : mTitle); + + sb.append(", icon="); + sb.append(mIcon); + + sb.append(", weight="); + sb.append(mWeight); + + sb.append(", timestamp="); + sb.append(mLastChangedTimestamp); + + sb.append(", intent="); + sb.append(mIntent); + + sb.append(", intentExtras="); + sb.append(secure ? "***" : mIntentPersistableExtras); + + sb.append(", extras="); + sb.append(mExtras); + + if (includeInternalData) { + sb.append(", flags="); + sb.append(mFlags); + + sb.append(", iconRes="); + sb.append(mIconResourceId); + + sb.append(", bitmapPath="); + sb.append(mBitmapPath); + } + + sb.append("}"); + return sb.toString(); + } + + /** @hide */ + public ShortcutInfo(String id, String packageName, ComponentName activityComponent, + Icon icon, String title, Intent intent, PersistableBundle intentPersistableExtras, + int weight, PersistableBundle extras, long lastChangedTimestamp, + int flags, int iconResId, String bitmapPath) { + mId = id; + mPackageName = packageName; + mActivityComponent = activityComponent; + mIcon = icon; + mTitle = title; + mIntent = intent; + mIntentPersistableExtras = intentPersistableExtras; + mWeight = weight; + mExtras = extras; + mLastChangedTimestamp = lastChangedTimestamp; + mFlags = flags; + mIconResourceId = iconResId; + mBitmapPath = bitmapPath; + } +} diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java new file mode 100644 index 000000000000..4c51d499ab4a --- /dev/null +++ b/core/java/android/content/pm/ShortcutManager.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.pm; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.List; + +/** + * TODO Enhance javadoc + * + * {@link ShortcutManager} manages shortcuts created by applications. + * + * <h3>Dynamic shortcuts and pinned shortcuts</h3> + * + * An application can publish shortcuts with {@link #setDynamicShortcuts(List)} and + * {@link #addDynamicShortcut(ShortcutInfo)}. There can be at most + * {@link #getMaxDynamicShortcutCount()} number of dynamic shortcuts at a time from the same + * application. + * A dynamic shortcut can be deleted with {@link #deleteDynamicShortcut(String)}, and apps + * can also use {@link #deleteAllDynamicShortcuts()} to delete all dynamic shortcuts. + * + * <p>The shortcuts that are currently published by the above APIs are called "dynamic", because + * they can be removed by the creator application at any time. The user may "pin" dynamic shortcuts + * on Launcher to make "pinned" shortcuts. Pinned shortcuts <b>cannot</b> be removed by the creator + * app. An application can obtain all pinned shortcuts from itself with + * {@link #getPinnedShortcuts()}. Applications should keep the pinned shortcut information + * up-to-date using {@link #updateShortcuts(List)}. + * + * <p>The number of pinned shortcuts does not affect the number of dynamic shortcuts that can be + * published by an application at a time. + * No matter how many pinned shortcuts that Launcher has for an application, the + * application can still always publish {@link #getMaxDynamicShortcutCount()} number of dynamic + * shortcuts. + * + * <h3>Shortcut IDs</h3> + * + * Each shortcut must have an ID, which must be unique within each application. When a shortcut is + * published, existing shortcuts with the same ID will be updated. Note this may include a + * pinned shortcut. + * + * <h3>Rate limiting</h3> + * + * Calls to {@link #setDynamicShortcuts(List)}, {@link #addDynamicShortcut(ShortcutInfo)}, + * and {@link #updateShortcuts(List)} will be + * rate-limited. An application can call these methods at most + * {@link #getRemainingCallCount()} times until the rate-limiting counter is reset, + * which happens at a certain time every day. + * + * <p>An applications can use {@link #getRateLimitResetTime()} to get the next reset time. + * + * <h3>Backup and Restore</h3> + * + * Shortcuts will be backed up and restored across devices. This means all information, including + * IDs, must be meaningful on a different device. + * + * TODO: Define a Broadcast to let apps update shortcuts on a restored device. + * + * <h3>APIs for launcher</h3> + * + * Launcher applications should use {@link LauncherApps} to get shortcuts that are published from + * applications. Launcher applications can also pin shortcuts with + * {@link LauncherApps#pinShortcuts(String, List, UserHandle)}. + */ +public class ShortcutManager { + private static final String TAG = "ShortcutManager"; + + private final Context mContext; + private final IShortcutService mService; + + /** + * @hide + */ + public ShortcutManager(Context context, IShortcutService service) { + mContext = context; + mService = service; + } + + /** + * Publish a list of shortcuts. All existing dynamic shortcuts from the caller application + * will be replaced. + * + * <p>This API will be rate-limited. + * + * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited. + * + * @throws IllegalArgumentException if {@code shortcutInfoList} contains more than + * {@link #getMaxDynamicShortcutCount()} shortcuts. + */ + public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) { + try { + return mService.setDynamicShortcuts(mContext.getPackageName(), + new ParceledListSlice(shortcutInfoList), injectMyUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return all dynamic shortcuts from the caller application. The number of result items + * will not exceed the value returned by {@link #getMaxDynamicShortcutCount()}. + */ + @NonNull + public List<ShortcutInfo> getDynamicShortcuts() { + try { + return mService.getDynamicShortcuts(mContext.getPackageName(), injectMyUserId()) + .getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Publish a single dynamic shortcut. If there's already dynamic or pinned shortcuts with + * the same ID, they will all be updated. + * + * <p>This API will be rate-limited. + * + * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited. + * + * @throws IllegalArgumentException if the caller application has already published the + * max number of dynamic shortcuts. + */ + public boolean addDynamicShortcut(@NonNull ShortcutInfo shortcutInfo) { + try { + return mService.addDynamicShortcut( + mContext.getPackageName(), shortcutInfo, injectMyUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Delete a single dynamic shortcut by ID. + */ + public void deleteDynamicShortcut(@NonNull String shortcutId) { + try { + mService.deleteDynamicShortcut(mContext.getPackageName(), shortcutId, injectMyUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Delete all dynamic shortcuts from the caller application. + */ + public void deleteAllDynamicShortcuts() { + try { + mService.deleteAllDynamicShortcuts(mContext.getPackageName(), injectMyUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return all pinned shortcuts from the caller application. + */ + @NonNull + public List<ShortcutInfo> getPinnedShortcuts() { + try { + return mService.getPinnedShortcuts(mContext.getPackageName(), injectMyUserId()) + .getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Update all existing shortcuts with the same IDs. Shortcuts may be pinned and/or dynamic. + * + * <p>This API will be rate-limited. + * + * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited. + */ + public boolean updateShortcuts(List<ShortcutInfo> shortcutInfoList) { + try { + return mService.updateShortcuts(mContext.getPackageName(), + new ParceledListSlice(shortcutInfoList), injectMyUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return the max number of dynamic shortcuts that each application can have at a time. + */ + public int getMaxDynamicShortcutCount() { + try { + return mService.getMaxDynamicShortcutCount(mContext.getPackageName(), injectMyUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return the number of times the caller application can call the rate-limited APIs + * before the rate limit counter is reset. + * + * @see #getRateLimitResetTime() + */ + public int getRemainingCallCount() { + try { + return mService.getRemainingCallCount(mContext.getPackageName(), injectMyUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Return when the rate limit count will be reset next time, in milliseconds since the epoch. + * + * @see #getRemainingCallCount() + * @see System#currentTimeMillis() + */ + public long getRateLimitResetTime() { + try { + return mService.getRateLimitResetTime(mContext.getPackageName(), injectMyUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide injection point */ + @VisibleForTesting + protected int injectMyUserId() { + return UserHandle.myUserId(); + } +} diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java new file mode 100644 index 000000000000..8055dd92965e --- /dev/null +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.LauncherApps.ShortcutQuery; + +import java.util.List; + +/** + * Entry points used by {@link LauncherApps}. + * + * <p>No permission / argument checks will be performed inside. + * Callers must check the calling app permission and the calling package name. + * @hide + */ +public abstract class ShortcutServiceInternal { + public interface ShortcutChangeListener { + void onShortcutChanged(@NonNull String packageName, + @NonNull List<ShortcutInfo> shortcuts, @UserIdInt int userId); + } + + public abstract List<ShortcutInfo> + getShortcuts(@NonNull String callingPackage, long changedSince, + @Nullable String packageName, @Nullable ComponentName componentName, + @ShortcutQuery.QueryFlags int flags, + int userId); + + public abstract List<ShortcutInfo> + getShortcutInfo(@NonNull String callingPackage, + @NonNull String packageName, @Nullable List<String> ids, int userId); + + public abstract void pinShortcuts(@NonNull String callingPackage, @NonNull String packageName, + @NonNull List<String> shortcutIds, int userId); + + public abstract Intent createShortcutIntent(@NonNull String callingPackage, + @NonNull ShortcutInfo shortcut, int userId); + + public abstract void addListener(@NonNull ShortcutChangeListener listener); +} diff --git a/core/java/android/content/res/GradientColor.java b/core/java/android/content/res/GradientColor.java index 98ef2eaa5e6d..cc46cbd43258 100644 --- a/core/java/android/content/res/GradientColor.java +++ b/core/java/android/content/res/GradientColor.java @@ -17,6 +17,7 @@ package android.content.res; import android.annotation.ColorInt; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Resources.Theme; @@ -37,13 +38,48 @@ import android.util.Log; import android.util.Xml; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; - +/** + * Lets you define a gradient color, which is used inside + * {@link android.graphics.drawable.VectorDrawable}. + * + * {@link android.content.res.GradientColor}s are created from XML resource files defined in the + * "color" subdirectory directory of an application's resource directory. The XML file contains + * a single "gradient" element with a number of attributes and elements inside. For example: + * <pre> + * <gradient xmlns:android="http://schemas.android.com/apk/res/android"> + * <android:startColor="?android:attr/colorPrimary"/> + * <android:endColor="?android:attr/colorControlActivated"/> + * <.../> + * <android:type="linear"/> + * </gradient> + * </pre> + * + * This can describe either a {@link android.graphics.LinearGradient}, + * {@link android.graphics.RadialGradient}, or {@link android.graphics.SweepGradient}. + * + * Note that different attributes are relevant for different types of gradient. + * For example, android:gradientRadius is only applied to RadialGradient. + * androd:centerX and android:centerY are only applied to SweepGradient or RadialGradient. + * android:startX, android:startY, android:endX and android:endY are only applied to LinearGradient. + * + * Also note if any color "item" element is defined, then startColor, centerColor and endColor will + * be ignored. + */ public class GradientColor extends ComplexColor { private static final String TAG = "GradientColor"; private static final boolean DBG_GRADIENT = false; + @IntDef({TILE_MODE_CLAMP, TILE_MODE_REPEAT, TILE_MODE_MIRROR}) + @Retention(RetentionPolicy.SOURCE) + private @interface GradientTileMode {} + private static final int TILE_MODE_CLAMP = 0; + private static final int TILE_MODE_REPEAT = 1; + private static final int TILE_MODE_MIRROR = 2; + /** Lazily-created factory for this GradientColor. */ private GradientColorFactory mFactory; @@ -54,7 +90,8 @@ public class GradientColor extends ComplexColor { // all the XML information. private Shader mShader = null; - // Below are the attributes at the root element <gradient> + // Below are the attributes at the root element <gradient>. + // NOTE: they need to be copied in the copy constructor! private int mGradientType = GradientDrawable.LINEAR_GRADIENT; private float mCenterX = 0f; @@ -70,6 +107,8 @@ public class GradientColor extends ComplexColor { private int mEndColor = 0; private boolean mHasCenterColor = false; + private int mTileMode = 0; // Clamp mode. + private float mGradientRadius = 0f; // Below are the attributes for the <item> element. @@ -100,6 +139,7 @@ public class GradientColor extends ComplexColor { mEndColor = copy.mEndColor; mHasCenterColor = copy.mHasCenterColor; mGradientRadius = copy.mGradientRadius; + mTileMode = copy.mTileMode; if (copy.mItemColors != null) { mItemColors = copy.mItemColors.clone(); @@ -117,6 +157,20 @@ public class GradientColor extends ComplexColor { } } + // Set the default to clamp mode. + private static Shader.TileMode parseTileMode(@GradientTileMode int tileMode) { + switch (tileMode) { + case TILE_MODE_CLAMP: + return Shader.TileMode.CLAMP; + case TILE_MODE_REPEAT: + return Shader.TileMode.REPEAT; + case TILE_MODE_MIRROR: + return Shader.TileMode.MIRROR; + default: + return Shader.TileMode.CLAMP; + } + } + /** * Update the root level's attributes, either for inflate or applyTheme. */ @@ -150,6 +204,9 @@ public class GradientColor extends ComplexColor { mEndColor = a.getColor( R.styleable.GradientColor_endColor, mEndColor); + mTileMode = a.getInt( + R.styleable.GradientColor_tileMode, mTileMode); + if (DBG_GRADIENT) { Log.v(TAG, "hasCenterColor is " + mHasCenterColor); if (mHasCenterColor) { @@ -157,6 +214,7 @@ public class GradientColor extends ComplexColor { } Log.v(TAG, "startColor: " + mStartColor); Log.v(TAG, "endColor: " + mEndColor); + Log.v(TAG, "tileMode: " + mTileMode); } mGradientRadius = a.getFloat(R.styleable.GradientColor_gradientRadius, @@ -406,11 +464,11 @@ public class GradientColor extends ComplexColor { if (mGradientType == GradientDrawable.LINEAR_GRADIENT) { mShader = new LinearGradient(mStartX, mStartY, mEndX, mEndY, tempColors, tempOffsets, - Shader.TileMode.CLAMP); + parseTileMode(mTileMode)); } else { if (mGradientType == GradientDrawable.RADIAL_GRADIENT) { mShader = new RadialGradient(mCenterX, mCenterY, mGradientRadius, tempColors, - tempOffsets, Shader.TileMode.CLAMP); + tempOffsets, parseTileMode(mTileMode)); } else { mShader = new SweepGradient(mCenterX, mCenterY, tempColors, tempOffsets); } diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index ee7bd9acec77..5c7137341cd3 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -235,6 +235,12 @@ public class BaseBundle { return mParcelledData != null; } + /** @hide */ + ArrayMap<String, Object> getMap() { + unparcel(); + return mMap; + } + /** * Returns the number of mappings contained in this Bundle. * diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java index ea180b216c63..f36bb2989c72 100644 --- a/core/java/android/os/PersistableBundle.java +++ b/core/java/android/os/PersistableBundle.java @@ -24,9 +24,6 @@ import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.IOException; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; /** * A mapping from String values to various types that can be saved to persistent and later @@ -82,6 +79,18 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa super(b); } + + /** + * Constructs a PersistableBundle from a Bundle. + * + * @param b a Bundle to be copied. + * + * @throws IllegalArgumentException if any element of {@code b} cannot be persisted. + */ + public PersistableBundle(Bundle b) { + this(b.getMap()); + } + /** * Constructs a PersistableBundle containing the mappings passed in. * @@ -101,6 +110,8 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa if (value instanceof ArrayMap) { // Fix up any Maps by replacing them with PersistableBundles. mMap.setValueAt(i, new PersistableBundle((ArrayMap<String, Object>) value)); + } else if (value instanceof Bundle) { + mMap.setValueAt(i, new PersistableBundle(((Bundle) value))); } else if (!isValidType(value)) { throw new IllegalArgumentException("Bad value in PersistableBundle key=" + mMap.keyAt(i) + " value=" + value); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a40cf963751e..d43ff4e04bcb 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1254,6 +1254,19 @@ public final class Settings { public static final String ACTION_SHOW_REMOTE_BUGREPORT_DIALOG = "android.settings.SHOW_REMOTE_BUGREPORT_DIALOG"; + /** + * Activity Action: Show VR listener settings. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + * + * @see android.service.vr.VrListenerService + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_VR_LISTENER_SETTINGS + = "android.settings.VR_LISTENER_SETTINGS"; + // End of Intent actions for Settings /** @@ -5854,15 +5867,6 @@ public final class Settings { public static final String ASSIST_SCREENSHOT_ENABLED = "assist_screenshot_enabled"; /** - * Names of the service component that the current user has explicitly allowed to - * see and change the importance of all of the user's notifications. - * - * @hide - */ - public static final String ENABLED_NOTIFICATION_ASSISTANT - = "enabled_notification_assistant"; - - /** * Names of the service components that the current user has explicitly allowed to * see all of the user's notifications, separated by ':'. * @@ -6041,6 +6045,14 @@ public final class Settings { public static final String BRIGHTNESS_USE_TWILIGHT = "brightness_use_twilight"; /** + * Names of the service components that the current user has explicitly allowed to + * be a VR mode listener, separated by ':'. + * + * @hide + */ + public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners"; + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear @@ -6067,6 +6079,7 @@ public final class Settings { BACKUP_AUTO_RESTORE, ENABLED_ACCESSIBILITY_SERVICES, ENABLED_NOTIFICATION_LISTENERS, + ENABLED_VR_LISTENERS, ENABLED_INPUT_METHODS, TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, TOUCH_EXPLORATION_ENABLED, @@ -8260,6 +8273,13 @@ public final class Settings { "allow_user_switching_when_system_user_locked"; /** + * Boot count since the device starts running APK level 24. + * <p> + * Type: int + */ + public static final String BOOT_COUNT = "boot_count"; + + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. * diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java index 11737c65314e..0163b47f2ba7 100644 --- a/core/java/android/service/notification/Condition.java +++ b/core/java/android/service/notification/Condition.java @@ -16,32 +16,56 @@ package android.service.notification; +import android.annotation.IntDef; import android.annotation.SystemApi; +import android.app.AutomaticZenRule; import android.content.Context; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** - * Condition information from condition providers. Used to tell the system to enter Do Not Disturb - * mode and request that the system exit Do Not Disturb mode. + * The current condition of an {@link android.app.AutomaticZenRule}, provided by the + * {@link ConditionProviderService} that owns the rule. Used to tell the system to enter Do Not + * Disturb mode and request that the system exit Do Not Disturb mode. */ public class Condition implements Parcelable { + @SystemApi public static final String SCHEME = "condition"; + /** @hide */ + @IntDef({STATE_FALSE, STATE_TRUE, STATE_TRUE, STATE_ERROR}) + @Retention(RetentionPolicy.SOURCE) + public @interface State {} + + /** + * Indicates that Do Not Disturb should be turned off. Note that all Conditions from all + * {@link ConditionProviderService} providers must be off for Do Not Disturb to be turned off on + * the device. + */ public static final int STATE_FALSE = 0; + /** + * Indicates that Do Not Disturb should be turned on. + */ public static final int STATE_TRUE = 1; + + @SystemApi public static final int STATE_UNKNOWN = 2; + @SystemApi public static final int STATE_ERROR = 3; + @SystemApi public static final int FLAG_RELEVANT_NOW = 1 << 0; + @SystemApi public static final int FLAG_RELEVANT_ALWAYS = 1 << 1; /** - * The URI representing the condition being updated. + * The URI representing the rule being updated. * See {@link android.app.AutomaticZenRule#getConditionId()}. */ public final Uri id; @@ -52,23 +76,17 @@ public class Condition implements Parcelable { */ public final String summary; - /** - * Additional information about what the rule encoded in {@link #id} means when it is enabled. - * User visible if the state of the condition is {@link #STATE_TRUE}. - */ + @SystemApi public final String line1; - - /** - * Additional information about what the rule encoded in {@link #id} means when it is enabled. - * User visible if the state of the condition is {@link #STATE_TRUE}. - */ + @SystemApi public final String line2; /** - * The state of this condition. {@link #STATE_TRUE} will enable Do Not Disturb mode. Any other - * state will turn Do Not Disturb off for this rule. Note that Do Not Disturb might still be - * enabled globally if other conditions are in a {@link #STATE_TRUE} state. + * The state of this condition. {@link #STATE_TRUE} will enable Do Not Disturb mode. + * {@link #STATE_FALSE} will turn Do Not Disturb off for this rule. Note that Do Not Disturb + * might still be enabled globally if other conditions are in a {@link #STATE_TRUE} state. */ + @State public final int state; @SystemApi @@ -76,8 +94,13 @@ public class Condition implements Parcelable { @SystemApi public final int icon; - public Condition(Uri id, String summary, String line1, String line2, int state) { - this(id, summary, line1, line2, -1, state, FLAG_RELEVANT_ALWAYS); + /** + * An object representing the current state of a {@link android.app.AutomaticZenRule}. + * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule + * @param summary a user visible description of the rule state. + */ + public Condition(Uri id, String summary, int state) { + this(id, summary, "", "", -1, state, FLAG_RELEVANT_ALWAYS); } @SystemApi @@ -85,8 +108,6 @@ public class Condition implements Parcelable { int state, int flags) { if (id == null) throw new IllegalArgumentException("id is required"); if (summary == null) throw new IllegalArgumentException("summary is required"); - if (line1 == null) throw new IllegalArgumentException("line1 is required"); - if (line2 == null) throw new IllegalArgumentException("line2 is required"); if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state); this.id = id; this.summary = summary; @@ -97,7 +118,7 @@ public class Condition implements Parcelable { this.flags = flags; } - private Condition(Parcel source) { + public Condition(Parcel source) { this((Uri)source.readParcelable(Condition.class.getClassLoader()), source.readString(), source.readString(), @@ -135,6 +156,7 @@ public class Condition implements Parcelable { .append(']').toString(); } + @SystemApi public static String stateToString(int state) { if (state == STATE_FALSE) return "STATE_FALSE"; if (state == STATE_TRUE) return "STATE_TRUE"; @@ -143,6 +165,7 @@ public class Condition implements Parcelable { throw new IllegalArgumentException("state is invalid: " + state); } + @SystemApi public static String relevanceToString(int flags) { final boolean now = (flags & FLAG_RELEVANT_NOW) != 0; final boolean always = (flags & FLAG_RELEVANT_ALWAYS) != 0; @@ -175,6 +198,7 @@ public class Condition implements Parcelable { return 0; } + @SystemApi public Condition copy() { final Parcel parcel = Parcel.obtain(); try { @@ -186,10 +210,14 @@ public class Condition implements Parcelable { } } + @SystemApi public static Uri.Builder newId(Context context) { - return new Uri.Builder().scheme(SCHEME).authority(context.getPackageName()); + return new Uri.Builder() + .scheme(Condition.SCHEME) + .authority(context.getPackageName()); } + @SystemApi public static boolean isValidId(Uri id, String pkg) { return id != null && SCHEME.equals(id.getScheme()) && pkg.equals(id.getAuthority()); } diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java index adcc9d688cda..44c3887ec35d 100644 --- a/core/java/android/service/notification/ConditionProviderService.java +++ b/core/java/android/service/notification/ConditionProviderService.java @@ -102,9 +102,6 @@ public abstract class ConditionProviderService extends Service { */ abstract public void onConnected(); - /** - * @removed - */ @SystemApi public void onRequestConditions(int relevance) {} diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl index a0de17f4b7cf..8c359012bc51 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -23,7 +23,7 @@ import android.service.notification.NotificationRankingUpdate; /** @hide */ oneway interface INotificationListener { - // listeners and assistants + // listeners and rankers void onListenerConnected(in NotificationRankingUpdate update); void onNotificationPosted(in IStatusBarNotificationHolder notificationHolder, in NotificationRankingUpdate update); @@ -33,7 +33,7 @@ oneway interface INotificationListener void onListenerHintsChanged(int hints); void onInterruptionFilterChanged(int interruptionFilter); - // assistants only + // rankers only void onNotificationEnqueued(in IStatusBarNotificationHolder notificationHolder, int importance, boolean user); void onNotificationVisibilityChanged(String key, long time, boolean visible); void onNotificationClick(String key, long time); diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 73a890fb0308..a8b8da102e36 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -69,16 +69,6 @@ import java.util.List; * <action android:name="android.service.notification.NotificationListenerService" /> * </intent-filter> * </service></pre> - * <p> Typically, while enabled in user settings, this service will be bound on boot or when a - * settings change occurs that could affect whether this service should run. However, for some - * system usage modes, the you may instead specify that this service is instead bound and unbound - * in response to mode changes by including a category in the intent filter. Currently - * supported categories are: - * <ul> - * <li>{@link #CATEGORY_VR_NOTIFICATIONS} - this service is bound when an Activity has enabled - * VR mode. {@see android.app.Activity#setVrMode(boolean)}.</li> - * </ul> - * </p> */ public abstract class NotificationListenerService extends Service { // TAG = "NotificationListenerService[MySubclass]" @@ -180,13 +170,18 @@ public abstract class NotificationListenerService extends Service { private INotificationManager mNoMan; - /** Only valid after a successful call to (@link registerAsService}. */ - private int mCurrentUser; - + /** + * Only valid after a successful call to (@link registerAsService}. + * @hide + */ + protected int mCurrentUser; - // This context is required for system services since NotificationListenerService isn't - // started as a real Service and hence no context is available. - private Context mSystemContext; + /** + * This context is required for system services since NotificationListenerService isn't + * started as a real Service and hence no context is available.. + * @hide + */ + protected Context mSystemContext; /** * The {@link Intent} that must be declared as handled by the service. @@ -195,17 +190,6 @@ public abstract class NotificationListenerService extends Service { public static final String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService"; - /** - * If this category is declared in the application manifest for a service of this type, this - * service will be bound when VR mode is enabled, and unbound when VR mode is disabled rather - * than the normal lifecycle for a notification service. - * - * {@see android.app.Activity#setVrMode(boolean)} - */ - @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY) - public static final String CATEGORY_VR_NOTIFICATIONS = - "android.intent.category.vr.notifications"; - @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); @@ -696,12 +680,18 @@ public abstract class NotificationListenerService extends Service { @SystemApi public void registerAsSystemService(Context context, ComponentName componentName, int currentUser) throws RemoteException { + registerAsSystemServiceImpl(context, componentName, currentUser, false /* asRanker */); + } + + /** @hide */ + protected void registerAsSystemServiceImpl(Context context, ComponentName componentName, + int currentUser, boolean asRanker) throws RemoteException { mSystemContext = context; if (mWrapper == null) { mWrapper = new NotificationListenerWrapper(); } INotificationManager noMan = getNotificationInterface(); - noMan.registerListener(mWrapper, componentName, currentUser); + noMan.registerListener(mWrapper, componentName, currentUser, asRanker); mCurrentUser = currentUser; mHandler = new MyHandler(context.getMainLooper()); } diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationRankerService.java index 41af837a1dbc..520b4c225dfd 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationRankerService.java @@ -31,31 +31,20 @@ import android.util.Log; import com.android.internal.os.SomeArgs; /** - * A service that helps the user manage notifications by modifying the - * relative importance of notifications. - * <p>To extend this class, you must declare the service in your manifest file with - * the {@link android.Manifest.permission#BIND_NOTIFICATION_ASSISTANT_SERVICE} permission - * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p> - * <pre> - * <service android:name=".NotificationAssistant" - * android:label="@string/service_name" - * android:permission="android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE"> - * <intent-filter> - * <action android:name="android.service.notification.NotificationAssistantService" /> - * </intent-filter> - * </service></pre> + * A service that helps the user manage notifications. This class is only used to + * extend the framework service and may not be implemented by non-framework components. * @hide */ @SystemApi -public abstract class NotificationAssistantService extends NotificationListenerService { - private static final String TAG = "NotificationAssistant"; +public abstract class NotificationRankerService extends NotificationListenerService { + private static final String TAG = "NotificationRanker"; /** * The {@link Intent} that must be declared as handled by the service. */ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE - = "android.service.notification.NotificationAssistantService"; + = "android.service.notification.NotificationRankerService"; /** Notification was canceled by the status bar reporting a click. */ public static final int REASON_DELEGATE_CLICK = 1; @@ -130,7 +119,7 @@ public abstract class NotificationAssistantService extends NotificationListenerS @Override public void registerAsSystemService(Context context, ComponentName componentName, int currentUser) throws RemoteException { - super.registerAsSystemService(context, componentName, currentUser); + registerAsSystemServiceImpl(context, componentName, currentUser, true /* as Ranker */); mHandler = new MyHandler(getContext().getMainLooper()); } @@ -143,7 +132,7 @@ public abstract class NotificationAssistantService extends NotificationListenerS @Override public final IBinder onBind(Intent intent) { if (mWrapper == null) { - mWrapper = new NotificationAssistantWrapper(); + mWrapper = new NotificationRankingServiceWrapper(); } return mWrapper; } @@ -216,14 +205,14 @@ public abstract class NotificationAssistantService extends NotificationListenerS public final void adjustImportance(String key, Adjustment adjustment) { if (!isBound()) return; try { - getNotificationInterface().setImportanceFromAssistant(mWrapper, key, + getNotificationInterface().setImportanceFromRankerService(mWrapper, key, adjustment.mImportance, adjustment.mExplanation); } catch (android.os.RemoteException ex) { Log.v(TAG, "Unable to contact notification manager", ex); } } - private class NotificationAssistantWrapper extends NotificationListenerWrapper { + private class NotificationRankingServiceWrapper extends NotificationListenerWrapper { @Override public void onNotificationEnqueued(IStatusBarNotificationHolder sbnHolder, int importance, boolean user) { diff --git a/core/java/android/service/vr/IVrListener.aidl b/core/java/android/service/vr/IVrListener.aidl new file mode 100644 index 000000000000..b7273ba91187 --- /dev/null +++ b/core/java/android/service/vr/IVrListener.aidl @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.vr; + +/** @hide */ +oneway interface IVrListener { + +}
\ No newline at end of file diff --git a/core/java/android/service/vr/VrListenerService.java b/core/java/android/service/vr/VrListenerService.java new file mode 100644 index 000000000000..5f1f659710a7 --- /dev/null +++ b/core/java/android/service/vr/VrListenerService.java @@ -0,0 +1,88 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.vr; + +import android.annotation.NonNull; +import android.annotation.SdkConstant; +import android.app.ActivityManager; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; + +/** + * A service that is bound from the system while running in virtual reality (VR) mode. + * + * <p>To extend this class, you must declare the service in your manifest file with + * the {@link android.Manifest.permission#BIND_VR_LISTENER_SERVICE} permission + * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p> + * <pre> + * <service android:name=".VrListener" + * android:label="@string/service_name" + * android:permission="android.permission.BIND_VR_LISTENER_SERVICE"> + * <intent-filter> + * <action android:name="android.service.vr.VrListenerService" /> + * </intent-filter> + * </service> + * </pre> + * + * <p> + * This service is bound when the system enters VR mode and is unbound when the system leaves VR + * mode. + * {@see android.app.Activity#setVrMode(boolean)} + * </p> + */ +public abstract class VrListenerService extends Service { + + /** + * The {@link Intent} that must be declared as handled by the service. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = "android.service.vr.VrListenerService"; + + /** + * @hide + */ + public static class VrListenerBinder extends IVrListener.Stub { + } + + private final VrListenerBinder mBinder = new VrListenerBinder(); + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + /** + * Check if the given package is available to be enabled/disabled in VR mode settings. + * + * @param context the {@link Context} to use for looking up the requested component. + * @param requestedComponent the name of the component that implements + * {@link android.service.vr.VrListenerService} to check. + * + * @return {@code true} if this package is enabled in settings. + */ + public static final boolean isVrModePackageEnabled(@NonNull Context context, + @NonNull ComponentName requestedComponent) { + ActivityManager am = context.getSystemService(ActivityManager.class); + if (am == null) { + return false; + } + return am.isVrModePackageEnabled(requestedComponent); + } +} diff --git a/core/java/android/transition/Slide.java b/core/java/android/transition/Slide.java index 9af65e403679..2645f861a5ad 100644 --- a/core/java/android/transition/Slide.java +++ b/core/java/android/transition/Slide.java @@ -47,6 +47,7 @@ public class Slide extends Visibility { private static final String PROPNAME_SCREEN_POSITION = "android:slide:screenPosition"; private CalculateSlide mSlideCalculator = sCalculateBottom; private @GravityFlag int mSlideEdge = Gravity.BOTTOM; + private float mSlideFraction = 1; /** @hide */ @Retention(RetentionPolicy.SOURCE) @@ -56,16 +57,16 @@ public class Slide extends Visibility { private interface CalculateSlide { /** Returns the translation value for view when it goes out of the scene */ - float getGoneX(ViewGroup sceneRoot, View view); + float getGoneX(ViewGroup sceneRoot, View view, float fraction); /** Returns the translation value for view when it goes out of the scene */ - float getGoneY(ViewGroup sceneRoot, View view); + float getGoneY(ViewGroup sceneRoot, View view, float fraction); } private static abstract class CalculateSlideHorizontal implements CalculateSlide { @Override - public float getGoneY(ViewGroup sceneRoot, View view) { + public float getGoneY(ViewGroup sceneRoot, View view, float fraction) { return view.getTranslationY(); } } @@ -73,27 +74,27 @@ public class Slide extends Visibility { private static abstract class CalculateSlideVertical implements CalculateSlide { @Override - public float getGoneX(ViewGroup sceneRoot, View view) { + public float getGoneX(ViewGroup sceneRoot, View view, float fraction) { return view.getTranslationX(); } } private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() { @Override - public float getGoneX(ViewGroup sceneRoot, View view) { - return view.getTranslationX() - sceneRoot.getWidth(); + public float getGoneX(ViewGroup sceneRoot, View view, float fraction) { + return view.getTranslationX() - sceneRoot.getWidth() * fraction; } }; private static final CalculateSlide sCalculateStart = new CalculateSlideHorizontal() { @Override - public float getGoneX(ViewGroup sceneRoot, View view) { + public float getGoneX(ViewGroup sceneRoot, View view, float fraction) { final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; final float x; if (isRtl) { - x = view.getTranslationX() + sceneRoot.getWidth(); + x = view.getTranslationX() + sceneRoot.getWidth() * fraction; } else { - x = view.getTranslationX() - sceneRoot.getWidth(); + x = view.getTranslationX() - sceneRoot.getWidth() * fraction; } return x; } @@ -101,27 +102,27 @@ public class Slide extends Visibility { private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() { @Override - public float getGoneY(ViewGroup sceneRoot, View view) { - return view.getTranslationY() - sceneRoot.getHeight(); + public float getGoneY(ViewGroup sceneRoot, View view, float fraction) { + return view.getTranslationY() - sceneRoot.getHeight() * fraction; } }; private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() { @Override - public float getGoneX(ViewGroup sceneRoot, View view) { - return view.getTranslationX() + sceneRoot.getWidth(); + public float getGoneX(ViewGroup sceneRoot, View view, float fraction) { + return view.getTranslationX() + sceneRoot.getWidth() * fraction; } }; private static final CalculateSlide sCalculateEnd = new CalculateSlideHorizontal() { @Override - public float getGoneX(ViewGroup sceneRoot, View view) { + public float getGoneX(ViewGroup sceneRoot, View view, float fraction) { final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; final float x; if (isRtl) { - x = view.getTranslationX() - sceneRoot.getWidth(); + x = view.getTranslationX() - sceneRoot.getWidth() * fraction; } else { - x = view.getTranslationX() + sceneRoot.getWidth(); + x = view.getTranslationX() + sceneRoot.getWidth() * fraction; } return x; } @@ -129,8 +130,8 @@ public class Slide extends Visibility { private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() { @Override - public float getGoneY(ViewGroup sceneRoot, View view) { - return view.getTranslationY() + sceneRoot.getHeight(); + public float getGoneY(ViewGroup sceneRoot, View view, float fraction) { + return view.getTranslationY() + sceneRoot.getHeight() * fraction; } }; @@ -237,8 +238,8 @@ public class Slide extends Visibility { int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION); float endX = view.getTranslationX(); float endY = view.getTranslationY(); - float startX = mSlideCalculator.getGoneX(sceneRoot, view); - float startY = mSlideCalculator.getGoneY(sceneRoot, view); + float startX = mSlideCalculator.getGoneX(sceneRoot, view, mSlideFraction); + float startY = mSlideCalculator.getGoneY(sceneRoot, view, mSlideFraction); return TranslationAnimationCreator .createAnimation(view, endValues, position[0], position[1], startX, startY, endX, endY, sDecelerate, this); @@ -253,10 +254,15 @@ public class Slide extends Visibility { int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION); float startX = view.getTranslationX(); float startY = view.getTranslationY(); - float endX = mSlideCalculator.getGoneX(sceneRoot, view); - float endY = mSlideCalculator.getGoneY(sceneRoot, view); + float endX = mSlideCalculator.getGoneX(sceneRoot, view, mSlideFraction); + float endY = mSlideCalculator.getGoneY(sceneRoot, view, mSlideFraction); return TranslationAnimationCreator .createAnimation(view, startValues, position[0], position[1], startX, startY, endX, endY, sAccelerate, this); } + + /** @hide */ + public void setSlideFraction(float slideFraction) { + mSlideFraction = slideFraction; + } } diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index b9a7421fe14c..cff9d8e93c98 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -35,7 +35,6 @@ import java.util.ArrayList; public class NotificationHeaderView extends ViewGroup { public static final int NO_COLOR = -1; private final int mChildMinWidth; - private final int mExpandTopPadding; private final int mContentEndMargin; private View mAppName; private View mSubTextView; @@ -43,12 +42,10 @@ public class NotificationHeaderView extends ViewGroup { private HeaderTouchListener mTouchListener = new HeaderTouchListener(); private ImageView mExpandButton; private View mIcon; - private TextView mChildCount; private View mProfileBadge; private View mInfo; private int mIconColor; private int mOriginalNotificationColor; - private boolean mGroupHeader; private boolean mExpanded; private boolean mShowWorkBadgeAtEnd; @@ -70,7 +67,6 @@ public class NotificationHeaderView extends ViewGroup { com.android.internal.R.dimen.notification_header_shrink_min_width); mContentEndMargin = getResources().getDimensionPixelSize( com.android.internal.R.dimen.notification_content_margin_end); - mExpandTopPadding = (int) (1 * getResources().getDisplayMetrics().density); } @Override @@ -80,7 +76,6 @@ public class NotificationHeaderView extends ViewGroup { mSubTextView = findViewById(com.android.internal.R.id.header_sub_text); mExpandButton = (ImageView) findViewById(com.android.internal.R.id.expand_button); mIcon = findViewById(com.android.internal.R.id.icon); - mChildCount = (TextView) findViewById(com.android.internal.R.id.number_of_children); mProfileBadge = findViewById(com.android.internal.R.id.profile_badge); mInfo = findViewById(com.android.internal.R.id.header_content_info); } @@ -193,17 +188,6 @@ public class NotificationHeaderView extends ViewGroup { updateTouchListener(); } - public void setChildCount(int childCount) { - if (childCount > 0) { - mChildCount.setText(getContext().getString( - com.android.internal.R.string.notification_children_count_bracketed, - childCount)); - mChildCount.setVisibility(VISIBLE); - } else { - mChildCount.setVisibility(GONE); - } - } - @RemotableViewMethod public void setOriginalIconColor(int color) { mIconColor = color; @@ -222,11 +206,6 @@ public class NotificationHeaderView extends ViewGroup { return mOriginalNotificationColor; } - public void setIsGroupHeader(boolean isGroupHeader) { - mGroupHeader = isGroupHeader; - updateExpandButton(); - } - @RemotableViewMethod public void setExpanded(boolean expanded) { mExpanded = expanded; @@ -235,24 +214,13 @@ public class NotificationHeaderView extends ViewGroup { private void updateExpandButton() { int drawableId; - int paddingTop = 0; - if (mGroupHeader) { - if (mExpanded) { - drawableId = com.android.internal.R.drawable.ic_collapse_bundle; - } else { - drawableId =com.android.internal.R.drawable.ic_expand_bundle; - } + if (mExpanded) { + drawableId = com.android.internal.R.drawable.ic_collapse_notification; } else { - if (mExpanded) { - drawableId = com.android.internal.R.drawable.ic_collapse_notification; - } else { - drawableId = com.android.internal.R.drawable.ic_expand_notification; - } - paddingTop = mExpandTopPadding; + drawableId = com.android.internal.R.drawable.ic_expand_notification; } mExpandButton.setImageDrawable(getContext().getDrawable(drawableId)); mExpandButton.setColorFilter(mOriginalNotificationColor); - mExpandButton.setPadding(0, paddingTop, 0, 0); } public void setShowWorkBadgeAtEnd(boolean showWorkBadgeAtEnd) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index afe2f1061f3a..f7405e2db304 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1078,8 +1078,7 @@ public final class ViewRootImpl implements ViewParent, scheduleTraversals(); } else { if (mAttachInfo.mHardwareRenderer != null) { - // TODO: Temporary to help track down b/27286867 - Log.d(mTag, "WindowStopped on " + getTitle()); + if (DEBUG_DRAW) Log.d(mTag, "WindowStopped on " + getTitle()); mAttachInfo.mHardwareRenderer.updateSurface(null); mAttachInfo.mHardwareRenderer.destroyHardwareResources(mView); } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 0e34067f3ab2..406dc2b2baa9 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -215,7 +215,7 @@ public class Editor { boolean mInBatchEditControllers; boolean mShowSoftInputOnFocus = true; - private boolean mPreserveDetachedSelection; + private boolean mPreserveSelection; boolean mTemporaryDetach; boolean mIsBeingLongClicked; @@ -351,9 +351,14 @@ public class Editor { } void replace() { + if (mSuggestionsPopupWindow == null) { + mSuggestionsPopupWindow = new SuggestionsPopupWindow(); + } + hideCursorAndSpanControllers(); + mSuggestionsPopupWindow.show(); + int middle = (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2; Selection.setSelection((Spannable) mTextView.getText(), middle); - showSuggestions(); } void onAttachedToWindow() { @@ -376,13 +381,7 @@ public class Editor { updateSpellCheckSpans(0, mTextView.getText().length(), true /* create the spell checker if needed */); - if (mTextView.hasTransientState() && - mTextView.getSelectionStart() != mTextView.getSelectionEnd()) { - // Since transient state is reference counted make sure it stays matched - // with our own calls to it for managing selection. - // The action mode callback will set this back again when/if the action mode starts. - mTextView.setHasTransientState(false); - + if (mTextView.getSelectionStart() != mTextView.getSelectionEnd()) { // We had an active selection from before, start the selection mode. startSelectionActionMode(); } @@ -2092,7 +2091,7 @@ public class Editor { mShowSuggestionRunnable = new Runnable() { public void run() { - showSuggestions(); + replace(); } }; // removeCallbacks is performed on every touch @@ -2113,9 +2112,9 @@ public class Editor { } private void stopTextActionModeWithPreservingSelection() { - mPreserveDetachedSelection = true; + mPreserveSelection = true; stopTextActionMode(); - mPreserveDetachedSelection = false; + mPreserveSelection = false; } /** @@ -2233,15 +2232,6 @@ public class Editor { mCorrectionHighlighter.highlight(info); } - void showSuggestions() { - if (mSuggestionsPopupWindow == null) { - mSuggestionsPopupWindow = new SuggestionsPopupWindow(); - } - hideCursorAndSpanControllers(); - stopTextActionMode(); - mSuggestionsPopupWindow.show(); - } - void onScrollChanged() { if (mPositionListener != null) { mPositionListener.onScrollChanged(); @@ -2515,7 +2505,7 @@ public class Editor { .setEnabled(mTextView.canSelectAllText()) .setOnMenuItemClickListener(mOnContextMenuItemClickListener); - mPreserveDetachedSelection = true; + mPreserveSelection = true; } private void replaceWithSuggestion(final SuggestionInfo suggestionInfo) { @@ -3426,7 +3416,7 @@ public class Editor { @Override protected int getTextOffset() { - return mTextView.getSelectionStart(); + return (mTextView.getSelectionStart() + mTextView.getSelectionStart()) / 2; } @Override @@ -3590,7 +3580,6 @@ public class Editor { } if (menu.hasVisibleItems() || mode.getCustomView() != null) { - mTextView.setHasTransientState(true); return true; } else { return false; @@ -3690,15 +3679,14 @@ public class Editor { customCallback.onDestroyActionMode(mode); } - /* - * If we're ending this mode because we're detaching from a window, - * we still have selection state to preserve. Don't clear it, we'll - * bring back the selection mode when (if) we get reattached. - */ - if (!mPreserveDetachedSelection) { + if (!mPreserveSelection) { + /* + * Leave current selection when we tentatively destroy action mode for the + * selection. If we're detaching from a window, we'll bring back the selection + * mode when (if) we get reattached. + */ Selection.setSelection((Spannable) mTextView.getText(), mTextView.getSelectionEnd()); - mTextView.setHasTransientState(false); } if (mSelectionModifierCursorController != null) { @@ -6037,7 +6025,7 @@ public class Editor { private boolean fireIntent(Intent intent) { if (intent != null && Intent.ACTION_PROCESS_TEXT.equals(intent.getAction())) { intent.putExtra(Intent.EXTRA_PROCESS_TEXT, mTextView.getSelectedText()); - mEditor.mPreserveDetachedSelection = true; + mEditor.mPreserveSelection = true; mTextView.startActivityForResult(intent, TextView.PROCESS_TEXT_REQUEST_CODE); return true; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 73f8fdcb4a92..ac3eaf7df334 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8286,6 +8286,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (newSelEnd < 0) { newSelEnd = Selection.getSelectionEnd(buf); } + + if (newSelStart == newSelEnd && hasTransientState()) { + setHasTransientState(false); + } else if (newSelStart != newSelEnd && !hasTransientState()) { + setHasTransientState(true); + } + if (mEditor != null) { mEditor.refreshTextActionMode(); } diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index 2ea225f99068..ea628992b5ab 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -21,6 +21,7 @@ import android.app.FragmentTransaction; import android.app.ListFragment; import android.content.Context; import android.os.Bundle; +import android.text.TextUtils; import android.util.LocaleList; import android.view.Menu; import android.view.MenuInflater; @@ -50,6 +51,11 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O private Set<LocaleStore.LocaleInfo> mLocaleList; private LocaleStore.LocaleInfo mParentLocale; private boolean mTranslatedOnly = false; + private SearchView mSearchView = null; + private CharSequence mPreviousSearch = null; + private boolean mPreviousSearchHadFocus = false; + private int mFirstVisiblePosition = 0; + private int mTopDistance = 0; /** * Other classes can register to be notified when a locale was selected. @@ -154,15 +160,35 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O super.onResume(); if (mParentLocale != null) { - this.getActivity().setTitle(mParentLocale.getFullNameNative()); + getActivity().setTitle(mParentLocale.getFullNameNative()); } else { - this.getActivity().setTitle(R.string.language_selection_title); + getActivity().setTitle(R.string.language_selection_title); } getListView().requestFocus(); } @Override + public void onPause() { + super.onPause(); + + // Save search status + if (mSearchView != null) { + mPreviousSearchHadFocus = mSearchView.hasFocus(); + mPreviousSearch = mSearchView.getQuery(); + } else { + mPreviousSearchHadFocus = false; + mPreviousSearch = null; + } + + // Save scroll position + final ListView list = getListView(); + final View firstChild = list.getChildAt(0); + mFirstVisiblePosition = list.getFirstVisiblePosition(); + mTopDistance = (firstChild == null) ? 0 : (firstChild.getTop() - list.getPaddingTop()); + } + + @Override public void onListItemClick(ListView l, View v, int position, long id) { final LocaleStore.LocaleInfo locale = (LocaleStore.LocaleInfo) getListAdapter().getItem(position); @@ -193,12 +219,27 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O if (mParentLocale == null) { inflater.inflate(R.menu.language_selection_list, menu); - MenuItem mSearchMenuItem = menu.findItem(R.id.locale_search_menu); - SearchView mSearchView = (SearchView) mSearchMenuItem.getActionView(); + final MenuItem searchMenuItem = menu.findItem(R.id.locale_search_menu); + mSearchView = (SearchView) searchMenuItem.getActionView(); mSearchView.setQueryHint(getText(R.string.search_language_hint)); mSearchView.setOnQueryTextListener(this); - mSearchView.setQuery("", false /* submit */); + + // Restore previous search status + if (!TextUtils.isEmpty(mPreviousSearch)) { + searchMenuItem.expandActionView(); + mSearchView.setIconified(false); + mSearchView.setActivated(true); + if (mPreviousSearchHadFocus) { + mSearchView.requestFocus(); + } + mSearchView.setQuery(mPreviousSearch, true /* submit */); + } else { + mSearchView.setQuery(null, false /* submit */); + } + + // Restore previous scroll position + getListView().setSelectionFromTop(mFirstVisiblePosition, mTopDistance); } } diff --git a/core/java/com/android/internal/widget/MediaNotificationView.java b/core/java/com/android/internal/widget/MediaNotificationView.java index 6bba1b3e1aec..f63afada7e38 100644 --- a/core/java/com/android/internal/widget/MediaNotificationView.java +++ b/core/java/com/android/internal/widget/MediaNotificationView.java @@ -89,36 +89,50 @@ public class MediaNotificationView extends FrameLayout { int topMargin = getMeasuredHeight() - mRightIcon.getMeasuredHeight() - iconParams.bottomMargin; // If the topMargin is high enough we can also remove the header constraint! + boolean reMeasure = false; if (!hasIcon || topMargin >= mImageMinTopMargin) { - resetHeaderIndention(); + reMeasure = resetHeaderIndention(); } else { int paddingEnd = mNotificationContentImageMarginEnd; ViewGroup.MarginLayoutParams headerParams = (MarginLayoutParams) mHeader.getLayoutParams(); - headerParams.setMarginEnd(mRightIcon.getMeasuredWidth() + iconParams.getMarginEnd()); + int newMarginEnd = mRightIcon.getMeasuredWidth() + iconParams.getMarginEnd(); + if (headerParams.getMarginEnd() != newMarginEnd) { + headerParams.setMarginEnd(newMarginEnd); + mHeader.setLayoutParams(headerParams); + reMeasure = true; + } if (mHeader.getPaddingEnd() != paddingEnd) { - mHeader.setPadding( - isLayoutRtl() ? paddingEnd : mHeader.getPaddingLeft(), + mHeader.setPaddingRelative(mHeader.getPaddingStart(), mHeader.getPaddingTop(), - isLayoutRtl() ? mHeader.getPaddingLeft() : paddingEnd, + paddingEnd, mHeader.getPaddingBottom()); - mHeader.setLayoutParams(headerParams); + reMeasure = true; } } + if (reMeasure) { + measureChildWithMargins(mHeader, widthMeasureSpec, 0, heightMeasureSpec, 0); + } } - private void resetHeaderIndention() { + private boolean resetHeaderIndention() { + boolean remeasure = false; if (mHeader.getPaddingEnd() != mNotificationContentMarginEnd) { - ViewGroup.MarginLayoutParams headerParams = - (MarginLayoutParams) mHeader.getLayoutParams(); - headerParams.setMarginEnd(0); - mHeader.setPadding( - isLayoutRtl() ? mNotificationContentMarginEnd : mHeader.getPaddingLeft(), + mHeader.setPaddingRelative(mHeader.getPaddingStart(), mHeader.getPaddingTop(), - isLayoutRtl() ? mHeader.getPaddingLeft() : mNotificationContentMarginEnd, + mNotificationContentMarginEnd, mHeader.getPaddingBottom()); + remeasure = true; + } + ViewGroup.MarginLayoutParams headerParams = + (MarginLayoutParams) mHeader.getLayoutParams(); + headerParams.setMarginEnd(0); + if (headerParams.getMarginEnd() != 0) { + headerParams.setMarginEnd(0); mHeader.setLayoutParams(headerParams); + remeasure = true; } + return remeasure; } public MediaNotificationView(Context context, AttributeSet attrs, int defStyleAttr, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8f85d4a67181..fbc96c262cfc 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -168,7 +168,7 @@ android:name="android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED" /> <protected-broadcast android:name="android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED" /> - <protected-broadcast + <protected-broadcast android:name="android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED" /> <protected-broadcast android:name="android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED" /> @@ -984,6 +984,11 @@ <permission android:name="android.permission.SEND_RESPOND_VIA_MESSAGE" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to send SMS to premium shortcodes without user permission. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.SEND_SMS_NO_CONFIRMATION" + android:protectionLevel="signature|privileged" /> + <!-- Allows an application to filter carrier specific sms. @hide --> <permission android:name="android.permission.CARRIER_FILTER_SMS" @@ -2769,12 +2774,11 @@ android:protectionLevel="signature" /> <!-- Must be required by an {@link - android.service.notification.NotificationAssistantService}, - to ensure that only the system can bind to it. + android.service.notification.NotificationRankerService to ensure that only the system can bind to it. <p>Protection level: signature @hide This is not a third-party API (intended for system apps). --> --> - <permission android:name="android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE" + <permission android:name="android.permission.BIND_NOTIFICATION_RANKER_SERVICE" android:protectionLevel="signature" /> <!-- Must be required by a {@link @@ -2929,6 +2933,12 @@ <permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" android:protectionLevel="signature" /> + <!-- Must be required by an {@link android.service.vr.VrListenerService}, to ensure that only + the system can bind to it. + <p>Protection level: signature --> + <permission android:name="android.permission.BIND_VR_LISTENER_SERVICE" + android:protectionLevel="signature" /> + <application android:process="system" android:persistent="true" android:hasCode="false" diff --git a/core/res/assets/images/clock64.png b/core/res/assets/images/clock64.png Binary files differnew file mode 100644 index 000000000000..493a1ea97717 --- /dev/null +++ b/core/res/assets/images/clock64.png diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 6669baedcfab..b8b00bfcc111 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -33,16 +33,6 @@ android:layout_marginEnd="3dp" /> <TextView - android:id="@+id/number_of_children" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.Material.Notification" - android:layout_marginEnd="3dp" - android:layout_marginStart="2dp" - android:visibility="gone" - android:singleLine="true" - /> - <TextView android:id="@+id/app_name_text" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 90a573ba8fbd..9ccd7f0a77dd 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8170,6 +8170,8 @@ i Defined in same coordinates as the path itself --> <attr name="endY" format="float" /> + <!-- Defines the tile mode of the gradient. SweepGradient don't support tiling. --> + <attr name="tileMode"/> </declare-styleable> <!-- Describes an item of a GradientColor. Minimally need 2 items to define the gradient diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index d8efd63047a4..17afd92b648a 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -548,9 +548,6 @@ [CHAR LIMIT=4] --> <string name="status_bar_notification_info_overflow">999+</string> - <!-- The number of notifications in the notification header. An example would be (2) or (12) --> - <string name="notification_children_count_bracketed">(<xliff:g id="notificationCount" example="1">%d</xliff:g>)</string> - <!-- The divider symbol between different parts of the notification header. not translatable [CHAR LIMIT=1] --> <string name="notification_header_divider_symbol" translatable="false">•</string> @@ -3135,11 +3132,13 @@ <!-- Label to show for a service that is running because it is observing the user's notifications. --> <string name="notification_listener_binding_label">Notification listener</string> + <!-- Label to show for a service that is running because the system is in VR mode. --> + <string name="vr_listener_binding_label">VR listener</string> <!-- Label to show for a service that is running because it is providing conditions. --> <string name="condition_provider_service_binding_label">Condition provider</string> <!-- Label to show for a service that is running because it is observing and modifying the importance of the user's notifications. --> - <string name="notification_assistant_binding_label">Notification assistant</string> + <string name="notification_ranker_binding_label">Notification ranker service</string> <!-- Do Not Translate: Alternate eri.xml --> <string name="alternate_eri_file">/data/eri.xml</string> @@ -4229,4 +4228,9 @@ <!-- The representation of a time duration when negative. An example is -1:14. This can be used with a countdown timer for example.--> <string name="negative_duration">\u2212<xliff:g id="time" example="1:14">%1$s</xliff:g></string> + <!-- Title of notification shown when device has been forced to safe mode after a security compromise. --> + <string name="audit_safemode_notification">Factory reset to use this device normally</string> + <!-- Description of notification shown when device has been forced to safe mode after a security compromise. --> + <string name="audit_safemode_notification_details">Touch to learn more.</string> + </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 15521e48d319..cad0e7b4570e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1842,8 +1842,9 @@ <java-symbol type="string" name="low_internal_storage_view_text_no_boot" /> <java-symbol type="string" name="low_internal_storage_view_title" /> <java-symbol type="string" name="notification_listener_binding_label" /> + <java-symbol type="string" name="vr_listener_binding_label" /> <java-symbol type="string" name="condition_provider_service_binding_label" /> - <java-symbol type="string" name="notification_assistant_binding_label" /> + <java-symbol type="string" name="notification_ranker_binding_label" /> <java-symbol type="string" name="report" /> <java-symbol type="string" name="select_input_method" /> <java-symbol type="string" name="select_keyboard_layout_notification_title" /> @@ -1894,6 +1895,8 @@ <java-symbol type="string" name="config_customVpnConfirmDialogComponent" /> <java-symbol type="string" name="config_defaultNetworkScorerPackageName" /> <java-symbol type="string" name="config_persistentDataPackageName" /> + <java-symbol type="string" name="audit_safemode_notification" /> + <java-symbol type="string" name="audit_safemode_notification_details" /> <java-symbol type="layout" name="resolver_list" /> <java-symbol type="id" name="resolver_list" /> @@ -2405,11 +2408,9 @@ <java-symbol type="id" name="notification_material_reply_text_2" /> <java-symbol type="id" name="notification_material_reply_text_3" /> - <java-symbol type="string" name="notification_children_count_bracketed" /> <java-symbol type="string" name="notification_hidden_text" /> <java-symbol type="string" name="notification_hidden_by_policy_text" /> <java-symbol type="id" name="app_name_text" /> - <java-symbol type="id" name="number_of_children" /> <java-symbol type="id" name="header_sub_text" /> <java-symbol type="id" name="expand_button" /> <java-symbol type="id" name="notification_header" /> diff --git a/core/tests/coretests/src/android/transition/SlideTransitionTest.java b/core/tests/coretests/src/android/transition/SlideTransitionTest.java new file mode 100644 index 000000000000..8b9ec74be8df --- /dev/null +++ b/core/tests/coretests/src/android/transition/SlideTransitionTest.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.transition; + +import android.animation.AnimatorSetActivity; +import android.app.Activity; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; + +import com.android.frameworks.coretests.R; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + + +public class SlideTransitionTest extends ActivityInstrumentationTestCase2<AnimatorSetActivity> { + + Activity mActivity; + + public SlideTransitionTest() { + super(AnimatorSetActivity.class); + } + + @Override + protected void setUp() throws Exception { + mActivity = getActivity(); + } + + @SmallTest + public void testShortSlide() throws Throwable { + final float slideFraction = 0.5f; + final View square1 = mActivity.findViewById(R.id.square1); + final View sceneRoot = mActivity.findViewById(R.id.container); + final SlideTranslationValueRatchet ratchet = new SlideTranslationValueRatchet(square1); + square1.getViewTreeObserver().addOnPreDrawListener(ratchet); + + final Slide slideOut = new Slide(Gravity.BOTTOM); + final float finalOffsetOut = sceneRoot.getHeight() * slideFraction; + slideOut.setSlideFraction(slideFraction); + TransitionLatch latch = setVisibilityInTransition(slideOut, R.id.square1, View.INVISIBLE); + assertTrue(latch.startLatch.await(200, TimeUnit.MILLISECONDS)); + assertEquals(0f, square1.getTranslationY(), 0.1f); + assertEquals(View.VISIBLE, square1.getVisibility()); + Thread.sleep(100); + assertFalse(square1.getTranslationY() < 0.1 + || square1.getTranslationY() > finalOffsetOut - 0.1); + assertTrue(latch.endLatch.await(400, TimeUnit.MILLISECONDS)); + // Give this 20% slop in case some frames get dropped. + assertTrue(finalOffsetOut * 0.8 < ratchet.maxY); + assertTrue(finalOffsetOut + 0.1 > ratchet.maxY); + assertEquals(View.INVISIBLE, square1.getVisibility()); + + ratchet.reset(); + final Slide slideIn = new Slide(Gravity.BOTTOM); + final float initialOffsetIn = sceneRoot.getHeight() * slideFraction; + slideIn.setSlideFraction(slideFraction); + latch = setVisibilityInTransition(slideIn, R.id.square1, View.VISIBLE); + assertTrue(latch.startLatch.await(200, TimeUnit.MILLISECONDS)); + assertEquals(initialOffsetIn, square1.getTranslationY(), 0.1f); + assertEquals(View.VISIBLE, square1.getVisibility()); + Thread.sleep(100); + assertFalse(square1.getTranslationY() < 0.1 + || square1.getTranslationY() > initialOffsetIn - 0.1); + assertTrue(latch.endLatch.await(400, TimeUnit.MILLISECONDS)); + assertEquals(0f, ratchet.minY, 0.1); + assertEquals(0f, square1.getTranslationY(), 0.1); + assertEquals(View.VISIBLE, square1.getVisibility()); + + square1.getViewTreeObserver().removeOnPreDrawListener(ratchet); + } + + public TransitionLatch setVisibilityInTransition(final Transition transition, int viewId, + final int visibility) throws Throwable { + final ViewGroup sceneRoot = (ViewGroup) mActivity.findViewById(R.id.container); + final View view = sceneRoot.findViewById(viewId); + TransitionLatch latch = new TransitionLatch(); + transition.addListener(latch); + runTestOnUiThread(new Runnable() { + @Override + public void run() { + TransitionManager.beginDelayedTransition(sceneRoot, transition); + view.setVisibility(visibility); + } + }); + return latch; + } + + public static class TransitionLatch implements Transition.TransitionListener { + public CountDownLatch startLatch = new CountDownLatch(1); + public CountDownLatch endLatch = new CountDownLatch(1); + public CountDownLatch cancelLatch = new CountDownLatch(1); + public CountDownLatch pauseLatch = new CountDownLatch(1); + public CountDownLatch resumeLatch = new CountDownLatch(1); + + @Override + public void onTransitionStart(Transition transition) { + startLatch.countDown(); + } + + @Override + public void onTransitionEnd(Transition transition) { + endLatch.countDown(); + transition.removeListener(this); + } + + @Override + public void onTransitionCancel(Transition transition) { + cancelLatch.countDown(); + } + + @Override + public void onTransitionPause(Transition transition) { + pauseLatch.countDown(); + } + + @Override + public void onTransitionResume(Transition transition) { + resumeLatch.countDown(); + } + } + + private static class SlideTranslationValueRatchet + implements ViewTreeObserver.OnPreDrawListener { + + private final View mView; + private boolean mInitialized; + public float minX = Float.NaN; + public float minY = Float.NaN; + public float maxX = Float.NaN; + public float maxY = Float.NaN; + + public SlideTranslationValueRatchet(View view) { + mView = view; + } + + public void reset() { + minX = minY = maxX = maxY = Float.NaN; + mInitialized = false; + } + + @Override + public boolean onPreDraw() { + if (!mInitialized) { + minX = maxX = mView.getTranslationX(); + minY = maxY = mView.getTranslationY(); + mInitialized = true; + } else { + minX = Math.min(minX, mView.getTranslationX()); + minY = Math.min(minY, mView.getTranslationY()); + maxX = Math.max(maxX, mView.getTranslationX()); + maxY = Math.max(maxY, mView.getTranslationY()); + } + return true; + } + } +} diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 70995acbb4ca..9a9a89bba53f 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -227,15 +227,16 @@ include $(CLEAR_VARS) LOCAL_MODULE := hwui_unit_tests LOCAL_MODULE_TAGS := tests LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu +LOCAL_SHARED_LIBRARIES := libmemunreachable LOCAL_CFLAGS := \ $(hwui_cflags) \ -DHWUI_NULL_GPU LOCAL_SRC_FILES += \ $(hwui_test_common_src_files) \ + tests/unit/main.cpp \ tests/unit/CanvasStateTests.cpp \ tests/unit/ClipAreaTests.cpp \ - tests/unit/CrashHandlerInjector.cpp \ tests/unit/DamageAccumulatorTests.cpp \ tests/unit/DeviceInfoTests.cpp \ tests/unit/FatVectorTests.cpp \ diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index 9595a85e99dd..c809ff4f85e8 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -19,9 +19,6 @@ #include "DeferredLayerUpdater.h" #include "LayerRenderer.h" -#include <unistd.h> -#include <signal.h> - namespace android { namespace uirenderer { @@ -136,27 +133,7 @@ void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text, canvas->drawTextOnPath(glyphs.data(), glyphs.size(), path, 0, 0, paint); } -static void defaultCrashHandler() { - fprintf(stderr, "RenderThread crashed!"); -} - -static std::function<void()> gCrashHandler = defaultCrashHandler; -static sighandler_t gPreviousSignalHandler; - -static void signalHandler(int sig) { - gCrashHandler(); - if (gPreviousSignalHandler) { - gPreviousSignalHandler(sig); - } -} - -void TestUtils::setRenderThreadCrashHandler(std::function<void()> crashHandler) { - gCrashHandler = crashHandler; -} - void TestUtils::TestTask::run() { - gPreviousSignalHandler = signal(SIGABRT, signalHandler); - // RenderState only valid once RenderThread is running, so queried here RenderState& renderState = renderthread::RenderThread::getInstance().renderState(); @@ -164,9 +141,6 @@ void TestUtils::TestTask::run() { rtCallback(renderthread::RenderThread::getInstance()); renderState.flush(Caches::FlushMode::Full); renderState.onGLContextDestroyed(); - - // Restore the previous signal handler - signal(SIGABRT, gPreviousSignalHandler); } } /* namespace uirenderer */ diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 6f237059dd33..28ac1166fc5c 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -180,8 +180,6 @@ public: typedef std::function<void(renderthread::RenderThread& thread)> RtCallback; - static void setRenderThreadCrashHandler(std::function<void()> crashHandler); - class TestTask : public renderthread::RenderTask { public: TestTask(RtCallback rtCallback) diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp new file mode 100644 index 000000000000..409a12d37693 --- /dev/null +++ b/libs/hwui/tests/unit/main.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gtest/gtest.h" + +#include "Caches.h" +#include "thread/TaskManager.h" +#include "tests/common/TestUtils.h" + +#include <memunreachable/memunreachable.h> + +#include <cstdio> +#include <iostream> +#include <map> +#include <unordered_set> +#include <signal.h> +#include <unistd.h> + +using namespace std; +using namespace android; +using namespace android::uirenderer; + +static auto CRASH_SIGNALS = { + SIGABRT, + SIGSEGV, + SIGBUS, +}; + +static map<int, struct sigaction> gSigChain; + +static void gtestSigHandler(int sig, siginfo_t* siginfo, void* context) { + auto testinfo = ::testing::UnitTest::GetInstance()->current_test_info(); + printf("[ FAILED ] %s.%s\n", testinfo->test_case_name(), + testinfo->name()); + printf("[ FATAL! ] Process crashed, aborting tests!\n"); + fflush(stdout); + + // restore the default sighandler and re-raise + struct sigaction sa = gSigChain[sig]; + sigaction(sig, &sa, nullptr); + raise(sig); +} + +static void logUnreachable(initializer_list<UnreachableMemoryInfo> infolist) { + // merge them all + UnreachableMemoryInfo merged; + unordered_set<uintptr_t> addrs; + merged.allocation_bytes = 0; + merged.leak_bytes = 0; + merged.num_allocations = 0; + merged.num_leaks = 0; + for (auto& info : infolist) { + // We'll be a little hazzy about these ones and just hope the biggest + // is the most accurate + merged.allocation_bytes = max(merged.allocation_bytes, info.allocation_bytes); + merged.num_allocations = max(merged.num_allocations, info.num_allocations); + for (auto& leak : info.leaks) { + if (addrs.find(leak.begin) == addrs.end()) { + merged.leaks.push_back(leak); + merged.num_leaks++; + merged.leak_bytes += leak.size; + addrs.insert(leak.begin); + } + } + } + + // Now log the result + if (merged.num_leaks) { + cout << endl << "Leaked memory!" << endl; + if (!merged.leaks[0].backtrace.num_frames) { + cout << "Re-run with 'setprop libc.debug.malloc.program hwui_unit_test'" + << endl << "and 'setprop libc.debug.malloc.options backtrace=8'" + << " to get backtraces" << endl; + } + cout << merged.ToString(false); + } +} + +static void checkForLeaks() { + // TODO: Until we can shutdown the RT thread we need to do this in + // two passes as GetUnreachableMemory has limited insight into + // thread-local caches so some leaks will not be properly tagged as leaks + nsecs_t before = systemTime(); + UnreachableMemoryInfo rtMemInfo; + TestUtils::runOnRenderThread([&rtMemInfo](renderthread::RenderThread& thread) { + if (Caches::hasInstance()) { + Caches::getInstance().tasks.stop(); + } + // Check for leaks + if (!GetUnreachableMemory(rtMemInfo)) { + cerr << "Failed to get unreachable memory!" << endl; + return; + } + }); + UnreachableMemoryInfo uiMemInfo; + if (!GetUnreachableMemory(uiMemInfo)) { + cerr << "Failed to get unreachable memory!" << endl; + return; + } + logUnreachable({rtMemInfo, uiMemInfo}); + nsecs_t after = systemTime(); + cout << "Leak check took " << ns2ms(after - before) << "ms" << endl; +} + +int main(int argc, char* argv[]) { + // Register a crash handler + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = >estSigHandler; + sa.sa_flags = SA_SIGINFO; + for (auto sig : CRASH_SIGNALS) { + struct sigaction old_sa; + sigaction(sig, &sa, &old_sa); + gSigChain.insert(pair<int, struct sigaction>(sig, old_sa)); + } + + // Run the tests + testing::InitGoogleTest(&argc, argv); + int ret = RUN_ALL_TESTS(); + checkForLeaks(); + return ret; +} + diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 8206d237ac42..69d44872d86e 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2730,15 +2730,16 @@ public class AudioManager { * this abstract class and register it with * {@link AudioManager#registerAudioRecordingCallback(AudioRecordingCallback, Handler)} * to be notified. - * Use {@link AudioManager#getActiveRecordConfigurations()} to query the current configuration. + * Use {@link AudioManager#getActiveRecordingConfigurations()} to query the current + * configuration. */ public static abstract class AudioRecordingCallback { /** * Called whenever the device recording configuration has changed. * @param configs array containing the results of - * {@link AudioManager#getActiveRecordConfigurations()}. + * {@link AudioManager#getActiveRecordingConfigurations()}. */ - public void onRecordConfigChanged(AudioRecordConfiguration[] configs) {} + public void onRecordConfigChanged(AudioRecordingConfiguration[] configs) {} } private static class AudioRecordingCallbackInfo { @@ -2752,10 +2753,10 @@ public class AudioManager { private final static class RecordConfigChangeCallbackData { final AudioRecordingCallback mCb; - final AudioRecordConfiguration[] mConfigs; + final AudioRecordingConfiguration[] mConfigs; RecordConfigChangeCallbackData(AudioRecordingCallback cb, - AudioRecordConfiguration[] configs) { + AudioRecordingConfiguration[] configs) { mCb = cb; mConfigs = configs; } @@ -2838,10 +2839,10 @@ public class AudioManager { * @return a non-null array of recording configurations. An array of length 0 indicates there is * no recording active when queried. */ - public @NonNull AudioRecordConfiguration[] getActiveRecordConfigurations() { + public @NonNull AudioRecordingConfiguration[] getActiveRecordingConfigurations() { final IAudioService service = getService(); try { - return service.getActiveRecordConfigurations(); + return service.getActiveRecordingConfigurations(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2896,7 +2897,7 @@ public class AudioManager { private final IRecordingConfigDispatcher mRecCb = new IRecordingConfigDispatcher.Stub() { - public void dispatchRecordingConfigChange(AudioRecordConfiguration[] configs) { + public void dispatchRecordingConfigChange(AudioRecordingConfiguration[] configs) { synchronized(mRecordCallbackLock) { if (mRecordCallbackList != null) { for (int i=0 ; i < mRecordCallbackList.size() ; i++) { diff --git a/media/java/android/media/AudioRecordConfiguration.aidl b/media/java/android/media/AudioRecordingConfiguration.aidl index afe912b10b8f..c63d30b0a3d6 100644 --- a/media/java/android/media/AudioRecordConfiguration.aidl +++ b/media/java/android/media/AudioRecordingConfiguration.aidl @@ -15,4 +15,4 @@ package android.media; -parcelable AudioRecordConfiguration; +parcelable AudioRecordingConfiguration; diff --git a/media/java/android/media/AudioRecordConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java index de78a5a813eb..cd6f95acda5f 100644 --- a/media/java/android/media/AudioRecordConfiguration.java +++ b/media/java/android/media/AudioRecordingConfiguration.java @@ -27,13 +27,13 @@ import java.util.ArrayList; import java.util.Objects; /** - * The AudioRecordConfiguration class collects the information describing an audio recording + * The AudioRecordingConfiguration class collects the information describing an audio recording * session. This information is returned through the - * {@link AudioManager#getActiveRecordConfigurations()} method. + * {@link AudioManager#getActiveRecordingConfigurations()} method. * */ -public final class AudioRecordConfiguration implements Parcelable { - private final static String TAG = new String("AudioRecordConfiguration"); +public final class AudioRecordingConfiguration implements Parcelable { + private final static String TAG = new String("AudioRecordingConfiguration"); private final int mSessionId; @@ -47,7 +47,7 @@ public final class AudioRecordConfiguration implements Parcelable { /** * @hide */ - public AudioRecordConfiguration(int session, int source, AudioFormat devFormat, + public AudioRecordingConfiguration(int session, int source, AudioFormat devFormat, AudioFormat clientFormat, int patchHandle) { mSessionId = session; mClientSource = source; @@ -136,18 +136,18 @@ public final class AudioRecordConfiguration implements Parcelable { return null; } - public static final Parcelable.Creator<AudioRecordConfiguration> CREATOR - = new Parcelable.Creator<AudioRecordConfiguration>() { + public static final Parcelable.Creator<AudioRecordingConfiguration> CREATOR + = new Parcelable.Creator<AudioRecordingConfiguration>() { /** - * Rebuilds an AudioRecordConfiguration previously stored with writeToParcel(). - * @param p Parcel object to read the AudioRecordConfiguration from - * @return a new AudioRecordConfiguration created from the data in the parcel + * Rebuilds an AudioRecordingConfiguration previously stored with writeToParcel(). + * @param p Parcel object to read the AudioRecordingConfiguration from + * @return a new AudioRecordingConfiguration created from the data in the parcel */ - public AudioRecordConfiguration createFromParcel(Parcel p) { - return new AudioRecordConfiguration(p); + public AudioRecordingConfiguration createFromParcel(Parcel p) { + return new AudioRecordingConfiguration(p); } - public AudioRecordConfiguration[] newArray(int size) { - return new AudioRecordConfiguration[size]; + public AudioRecordingConfiguration[] newArray(int size) { + return new AudioRecordingConfiguration[size]; } }; @@ -170,7 +170,7 @@ public final class AudioRecordConfiguration implements Parcelable { dest.writeInt(mPatchHandle); } - private AudioRecordConfiguration(Parcel in) { + private AudioRecordingConfiguration(Parcel in) { mSessionId = in.readInt(); mClientSource = in.readInt(); mClientFormat = AudioFormat.CREATOR.createFromParcel(in); @@ -181,9 +181,9 @@ public final class AudioRecordConfiguration implements Parcelable { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || !(o instanceof AudioRecordConfiguration)) return false; + if (o == null || !(o instanceof AudioRecordingConfiguration)) return false; - AudioRecordConfiguration that = (AudioRecordConfiguration) o; + AudioRecordingConfiguration that = (AudioRecordingConfiguration) o; return ((mSessionId == that.mSessionId) && (mClientSource == that.mClientSource) diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 987a8b6a48ee..97f670b17114 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -20,7 +20,7 @@ import android.app.PendingIntent; import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.media.AudioAttributes; -import android.media.AudioRecordConfiguration; +import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; import android.media.IAudioFocusDispatcher; import android.media.IAudioRoutesObserver; @@ -164,5 +164,5 @@ interface IAudioService { oneway void unregisterRecordingCallback(in IRecordingConfigDispatcher rcdb); - AudioRecordConfiguration[] getActiveRecordConfigurations(); + AudioRecordingConfiguration[] getActiveRecordingConfigurations(); } diff --git a/media/java/android/media/IRecordingConfigDispatcher.aidl b/media/java/android/media/IRecordingConfigDispatcher.aidl index eaa92ca56d20..e8032831af88 100644 --- a/media/java/android/media/IRecordingConfigDispatcher.aidl +++ b/media/java/android/media/IRecordingConfigDispatcher.aidl @@ -16,7 +16,7 @@ package android.media; -import android.media.AudioRecordConfiguration; +import android.media.AudioRecordingConfiguration; /** * AIDL for the RecordingActivity monitor in AudioService to signal audio recording updates. @@ -25,6 +25,6 @@ import android.media.AudioRecordConfiguration; */ oneway interface IRecordingConfigDispatcher { - void dispatchRecordingConfigChange(in AudioRecordConfiguration[] configs); + void dispatchRecordingConfigChange(in AudioRecordingConfiguration[] configs); } diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java index ada0e2cc653f..364215534d62 100644 --- a/media/java/android/media/browse/MediaBrowser.java +++ b/media/java/android/media/browse/MediaBrowser.java @@ -1116,7 +1116,7 @@ public final class MediaBrowser { } } mCallbacks.add(callback); - mOptionsList.add(options); + mOptionsList.add(options == null ? null : new Bundle(options)); } public boolean removeCallback(Bundle options) { diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 63e3edcffa40..7c9591d68254 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -349,12 +349,12 @@ public final class TvInputInfo implements Parcelable { /** * Returns the number of tuners this TV input has. * - * <p>This method is valid only for the input of type {@link #TYPE_TUNER}. + * <p>This method is valid only for inputs of type {@link #TYPE_TUNER}. For inputs of other + * types, it returns 0. * * <p>Tuners correspond to physical/logical resources that allow reception of TV signal. Having * <i>N</i> tuners means that the TV input is capable of receiving <i>N</i> different channels * concurrently. - * */ public int getTunerCount() { return mTunerCount; diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java index 6a44b1e6e4e9..e623353c2440 100644 --- a/media/java/android/media/tv/TvTrackInfo.java +++ b/media/java/android/media/tv/TvTrackInfo.java @@ -121,6 +121,8 @@ public final class TvTrackInfo implements Parcelable { /** * Returns the audio channel count. Valid only for {@link #TYPE_AUDIO} tracks. + * + * @throws IllegalStateException if not called on an audio track */ public final int getAudioChannelCount() { if (mType != TYPE_AUDIO) { @@ -131,6 +133,8 @@ public final class TvTrackInfo implements Parcelable { /** * Returns the audio sample rate, in the unit of Hz. Valid only for {@link #TYPE_AUDIO} tracks. + * + * @throws IllegalStateException if not called on an audio track */ public final int getAudioSampleRate() { if (mType != TYPE_AUDIO) { @@ -142,6 +146,8 @@ public final class TvTrackInfo implements Parcelable { /** * Returns the width of the video, in the unit of pixels. Valid only for {@link #TYPE_VIDEO} * tracks. + * + * @throws IllegalStateException if not called on a video track */ public final int getVideoWidth() { if (mType != TYPE_VIDEO) { @@ -153,6 +159,8 @@ public final class TvTrackInfo implements Parcelable { /** * Returns the height of the video, in the unit of pixels. Valid only for {@link #TYPE_VIDEO} * tracks. + * + * @throws IllegalStateException if not called on a video track */ public final int getVideoHeight() { if (mType != TYPE_VIDEO) { @@ -164,6 +172,8 @@ public final class TvTrackInfo implements Parcelable { /** * Returns the frame rate of the video, in the unit of fps (frames per second). Valid only for * {@link #TYPE_VIDEO} tracks. + * + * @throws IllegalStateException if not called on a video track */ public final float getVideoFrameRate() { if (mType != TYPE_VIDEO) { @@ -175,6 +185,8 @@ public final class TvTrackInfo implements Parcelable { /** * Returns the pixel aspect ratio (the ratio of a pixel's width to its height) of the video. * Valid only for {@link #TYPE_VIDEO} tracks. + * + * @throws IllegalStateException if not called on a video track */ public final float getVideoPixelAspectRatio() { if (mType != TYPE_VIDEO) { @@ -189,6 +201,8 @@ public final class TvTrackInfo implements Parcelable { * * <p>The complete list of values are defined in ETSI TS 101 154 V1.7.1 Annex B, ATSC A/53 Part * 4 and SMPTE 2016-1-2007. + * + * @throws IllegalStateException if not called on a video track */ public final byte getVideoActiveFormatDescription() { if (mType != TYPE_VIDEO) { @@ -268,6 +282,8 @@ public final class TvTrackInfo implements Parcelable { * @param type The type of the track. * @param id The ID of the track that uniquely identifies the current track among all the * other tracks in the same TV program. + * @throws IllegalArgumentException if the type is not any of {@link #TYPE_AUDIO}, + * {@link #TYPE_VIDEO} and {@link #TYPE_SUBTITLE} */ public Builder(int type, @NonNull String id) { if (type != TYPE_AUDIO @@ -304,6 +320,7 @@ public final class TvTrackInfo implements Parcelable { * Sets the audio channel count. Valid only for {@link #TYPE_AUDIO} tracks. * * @param audioChannelCount The audio channel count. + * @throws IllegalStateException if not called on an audio track */ public final Builder setAudioChannelCount(int audioChannelCount) { if (mType != TYPE_AUDIO) { @@ -318,6 +335,7 @@ public final class TvTrackInfo implements Parcelable { * tracks. * * @param audioSampleRate The audio sample rate. + * @throws IllegalStateException if not called on an audio track */ public final Builder setAudioSampleRate(int audioSampleRate) { if (mType != TYPE_AUDIO) { @@ -332,6 +350,7 @@ public final class TvTrackInfo implements Parcelable { * tracks. * * @param videoWidth The width of the video. + * @throws IllegalStateException if not called on a video track */ public final Builder setVideoWidth(int videoWidth) { if (mType != TYPE_VIDEO) { @@ -346,6 +365,7 @@ public final class TvTrackInfo implements Parcelable { * tracks. * * @param videoHeight The height of the video. + * @throws IllegalStateException if not called on a video track */ public final Builder setVideoHeight(int videoHeight) { if (mType != TYPE_VIDEO) { @@ -360,6 +380,7 @@ public final class TvTrackInfo implements Parcelable { * {@link #TYPE_VIDEO} tracks. * * @param videoFrameRate The frame rate of the video. + * @throws IllegalStateException if not called on a video track */ public final Builder setVideoFrameRate(float videoFrameRate) { if (mType != TYPE_VIDEO) { @@ -379,6 +400,7 @@ public final class TvTrackInfo implements Parcelable { * pixel aspect ratio for most video formats. * * @param videoPixelAspectRatio The pixel aspect ratio of the video. + * @throws IllegalStateException if not called on a video track */ public final Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) { if (mType != TYPE_VIDEO) { @@ -398,6 +420,7 @@ public final class TvTrackInfo implements Parcelable { * 4 and SMPTE 2016-1-2007. * * @param videoActiveFormatDescription The AFD code of the video. + * @throws IllegalStateException if not called on a video track */ public final Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) { if (mType != TYPE_VIDEO) { diff --git a/packages/DocumentsUI/res/layout/dialog_delete_confirmation.xml b/packages/DocumentsUI/res/layout/dialog_delete_confirmation.xml index 990ce0b3352b..a1c291093f41 100644 --- a/packages/DocumentsUI/res/layout/dialog_delete_confirmation.xml +++ b/packages/DocumentsUI/res/layout/dialog_delete_confirmation.xml @@ -18,8 +18,9 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingTop="30dp" - android:gravity="center" + android:paddingTop="24dp" + android:paddingStart="24dp" + android:paddingEnd="24dp" android:textAppearance="@android:style/TextAppearance.Material.Subhead" android:textColor="@*android:color/primary_text_default_material_light"> </TextView> diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java index c17be067d55d..c56a12f5f1e4 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java @@ -282,6 +282,24 @@ public class FilesActivity extends BaseActivity { * Launches an intent to view the specified document. */ private void openDocument(DocumentInfo doc, Model model) { + + // Provide specialized handling of downloaded APKs This sends the APK + // details off to get extra security information added, and finally + // to be handled by the package manager. + if (MimePredicate.isApkType(doc.mimeType)) { + // First try managing the document; we expect manager to filter + // based on authority, so we don't grant. + final Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT); + manage.setData(doc.derivedUri); + + try { + startActivity(manage); + return; + } catch (ActivityNotFoundException ex) { + // fall back to regular handling below. + } + } + Intent intent = new QuickViewIntentBuilder( getPackageManager(), getResources(), doc, model).build(); @@ -296,7 +314,7 @@ public class FilesActivity extends BaseActivity { } } - // Fallback to traditional VIEW action... + // Fall back to traditional VIEW action... intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setData(doc.derivedUri); diff --git a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java index 9df55a042929..2f202e7b976b 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java +++ b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java @@ -16,12 +16,15 @@ package com.android.documentsui; +import android.annotation.Nullable; + import com.android.documentsui.model.DocumentInfo; import com.android.internal.util.Predicate; public class MimePredicate implements Predicate<DocumentInfo> { private final String[] mFilters; + private static final String APK_TYPE = "application/vnd.android.package-archive"; /** * MIME types that are visual in nature. For example, they should always be * shown as thumbnails in list mode. @@ -92,4 +95,8 @@ public class MimePredicate implements Predicate<DocumentInfo> { return false; } } + + public static boolean isApkType(@Nullable String mimeType) { + return APK_TYPE.equals(mimeType); + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index 83838d3328fb..19679231c130 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -17,7 +17,6 @@ package com.android.documentsui.dirlist; import static com.android.documentsui.Shared.DEBUG; -import static com.android.documentsui.State.ACTION_MANAGE; import static com.android.documentsui.State.MODE_GRID; import static com.android.documentsui.State.MODE_LIST; import static com.android.documentsui.State.SORT_ORDER_UNKNOWN; @@ -1535,7 +1534,7 @@ public class DirectoryFragment extends Fragment mRoot.authority, mRoot.rootId, mQuery) : DocumentsContract.buildChildDocumentsUri( mDocument.authority, mDocument.documentId); - if (state.action == ACTION_MANAGE) { + if (mTuner.enableManagedMode()) { contentsUri = DocumentsContract.setManageMode(contentsUri); } return new DirectoryLoader( @@ -1544,6 +1543,7 @@ public class DirectoryFragment extends Fragment case TYPE_RECENT_OPEN: final RootsCache roots = DocumentsApplication.getRootsCache(context); return new RecentsLoader(context, roots, state); + default: throw new IllegalStateException("Unknown type " + mType); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java index f99ec85c358b..adaa850b0950 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java @@ -82,6 +82,12 @@ public abstract class FragmentTuner { abstract void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch); /** + * When managed mode is enabled, active downloads will be visible in the UI. + * Presumably this should only be true when in the downloads directory. + */ + abstract boolean enableManagedMode(); + + /** * Provides support for Platform specific specializations of DirectoryFragment. */ private static final class DocumentsTuner extends FragmentTuner { @@ -157,6 +163,11 @@ public abstract class FragmentTuner { ((BaseActivity) mContext).setRootsDrawerOpen(true); } } + + @Override + public boolean enableManagedMode() { + return false; + } } /** @@ -166,6 +177,7 @@ public abstract class FragmentTuner { public DownloadsTuner(Context context, State state) { super(context, state); + assert(state.action == ACTION_MANAGE); } @Override @@ -192,6 +204,11 @@ public abstract class FragmentTuner { @Override void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) {} + + @Override + public boolean enableManagedMode() { + return mState.stack.root != null && mState.stack.root.isDownloads(); + } } /** @@ -233,6 +250,17 @@ public abstract class FragmentTuner { ((BaseActivity) mContext).setRootsDrawerOpen(true); } } + + @Override + public boolean enableManagedMode() { + // When in downloads top level directory, we also show active downloads. + // And while we don't allow folders in Downloads, we do allow Zip files in + // downloads that themselves can be opened and viewed like directories. + // This method helps us understand when to kick in on those special behaviors. + return mState.stack.root != null + && mState.stack.root.isDownloads() + && mState.stack.size() == 1; + } } private static boolean isDirectory(String mimeType) { diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java index 498471e6828b..2486209d777e 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java @@ -21,6 +21,7 @@ import static com.android.documentsui.StubProvider.ROOT_1_ID; import android.os.RemoteException; import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.Suppress; import android.view.KeyEvent; import com.android.documentsui.model.RootInfo; @@ -139,6 +140,7 @@ public class FilesActivityUiTest extends ActivityTest<FilesActivity> { } // Tests that pressing tab switches focus between the roots and directory listings. + @Suppress public void testKeyboard_tab() throws Exception { bots.main.pressKey(KeyEvent.KEYCODE_TAB); bots.roots.assertHasFocus(); @@ -147,6 +149,7 @@ public class FilesActivityUiTest extends ActivityTest<FilesActivity> { } // Tests that arrow keys do not switch focus away from the dir list. + @Suppress public void testKeyboard_arrowsDirList() throws Exception { for (int i = 0; i < 10; i++) { bots.main.pressKey(KeyEvent.KEYCODE_DPAD_LEFT); diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/RootsUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/RootsUiTest.java index 73c1c5ffc955..621410a44f50 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/RootsUiTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/RootsUiTest.java @@ -20,6 +20,7 @@ import static com.android.documentsui.StubProvider.ROOT_0_ID; import static com.android.documentsui.StubProvider.ROOT_1_ID; import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.Suppress; @LargeTest public class RootsUiTest extends ActivityTest<FilesActivity> { @@ -44,6 +45,7 @@ public class RootsUiTest extends ActivityTest<FilesActivity> { assertDefaultContentOfTestDir0(); } + @Suppress public void testRootChanged_ClearSelection() throws Exception { bots.directory.selectDocument(fileName1); bots.main.assertInActionMode(true); diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java index 8c3fd904762a..a9451a6e3792 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java @@ -20,6 +20,7 @@ import static com.android.documentsui.StubProvider.ROOT_0_ID; import static com.android.documentsui.StubProvider.ROOT_1_ID; import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.Suppress; @LargeTest public class SearchViewUiTest extends ActivityTest<FilesActivity> { @@ -28,6 +29,7 @@ public class SearchViewUiTest extends ActivityTest<FilesActivity> { super(FilesActivity.class); } + @Suppress public void testSearchView_ExpandsOnClick() throws Exception { bots.main.openSearchView(); bots.main.assertSearchTextFiledAndIcon(true, false); @@ -41,6 +43,7 @@ public class SearchViewUiTest extends ActivityTest<FilesActivity> { bots.main.assertSearchTextFiledAndIcon(false, true); } + @Suppress public void testSearchView_ClearsTextOnBack() throws Exception { String query = "file2"; bots.main.openSearchView(); @@ -51,6 +54,7 @@ public class SearchViewUiTest extends ActivityTest<FilesActivity> { bots.main.assertSearchTextFiledAndIcon(false, true); } + @Suppress public void testSearch_ResultsFound() throws Exception { initTestFiles(); assertDefaultContentOfTestDir0(); @@ -68,6 +72,7 @@ public class SearchViewUiTest extends ActivityTest<FilesActivity> { bots.main.assertSearchTextField(false, query); } + @Suppress public void testSearchResultsFound_ClearsOnBack() throws Exception { initTestFiles(); assertDefaultContentOfTestDir0(); @@ -82,6 +87,7 @@ public class SearchViewUiTest extends ActivityTest<FilesActivity> { assertDefaultContentOfTestDir0(); } + @Suppress public void testSearch_NoResults() throws Exception { initTestFiles(); assertDefaultContentOfTestDir0(); @@ -101,6 +107,7 @@ public class SearchViewUiTest extends ActivityTest<FilesActivity> { bots.main.assertSearchTextField(false, query); } + @Suppress public void testSearchNoResults_ClearsOnBack() throws Exception { initTestFiles(); assertDefaultContentOfTestDir0(); @@ -116,6 +123,7 @@ public class SearchViewUiTest extends ActivityTest<FilesActivity> { assertDefaultContentOfTestDir0(); } + @Suppress public void testSearchResultsFound_ClearsOnDirectoryChange() throws Exception { initTestFiles(); assertDefaultContentOfTestDir0(); diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java index 3920c62f526e..ad4823eb086c 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java @@ -2634,6 +2634,10 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } mPrinterAvailabilityDetector.updatePrinter(currentPrinter); + + // Force a reload of the enabled print services to update + // mAdvancedPrintOptionsActivity in onLoadFinished(); + getLoaderManager().getLoader(LOADER_ID_ENABLED_PRINT_SERVICES).forceLoad(); } else if (spinner == mMediaSizeSpinner) { SpinnerItem<MediaSize> mediaItem = mMediaSizeSpinnerAdapter.getItem(position); PrintAttributes attributes = mPrintJob.getAttributes(); diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index ae2c6e71bfc3..ef397f664407 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -677,7 +677,7 @@ <!-- Services settings screen, setting option summary for the user to go to the screen to view running services --> <string name="runningservices_settings_summary">View and control currently running services</string> - <!-- Developer settings: enable WebView multiprocess name [CHAR LIMIT=30] --> + <!-- Developer settings: enable WebView multiprocess name [CHAR LIMIT=50] --> <string name="enable_webview_multiprocess">Enable multiprocess WebView</string> <!-- Developer settings: enable WebView multiprocess summary [CHAR LIMIT=60] --> <string name="enable_webview_multiprocess_desc">Run WebView renderers in an isolated process.</string> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index d89abf42a929..b79ce8027231 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -62,8 +62,9 @@ public class SettingsHelper { */ private static final ArraySet<String> sBroadcastOnRestore; static { - sBroadcastOnRestore = new ArraySet<String>(3); + sBroadcastOnRestore = new ArraySet<String>(4); sBroadcastOnRestore.add(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); + sBroadcastOnRestore.add(Settings.Secure.ENABLED_VR_LISTENERS); sBroadcastOnRestore.add(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); sBroadcastOnRestore.add(Settings.Secure.ENABLED_INPUT_METHODS); } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index a424d554729b..83974db87625 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1671,16 +1671,16 @@ public class SettingsProvider extends ContentProvider { private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper, SQLiteDatabase database, int userId) { - // Move over the global settings if owner. - if (userId == UserHandle.USER_SYSTEM) { - final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, userId); - ensureSettingsStateLocked(globalKey); - SettingsState globalSettings = mSettingsStates.get(globalKey); - migrateLegacySettingsLocked(globalSettings, database, TABLE_GLOBAL); - globalSettings.persistSyncLocked(); - } + // Move over the system settings. + final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId); + ensureSettingsStateLocked(systemKey); + SettingsState systemSettings = mSettingsStates.get(systemKey); + migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM); + systemSettings.persistSyncLocked(); // Move over the secure settings. + // Do this after System settings, since this is the first thing we check when deciding + // to skip over migration from db to xml for a secondary user. final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId); ensureSettingsStateLocked(secureKey); SettingsState secureSettings = mSettingsStates.get(secureKey); @@ -1688,12 +1688,16 @@ public class SettingsProvider extends ContentProvider { ensureSecureSettingAndroidIdSetLocked(secureSettings); secureSettings.persistSyncLocked(); - // Move over the system settings. - final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId); - ensureSettingsStateLocked(systemKey); - SettingsState systemSettings = mSettingsStates.get(systemKey); - migrateLegacySettingsLocked(systemSettings, database, TABLE_SYSTEM); - systemSettings.persistSyncLocked(); + // Move over the global settings if owner. + // Do this last, since this is the first thing we check when deciding + // to skip over migration from db to xml for owner user. + if (userId == UserHandle.USER_SYSTEM) { + final int globalKey = makeKey(SETTINGS_TYPE_GLOBAL, userId); + ensureSettingsStateLocked(globalKey); + SettingsState globalSettings = mSettingsStates.get(globalKey); + migrateLegacySettingsLocked(globalSettings, database, TABLE_GLOBAL); + globalSettings.persistSyncLocked(); + } // Drop the database as now all is moved and persisted. if (DROP_DATABASE_ON_MIGRATION) { diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index b9eee2e5d913..9697ea6f3788 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -48,6 +48,7 @@ <item type="id" name="transformation_start_y_tag"/> <item type="id" name="transformation_start_scale_x_tag"/> <item type="id" name="transformation_start_scale_y_tag"/> + <item type="id" name="custom_background_color"/> <!-- Whether the icon is from a notification for which targetSdk < L --> <item type="id" name="icon_is_pre_L"/> diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 913b2b346b34..2e94bc7e4d52 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -23,7 +23,6 @@ import android.view.ViewGroup; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; -import com.android.systemui.shortcut.ShortcutKeyDispatcher; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.ScrimController; @@ -72,8 +71,8 @@ public class SystemUIFactory { } public ScrimController createScrimController(ScrimView scrimBehind, ScrimView scrimInFront, - View headsUpScrim, boolean scrimSrcEnabled) { - return new ScrimController(scrimBehind, scrimInFront, headsUpScrim, scrimSrcEnabled); + View headsUpScrim) { + return new ScrimController(scrimBehind, scrimInFront, headsUpScrim); } public <T> T createInstance(Class<T> classType) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 54bf68bcb678..efac0fb3d90a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -35,9 +35,11 @@ import android.util.EventLog; import android.util.Log; import android.view.Display; import android.view.View; +import android.widget.Toast; import com.android.systemui.EventLogConstants; import com.android.systemui.EventLogTags; +import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.SystemUI; import com.android.systemui.recents.events.EventBus; @@ -393,28 +395,35 @@ public class Recents extends SystemUI boolean screenPinningActive = ssp.isScreenPinningActive(); boolean isTopTaskHome = topTask != null && SystemServicesProxy.isHomeStack(topTask.stackId); if (topTask != null && !isTopTaskHome && !screenPinningActive) { - if (sSystemServicesProxy.isSystemUser(currentUser)) { - mImpl.dockTopTask(topTask.id, dragMode, stackCreateMode, initialBounds); - } else { - if (mSystemToUserCallbacks != null) { - IRecentsNonSystemUserCallbacks callbacks = - mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser); - if (callbacks != null) { - try { - callbacks.dockTopTask(topTask.id, dragMode, stackCreateMode, - initialBounds); - } catch (RemoteException e) { - Log.e(TAG, "Callback failed", e); + if (topTask.isDockable) { + if (sSystemServicesProxy.isSystemUser(currentUser)) { + mImpl.dockTopTask(topTask.id, dragMode, stackCreateMode, initialBounds); + } else { + if (mSystemToUserCallbacks != null) { + IRecentsNonSystemUserCallbacks callbacks = + mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser); + if (callbacks != null) { + try { + callbacks.dockTopTask(topTask.id, dragMode, stackCreateMode, + initialBounds); + } catch (RemoteException e) { + Log.e(TAG, "Callback failed", e); + } + } else { + Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser); } - } else { - Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser); } } + mDraggingInRecentsCurrentUser = currentUser; + return true; + } else { + Toast.makeText(mContext, R.string.recents_drag_non_dockable_task_message, + Toast.LENGTH_SHORT).show(); + return false; } - mDraggingInRecentsCurrentUser = currentUser; - return true; + } else { + return false; } - return false; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index 699b85e34348..a5ed32a081d4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -916,11 +916,11 @@ public class TaskStackLayoutAlgorithm { float peekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height(); float slope = ((1f - peekHeightPct) - cpoint1Y) / (0.5f - cpoint1X); float b = 1f - slope * cpoint1X; - float cpoint2X = 0.75f; + float cpoint2X = 0.65f; float cpoint2Y = slope * cpoint2X + b; Path p = new Path(); p.moveTo(0f, 1f); - p.cubicTo(0f, 1f, cpoint1X, 1f, 0.5f, 1f - peekHeightPct); + p.cubicTo(0f, 1f, cpoint1X, cpoint1Y, 0.5f, 1f - peekHeightPct); p.cubicTo(0.5f, 1f - peekHeightPct, cpoint2X, cpoint2Y, 1f, 0f); return p; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 6abb82686c63..9da8fee137a6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -307,7 +307,16 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // measure/layout pass updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET); updateToInitialState(); - relayoutTaskViewsOnNextFrame(AnimationProps.IMMEDIATE); + relayoutTaskViews(AnimationProps.IMMEDIATE); + + // Rebind all the task views. This will not trigger new resources to be loaded unless + // they have actually changed + List<TaskView> taskViews = getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + TaskView tv = taskViews.get(i); + bindTaskView(tv, tv.getTask()); + } } } @@ -640,9 +649,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal List<TaskView> taskViews = getTaskViews(); int taskViewCount = taskViews.size(); for (int i = 0; i < taskViewCount; i++) { - final TaskView tv = taskViews.get(i); - final int taskIndex = mStack.indexOfStackTask(tv.getTask()); - final TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex); + TaskView tv = taskViews.get(i); + int taskIndex = mStack.indexOfStackTask(tv.getTask()); + TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex); if (ignoreTasksSet.contains(tv.getTask().key)) { continue; @@ -1430,8 +1439,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void onReturnViewToPool(TaskView tv) { final Task task = tv.getTask(); - // Report that this task's data is no longer being used - Recents.getTaskLoader().unloadTaskData(task); + // Unbind the task from the task view + unbindTaskView(tv, task); // Reset the view properties and view state tv.resetViewProperties(); @@ -1476,11 +1485,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Update the task views list after adding the new task view updateTaskViewsList(); - // Rebind the task and request that this task's data be filled into the TaskView - tv.onTaskBound(task); - - // Load the task data - Recents.getTaskLoader().loadTaskData(task, true /* fetchAndInvalidateThumbnails */); + // Bind the task view to the new task + bindTaskView(tv, task); // If the doze trigger has already fired, then update the state for this task view if (mUIDozeTrigger.isAsleep()) { @@ -1512,6 +1518,19 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal return (tv.getTask() == preferredData); } + private void bindTaskView(TaskView tv, Task task) { + // Rebind the task and request that this task's data be filled into the TaskView + tv.onTaskBound(task); + + // Load the task data + Recents.getTaskLoader().loadTaskData(task, true /* fetchAndInvalidateThumbnails */); + } + + private void unbindTaskView(TaskView tv, Task task) { + // Report that this task's data is no longer being used + Recents.getTaskLoader().unloadTaskData(task); + } + /**** TaskViewCallbacks Implementation ****/ @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 52f8fc89f453..863591124ff7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; +import android.graphics.Path; import android.graphics.Rect; import android.util.ArrayMap; import android.util.MutableBoolean; @@ -44,6 +45,7 @@ import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.HideRecentsEvent; import com.android.systemui.recents.events.ui.StackViewScrolledEvent; import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; +import com.android.systemui.recents.misc.FreePathInterpolator; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.Task; @@ -60,6 +62,16 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { private static final int INACTIVE_POINTER_ID = -1; private static final Interpolator STACK_TRANSFORM_INTERPOLATOR = new PathInterpolator(0.73f, 0.33f, 0.42f, 0.85f); + // The min overscroll is the amount of task progress overscroll we want / the max overscroll + // curve value below + private static final float MAX_OVERSCROLL = 0.7f / 0.3f; + private static final Interpolator OVERSCROLL_INTERP; + static { + Path OVERSCROLL_PATH = new Path(); + OVERSCROLL_PATH.moveTo(0, 0); + OVERSCROLL_PATH.cubicTo(0.2f, 0.175f, 0.25f, 0.3f, 1f, 0.3f); + OVERSCROLL_INTERP = new FreePathInterpolator(OVERSCROLL_PATH); + } Context mContext; TaskStackView mSv; @@ -245,6 +257,18 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { // of the curve, so just move the scroll proportional to that float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y); float curScrollP = mDownScrollP + deltaP; + + // Modulate the overscroll to prevent users from pulling the stack too far + float minScrollP = layoutAlgorithm.mMinScrollP; + float maxScrollP = layoutAlgorithm.mMaxScrollP; + if (curScrollP < minScrollP || curScrollP > maxScrollP) { + float clampedScrollP = Utilities.clamp(curScrollP, minScrollP, maxScrollP); + float overscrollP = (curScrollP - clampedScrollP); + float overscrollX = Math.abs(overscrollP) / MAX_OVERSCROLL; + curScrollP = clampedScrollP + (Math.signum(overscrollP) * + (OVERSCROLL_INTERP.getInterpolation(overscrollX) * MAX_OVERSCROLL)); + } + mScroller.setStackScroll(curScrollP); mStackViewScrolledEvent.updateY(y - mLastY); EventBus.getDefault().send(mStackViewScrolledEvent); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index effe581ac9ca..5652c7da7ebb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -36,7 +36,9 @@ import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.classifier.FalsingManager; import com.android.systemui.statusbar.notification.FakeShadowView; +import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; +import com.android.systemui.statusbar.stack.StackStateAnimator; /** * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer} @@ -121,6 +123,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private float mAnimationTranslationY; private boolean mDrawingAppearAnimation; private ValueAnimator mAppearAnimator; + private ValueAnimator mBackgroundColorAnimator; private float mAppearAnimationFraction = -1.0f; private float mAppearAnimationTranslation; private boolean mShowingLegacyBackground; @@ -157,6 +160,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView }; private float mShadowAlpha = 1.0f; private FakeShadowView mFakeShadow; + private int mCurrentBackgroundTint; + private int mTargetTint; + private int mStartTint; public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); @@ -457,21 +463,63 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView * Sets the tint color of the background */ public void setTintColor(int color) { + setTintColor(color, false); + } + + /** + * Sets the tint color of the background + */ + public void setTintColor(int color, boolean animated) { mBgTint = color; - updateBackgroundTint(); + updateBackgroundTint(animated); } protected void updateBackgroundTint() { - int color = getBgColor(); + updateBackgroundTint(false /* animated */); + } + + private void updateBackgroundTint(boolean animated) { + if (mBackgroundColorAnimator != null) { + mBackgroundColorAnimator.cancel(); + } int rippleColor = getRippleColor(); + mBackgroundDimmed.setRippleColor(rippleColor); + mBackgroundNormal.setRippleColor(rippleColor); + int color = calculateBgColor(); + if (!animated) { + setBackgroundTintColor(color); + } else if (color != mCurrentBackgroundTint) { + mStartTint = mCurrentBackgroundTint; + mTargetTint = color; + mBackgroundColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); + mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint, + animation.getAnimatedFraction()); + setBackgroundTintColor(newColor); + } + }); + mBackgroundColorAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + mBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR); + mBackgroundColorAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBackgroundColorAnimator = null; + } + }); + mBackgroundColorAnimator.start(); + } + } + + private void setBackgroundTintColor(int color) { + mCurrentBackgroundTint = color; if (color == mNormalColor) { // We don't need to tint a normal notification color = 0; } mBackgroundDimmed.setTint(color); mBackgroundNormal.setTint(color); - mBackgroundDimmed.setRippleColor(rippleColor); - mBackgroundNormal.setRippleColor(rippleColor); } /** @@ -773,8 +821,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView protected abstract View getContentView(); - public int getBgColor() { - if (mBgTint != 0) { + public int calculateBgColor() { + return calculateBgColor(true /* withTint */); + } + + private int calculateBgColor(boolean withTint) { + if (withTint && mBgTint != 0) { return mBgTint; } else if (mShowingLegacyBackground) { return mLegacyColor; @@ -839,7 +891,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } public boolean hasSameBgColor(ActivatableNotificationView otherView) { - return getBgColor() == otherView.getBgColor(); + return calculateBgColor() == otherView.calculateBgColor(); } @Override @@ -863,6 +915,10 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView outlineTranslation); } + public int getBackgroundColorWithoutTint() { + return calculateBgColor(false /* withTint */); + } + public interface OnActivatedListener { void onActivated(ActivatableNotificationView view); void onActivationReset(ActivatableNotificationView view); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 12a83fdb6d36..c2e1f7d79df0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -411,8 +411,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } public ExpandableNotificationRow getViewAtPosition(float y) { - if (!mIsSummaryWithChildren || !mChildrenExpanded - || (getNotificationChildren().size() == 1 && isClearable())) { + if (!mIsSummaryWithChildren || !mChildrenExpanded) { return this; } else { ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y); @@ -569,6 +568,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mPublicLayout.reInflateViews(); } + public void setContentBackground(int customBackgroundColor, boolean animate, + NotificationContentView notificationContentView) { + if (getShowingLayout() == notificationContentView) { + setTintColor(customBackgroundColor, animate); + } + } + public interface ExpansionLogger { public void logNotificationExpansion(String key, boolean userAction, boolean expanded); } @@ -1011,7 +1017,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } mPrivateLayout.updateExpandButtons(isExpandable()); updateChildrenHeaderAppearance(); - updateHeaderChildCount(); updateChildrenVisibility(); } @@ -1024,7 +1029,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { * @return whether the view state is currently expanded. */ public boolean isExpanded() { - return !mOnKeyguard + return isExpanded(false /* allowOnKeyguard */); + } + + public boolean isExpanded(boolean allowOnKeyguard) { + return (!mOnKeyguard || allowOnKeyguard) && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded()) || isUserExpanded()); } @@ -1104,7 +1113,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } else { animateShowingPublic(delay, duration); } - + NotificationContentView showingLayout = getShowingLayout(); + showingLayout.updateBackgroundColor(animated); mPrivateLayout.updateExpandButtons(isExpandable()); updateClearability(); mShowingPublicInitialized = true; @@ -1162,13 +1172,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } } - public void updateHeaderChildCount() { - if (mIsSummaryWithChildren) { - mNotificationHeader.setChildCount( - mChildrenContainer.getNotificationChildren().size()); - } - } - public static void applyTint(View v, int color) { int alpha; if (color != 0) { @@ -1280,15 +1283,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { header.reapply(getContext(), mNotificationHeader); mNotificationHeaderWrapper.notifyContentUpdated(mEntry.notification); } - updateHeaderExpandButton(); updateChildrenHeaderAppearance(); - updateHeaderChildCount(); - } - - private void updateHeaderExpandButton() { - if (mIsSummaryWithChildren) { - mNotificationHeader.setIsGroupHeader(true /* isGroupHeader*/); - } } public void updateChildrenHeaderAppearance() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index b94c15b1d191..3b875774681e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -33,6 +33,7 @@ import com.android.systemui.R; import com.android.systemui.statusbar.notification.HybridNotificationView; import com.android.systemui.statusbar.notification.HybridNotificationViewManager; import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper; +import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.NotificationViewWrapper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.RemoteInputView; @@ -48,7 +49,7 @@ public class NotificationContentView extends FrameLayout { private static final int VISIBLE_TYPE_EXPANDED = 1; private static final int VISIBLE_TYPE_HEADSUP = 2; private static final int VISIBLE_TYPE_SINGLELINE = 3; - private static final int UNDEFINED = -1; + public static final int UNDEFINED = -1; private final Rect mClipBounds = new Rect(); private final int mMinContractedHeight; @@ -367,6 +368,7 @@ public class NotificationContentView extends FrameLayout { getViewForVisibleType(visibleType).setVisibility(View.VISIBLE); hiddenView.transformTo(shownView, 0.0f); mVisibleType = visibleType; + updateBackgroundColor(true /* animate */); } if (mTransformationStartVisibleType != UNDEFINED && mVisibleType != mTransformationStartVisibleType) { @@ -376,11 +378,29 @@ public class NotificationContentView extends FrameLayout { float transformationAmount = calculateTransformationAmount(); shownView.transformFrom(hiddenView, transformationAmount); hiddenView.transformTo(shownView, transformationAmount); + updateBackgroundTransformation(transformationAmount); } else { updateViewVisibilities(visibleType); + updateBackgroundColor(false); } } + private void updateBackgroundTransformation(float transformationAmount) { + int endColor = getBackgroundColor(mVisibleType); + int startColor = getBackgroundColor(mTransformationStartVisibleType); + if (endColor != startColor) { + if (startColor == 0) { + startColor = mContainingNotification.getBackgroundColorWithoutTint(); + } + if (endColor == 0) { + endColor = mContainingNotification.getBackgroundColorWithoutTint(); + } + endColor = NotificationUtils.interpolateColors(startColor, endColor, + transformationAmount); + } + mContainingNotification.setContentBackground(endColor, false, this); + } + private float calculateTransformationAmount() { int startHeight = getViewForVisibleType(mTransformationStartVisibleType).getHeight(); int endHeight = getViewForVisibleType(mVisibleType).getHeight(); @@ -457,7 +477,22 @@ public class NotificationContentView extends FrameLayout { updateViewVisibilities(visibleType); } mVisibleType = visibleType; + updateBackgroundColor(animate); + } + } + + public void updateBackgroundColor(boolean animate) { + int customBackgroundColor = getBackgroundColor(mVisibleType); + mContainingNotification.setContentBackground(customBackgroundColor, animate, this); + } + + private int getBackgroundColor(int visibleType) { + NotificationViewWrapper currentVisibleWrapper = getVisibleWrapper(visibleType); + int customBackgroundColor = 0; + if (currentVisibleWrapper != null) { + customBackgroundColor = currentVisibleWrapper.getCustomBackgroundColor(); } + return customBackgroundColor; } private void updateViewVisibilities(int visibleType) { @@ -530,8 +565,8 @@ public class NotificationContentView extends FrameLayout { } } - private NotificationViewWrapper getCurrentVisibleWrapper() { - switch (mVisibleType) { + private NotificationViewWrapper getVisibleWrapper(int visibleType) { + switch (visibleType) { case VISIBLE_TYPE_EXPANDED: return mExpandedWrapper; case VISIBLE_TYPE_HEADSUP: @@ -549,7 +584,7 @@ public class NotificationContentView extends FrameLayout { private int calculateVisibleType() { if (mUserExpanding) { int height = !mIsChildInGroup || isGroupExpanded() - || mContainingNotification.isExpanded() + || mContainingNotification.isExpanded(true /* allowOnKeyguard */) ? mContainingNotification.getMaxContentHeight() : mContainingNotification.getShowingLayout().getMinHeight(); if (height == 0) { @@ -588,7 +623,8 @@ public class NotificationContentView extends FrameLayout { } } else { if (noExpandedChild || (viewHeight <= mContractedChild.getHeight() - && (!mIsChildInGroup || !mContainingNotification.isExpanded()))) { + && (!mIsChildInGroup + || !mContainingNotification.isExpanded(true /* allowOnKeyguard */)))) { return VISIBLE_TYPE_CONTRACTED; } else { return VISIBLE_TYPE_EXPANDED; @@ -605,7 +641,6 @@ public class NotificationContentView extends FrameLayout { return; } mDark = dark; - dark = dark && !mShowingLegacyBackground; if (mVisibleType == VISIBLE_TYPE_CONTRACTED || !dark) { mContractedWrapper.setDark(dark, fade, delay); } @@ -636,6 +671,19 @@ public class NotificationContentView extends FrameLayout { public void setShowingLegacyBackground(boolean showing) { mShowingLegacyBackground = showing; + updateShowingLegacyBackground(); + } + + private void updateShowingLegacyBackground() { + if (mContractedChild != null) { + mContractedWrapper.setShowingLegacyBackground(mShowingLegacyBackground); + } + if (mExpandedChild != null) { + mExpandedWrapper.setShowingLegacyBackground(mShowingLegacyBackground); + } + if (mHeadsUpChild != null) { + mHeadsUpWrapper.setShowingLegacyBackground(mShowingLegacyBackground); + } } public void setIsChildInGroup(boolean isChildInGroup) { @@ -657,6 +705,7 @@ public class NotificationContentView extends FrameLayout { if (mHeadsUpChild != null) { mHeadsUpWrapper.notifyContentUpdated(entry.notification); } + updateShowingLegacyBackground(); selectLayout(false /* animate */, true /* force */); setDark(mDark, false /* animate */, 0 /* delay */); } @@ -778,7 +827,7 @@ public class NotificationContentView extends FrameLayout { } public NotificationHeaderView getVisibleNotificationHeader() { - NotificationViewWrapper wrapper = getCurrentVisibleWrapper(); + NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType); return wrapper == null ? null : wrapper.getNotificationHeader(); } @@ -806,6 +855,7 @@ public class NotificationContentView extends FrameLayout { mTransformationStartVisibleType = UNDEFINED; mVisibleType = calculateVisibleType(); updateViewVisibilities(mVisibleType); + updateBackgroundColor(false); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java index 7f8f20f7a8f0..49e4ba8a83f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java @@ -16,8 +16,19 @@ package com.android.systemui.statusbar.notification; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.graphics.Color; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.service.notification.StatusBarNotification; +import android.support.v4.graphics.ColorUtils; import android.view.View; +import com.android.systemui.R; import com.android.systemui.ViewInvertHelper; import com.android.systemui.statusbar.phone.NotificationPanelView; @@ -27,6 +38,11 @@ import com.android.systemui.statusbar.phone.NotificationPanelView; public class NotificationCustomViewWrapper extends NotificationViewWrapper { private final ViewInvertHelper mInvertHelper; + private final Paint mGreyPaint = new Paint(); + private int mBackgroundColor = 0; + private static final int CUSTOM_BACKGROUND_TAG = R.id.custom_background_color; + private boolean mShouldInvertDark; + private boolean mShowingLegacyBackground; protected NotificationCustomViewWrapper(View view) { super(view); @@ -39,10 +55,46 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { return; } super.setDark(dark, fade, delay); - if (fade) { - mInvertHelper.fade(dark, delay); + if (!mShowingLegacyBackground && mShouldInvertDark) { + if (fade) { + mInvertHelper.fade(dark, delay); + } else { + mInvertHelper.update(dark); + } } else { - mInvertHelper.update(dark); + mView.setLayerType(dark ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE, null); + if (fade) { + fadeGrayscale(dark, delay); + } else { + updateGrayscale(dark); + } + } + } + + protected void fadeGrayscale(final boolean dark, long delay) { + startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + updateGrayscaleMatrix((float) animation.getAnimatedValue()); + mGreyPaint.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix)); + mView.setLayerPaint(mGreyPaint); + } + }, dark, delay, new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (!dark) { + mView.setLayerType(View.LAYER_TYPE_NONE, null); + } + } + }); + } + + protected void updateGrayscale(boolean dark) { + if (dark) { + updateGrayscaleMatrix(1f); + mGreyPaint.setColorFilter( + new ColorMatrixColorFilter(mGrayscaleColorMatrix)); + mView.setLayerPaint(mGreyPaint); } } @@ -51,4 +103,35 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { super.setVisible(visible); mView.setAlpha(visible ? 1.0f : 0.0f); } + + @Override + public void notifyContentUpdated(StatusBarNotification notification) { + super.notifyContentUpdated(notification); + Drawable background = mView.getBackground(); + mBackgroundColor = 0; + if (background instanceof ColorDrawable) { + mBackgroundColor = ((ColorDrawable) background).getColor(); + mView.setBackground(null); + mView.setTag(CUSTOM_BACKGROUND_TAG, mBackgroundColor); + } else if (mView.getTag(CUSTOM_BACKGROUND_TAG) != null) { + mBackgroundColor = (int) mView.getTag(CUSTOM_BACKGROUND_TAG); + } + mShouldInvertDark = mBackgroundColor == 0 || isColorLight(mBackgroundColor); + } + + private boolean isColorLight(int backgroundColor) { + return Color.alpha(backgroundColor) == 0 + || ColorUtils.calculateLuminance(backgroundColor) > 0.5; + } + + @Override + public int getCustomBackgroundColor() { + return mBackgroundColor; + } + + @Override + public void setShowingLegacyBackground(boolean showing) { + super.setShowingLegacyBackground(showing); + mShowingLegacyBackground = showing; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java index 842bd22cf4cc..b201d8fe68d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java @@ -47,7 +47,6 @@ import java.util.Stack; */ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { - private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix(); private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter( 0, PorterDuff.Mode.SRC_ATOP); private final int mIconDarkAlpha; @@ -178,21 +177,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } } - protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener, - boolean dark, long delay, Animator.AnimatorListener listener) { - float startIntensity = dark ? 0f : 1f; - float endIntensity = dark ? 1f : 0f; - ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity); - animator.addUpdateListener(updateListener); - animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION); - animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - animator.setStartDelay(delay); - if (listener != null) { - animator.addListener(listener); - } - animator.start(); - } - private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) { startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() { @Override @@ -264,10 +248,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { mNotificationHeader.setOnClickListener(expandable ? onClickListener : null); } - private void updateGrayscaleMatrix(float intensity) { - mGrayscaleColorMatrix.setSaturation(1 - intensity); - } - private static int interpolateColor(int source, int target, float t) { int aSource = Color.alpha(source); int rSource = Color.red(source); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java index 47386575de4f..6ef61ec337c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification; +import android.graphics.Color; import android.widget.ImageView; import com.android.internal.util.NotificationColorUtil; @@ -38,4 +39,12 @@ public class NotificationUtils { public static float interpolate(float start, float end, float amount) { return start * (1.0f - amount) + end * amount; } + + public static int interpolateColors(int startColor, int endColor, float amount) { + return Color.argb( + (int) interpolate(Color.alpha(startColor), Color.alpha(endColor), amount), + (int) interpolate(Color.red(startColor), Color.red(endColor), amount), + (int) interpolate(Color.green(startColor), Color.green(endColor), amount), + (int) interpolate(Color.blue(startColor), Color.blue(endColor), amount)); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java index 0df0d26aea10..ebff69da46d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java @@ -16,13 +16,19 @@ package com.android.systemui.statusbar.notification; +import android.animation.Animator; +import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.ColorMatrix; import android.service.notification.StatusBarNotification; import android.view.NotificationHeaderView; import android.view.View; +import com.android.systemui.Interpolators; import com.android.systemui.statusbar.CrossFadeHelper; +import com.android.systemui.statusbar.NotificationContentView; import com.android.systemui.statusbar.TransformableView; +import com.android.systemui.statusbar.phone.NotificationPanelView; /** * Wraps the actual notification content view; used to implement behaviors which are different for @@ -30,6 +36,7 @@ import com.android.systemui.statusbar.TransformableView; */ public abstract class NotificationViewWrapper implements TransformableView { + protected final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix(); protected final View mView; protected boolean mDark; protected boolean mDarkInitialized = false; @@ -75,6 +82,26 @@ public abstract class NotificationViewWrapper implements TransformableView { mDarkInitialized = false; }; + + protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener, + boolean dark, long delay, Animator.AnimatorListener listener) { + float startIntensity = dark ? 0f : 1f; + float endIntensity = dark ? 1f : 0f; + ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity); + animator.addUpdateListener(updateListener); + animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION); + animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); + animator.setStartDelay(delay); + if (listener != null) { + animator.addListener(listener); + } + animator.start(); + } + + protected void updateGrayscaleMatrix(float intensity) { + mGrayscaleColorMatrix.setSaturation(1 - intensity); + } + /** * Update the appearance of the expand button. * @@ -122,4 +149,11 @@ public abstract class NotificationViewWrapper implements TransformableView { mView.animate().cancel(); mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); } + + public int getCustomBackgroundColor() { + return 0; + } + + public void setShowingLegacyBackground(boolean showing) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 9c6f2c53f9e3..d50e67aaefbe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -92,6 +92,7 @@ public class NotificationPanelView extends PanelView implements private KeyguardUserSwitcher mKeyguardUserSwitcher; private KeyguardStatusBarView mKeyguardStatusBar; protected QSContainer mQsContainer; + private DensityContainer mQsDensityContainer; private KeyguardStatusView mKeyguardStatusView; private TextView mClockView; private View mReserveNotificationSpace; @@ -217,8 +218,8 @@ public class NotificationPanelView extends PanelView implements super.onFinishInflate(); mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header); mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view); - DensityContainer container = (DensityContainer) findViewById(R.id.qs_density_container); - container.addInflateListener(new InflateListener() { + mQsDensityContainer = (DensityContainer) findViewById(R.id.qs_density_container); + mQsDensityContainer.addInflateListener(new InflateListener() { @Override public void onInflated(View v) { mQsContainer = (QSContainer) v.findViewById(R.id.quick_settings_container); @@ -2209,7 +2210,7 @@ public class NotificationPanelView extends PanelView implements protected void setVerticalPanelTranslation(float translation) { mNotificationStackScroller.setTranslationX(translation); - mQsContainer.setTranslationX(translation); + mQsDensityContainer.setTranslationX(translation); } protected void updateStackHeight(float stackHeight) { 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 073a8484dc30..7856f6cfbdf6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -772,10 +772,21 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front); View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim); mScrimController = SystemUIFactory.getInstance().createScrimController( - scrimBehind, scrimInFront, headsUpScrim, mScrimSrcModeEnabled); + scrimBehind, scrimInFront, headsUpScrim); + if (mScrimSrcModeEnabled) { + Runnable runnable = new Runnable() { + @Override + public void run() { + boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE; + mScrimController.setDrawBehindAsSrc(asSrc); + mStackScroller.setDrawBackgroundAsSrc(asSrc); + } + }; + mBackdrop.setOnVisibilityChangedRunnable(runnable); + runnable.run(); + } mHeadsUpManager.addListener(mScrimController); mStackScroller.setScrimController(mScrimController); - mScrimController.setBackDropView(mBackdrop); mStatusBarView.setScrimController(mScrimController); mDozeScrimController = new DozeScrimController(mScrimController, context); @@ -1466,6 +1477,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStackScroller.removeView(remove); mStackScroller.setChildTransferInProgress(false); } + + removeNotificationChildren(); + for (int i=0; i<toShow.size(); i++) { View v = toShow.get(i); if (v.getParent() == null) { @@ -1473,6 +1487,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } + addNotificationChildrenAndSort(); + // So after all this work notifications still aren't sorted correctly. // Let's do that now by advancing through toShow and mStackScroller in // lock-step, making sure mStackScroller matches what we see in toShow. @@ -1494,9 +1510,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } - // lets handle the child notifications now - updateNotificationShadeForChildren(); - // clear the map again for the next usage mTmpChildOrderMap.clear(); @@ -1522,9 +1535,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, && !ONLY_CORE_APPS); } - private void updateNotificationShadeForChildren() { - // First let's remove all children which don't belong in the parents - ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); + private void addNotificationChildrenAndSort() { + // Let's now add all notification children which are missing + boolean orderChanged = false; for (int i = 0; i < mStackScroller.getChildCount(); i++) { View view = mStackScroller.getChildAt(i); if (!(view instanceof ExpandableNotificationRow)) { @@ -1536,22 +1549,26 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, List<ExpandableNotificationRow> children = parent.getNotificationChildren(); List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); - if (children != null) { - toRemove.clear(); - for (ExpandableNotificationRow childRow : children) { - if (orderedChildren == null || !orderedChildren.contains(childRow)) { - toRemove.add(childRow); - } - } - for (ExpandableNotificationRow remove : toRemove) { - parent.removeChildNotification(remove); - mStackScroller.notifyGroupChildRemoved(remove); + for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size(); + childIndex++) { + ExpandableNotificationRow childView = orderedChildren.get(childIndex); + if (children == null || !children.contains(childView)) { + parent.addChildNotification(childView, childIndex); + mStackScroller.notifyGroupChildAdded(childView); } } + + // Finally after removing and adding has been beformed we can apply the order. + orderChanged |= parent.applyChildOrder(orderedChildren); } + if (orderChanged) { + mStackScroller.generateChildOrderChangedEvent(); + } + } - // Let's now add all notification children which are missing - boolean orderChanged = false; + private void removeNotificationChildren() { + // First let's remove all children which don't belong in the parents + ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); for (int i = 0; i < mStackScroller.getChildCount(); i++) { View view = mStackScroller.getChildAt(i); if (!(view instanceof ExpandableNotificationRow)) { @@ -1563,20 +1580,22 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, List<ExpandableNotificationRow> children = parent.getNotificationChildren(); List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); - for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size(); - childIndex++) { - ExpandableNotificationRow childView = orderedChildren.get(childIndex); - if (children == null || !children.contains(childView)) { - parent.addChildNotification(childView, childIndex); - mStackScroller.notifyGroupChildAdded(childView); + if (children != null) { + toRemove.clear(); + for (ExpandableNotificationRow childRow : children) { + if (orderedChildren == null || !orderedChildren.contains(childRow)) { + toRemove.add(childRow); + } + } + for (ExpandableNotificationRow remove : toRemove) { + parent.removeChildNotification(remove); + if (mNotificationData.get(remove.getStatusBarNotification().getKey()) == null) { + // We only want to add an animation if the view is completely removed + // otherwise it's just a transfer + mStackScroller.notifyGroupChildRemoved(remove); + } } } - - // Finally after removing and adding has been beformed we can apply the order. - orderChanged |= parent.applyChildOrder(orderedChildren); - } - if (orderChanged) { - mStackScroller.generateChildOrderChangedEvent(); } } 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 fe76ae751520..1a557e428fe3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -30,7 +30,6 @@ import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import com.android.systemui.R; -import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.ScrimView; @@ -75,8 +74,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private long mAnimationDelay; private Runnable mOnAnimationFinished; private final Interpolator mInterpolator = new DecelerateInterpolator(); - private BackDropView mBackDropView; - private boolean mScrimSrcEnabled; private boolean mDozing; private float mDozeInFrontAlpha; private float mDozeBehindAlpha; @@ -90,14 +87,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private boolean mSkipFirstFrame; private boolean mDontAnimateBouncerChanges; - public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim, - boolean scrimSrcEnabled) { + public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim) { mScrimBehind = scrimBehind; mScrimInFront = scrimInFront; mHeadsUpScrim = headsUpScrim; final Context context = scrimBehind.getContext(); mUnlockMethodCache = UnlockMethodCache.getInstance(context); - mScrimSrcEnabled = scrimSrcEnabled; updateHeadsUpScrim(false); } @@ -378,19 +373,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, return scrim.getTag(TAG_KEY_ANIM) != null; } - public void setBackDropView(BackDropView backDropView) { - mBackDropView = backDropView; - mBackDropView.setOnVisibilityChangedRunnable(new Runnable() { - @Override - public void run() { - updateScrimBehindDrawingMode(); - } - }); - updateScrimBehindDrawingMode(); - } - - private void updateScrimBehindDrawingMode() { - boolean asSrc = mBackDropView.getVisibility() != View.VISIBLE && mScrimSrcEnabled; + public void setDrawBehindAsSrc(boolean asSrc) { mScrimBehind.setDrawAsSrc(asSrc); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java index 795573352ba8..9d50ab44ffa6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java @@ -511,7 +511,7 @@ public class NotificationChildrenContainer extends ViewGroup { break; } ExpandableNotificationRow child = mChildren.get(i); - float childHeight = child.isExpanded() + float childHeight = child.isExpanded(true /* allowOnKeyguard */) ? child.getMaxExpandHeight() : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */); maxContentHeight += childHeight; @@ -532,7 +532,7 @@ public class NotificationChildrenContainer extends ViewGroup { int childCount = mChildren.size(); for (int i = 0; i < childCount; i++) { ExpandableNotificationRow child = mChildren.get(i); - float childHeight = child.isExpanded() + float childHeight = child.isExpanded(true /* allowOnKeyguard */) ? child.getMaxExpandHeight() : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */); float singleLineHeight = child.getShowingLayout().getMinHeight( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index aa444f5634b3..cca374602b63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.stack; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.TimeAnimator; @@ -324,6 +323,7 @@ public class NotificationStackScrollLayout extends ViewGroup } } }; + private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); public NotificationStackScrollLayout(Context context) { this(context, null); @@ -359,7 +359,6 @@ public class NotificationStackScrollLayout extends ViewGroup mDebugPaint.setStyle(Paint.Style.STROKE); } mFalsingManager = FalsingManager.getInstance(context); - mBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); } @Override @@ -427,6 +426,11 @@ public class NotificationStackScrollLayout extends ViewGroup R.dimen.min_top_overscroll_to_qs); } + public void setDrawBackgroundAsSrc(boolean asSrc) { + mBackgroundPaint.setXfermode(asSrc ? mSrcMode : null); + invalidate(); + } + private void notifyHeightChangeListener(ExpandableView view) { if (mOnHeightChangedListener != null) { mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */); @@ -774,12 +778,15 @@ public class NotificationStackScrollLayout extends ViewGroup if (child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; ExpandableNotificationRow parent = row.getNotificationParent(); - if (mGearExposedView != null && parent != null - && parent.areChildrenExpanded() && mGearExposedView == parent) { + if (parent != null && parent.areChildrenExpanded() + && (mGearExposedView == parent + || (parent.getNotificationChildren().size() == 1 + && parent.isClearable()))) { // In this case the group is expanded and showing the gear for the // group, further interaction should apply to the group, not any - // child notifications so we use the parent of the child. - child = row.getNotificationParent(); + // child notifications so we use the parent of the child. We also do the same + // if we only have a single child. + child = parent; } } return child; @@ -857,7 +864,7 @@ public class NotificationStackScrollLayout extends ViewGroup public boolean canChildBeExpanded(View v) { return v instanceof ExpandableNotificationRow && ((ExpandableNotificationRow) v).isExpandable() - && !((ExpandableNotificationRow) v).isHeadsUp(); + && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned()); } public void setUserExpandedChild(View v, boolean userExpanded) { diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index f3140d28ad3a..65654a8d4270 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -1953,8 +1953,10 @@ message MetricsEvent { // a notification. ACTION_TOUCH_GEAR = 333; + // Logs that the user has edited the enabled VR listeners. + VR_MANAGE_LISTENERS = 334; + // Add new aosp constants above this line. // END OF AOSP CONSTANTS - } } diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 3b6c62b5f111..a93b4d8bc250 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -39,6 +39,7 @@ import org.xmlpull.v1.XmlSerializer; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.ActivityManagerNative; import android.app.AlertDialog; import android.app.AppGlobals; @@ -47,7 +48,6 @@ import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.SynchronousUserSwitchObserver; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -465,7 +465,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub super(handler); } - public void registerContentObserverLocked(int userId) { + public void registerContentObserverLocked(@UserIdInt int userId) { if (mRegistered && mUserId == userId) { return; } @@ -774,7 +774,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override + public void onSwitchUser(@UserIdInt int userHandle) { + // Called on the system server's main looper thread. + // TODO: Dispatch this to a worker thread as needed. + mService.onSwitchUser(userHandle); + } + + @Override public void onBootPhase(int phase) { + // Called on the system server's main looper thread. + // TODO: Dispatch this to a worker thread as needed. if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { StatusBarManagerService statusBarService = (StatusBarManagerService) ServiceManager .getService(Context.STATUS_BAR_SERVICE); @@ -783,12 +792,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void onUnlockUser(int userHandle) { + public void onUnlockUser(@UserIdInt int userHandle) { + // Called on the system server's main looper thread. + // TODO: Dispatch this to a worker thread as needed. mService.onUnlockUser(userHandle); } } - public void onUnlockUser(int userId) { + void onUnlockUser(@UserIdInt int userId) { synchronized(mMethodMap) { final int currentUserId = mSettings.getCurrentUserId(); if (DEBUG) { @@ -804,6 +815,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + void onSwitchUser(@UserIdInt int userId) { + synchronized (mMethodMap) { + switchUserLocked(userId); + } + } + public InputMethodManagerService(Context context) { mIPackageManager = AppGlobals.getPackageManager(); mContext = context; @@ -852,25 +869,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mNotificationShown = false; int userId = 0; try { - ActivityManagerNative.getDefault().registerUserSwitchObserver( - new SynchronousUserSwitchObserver() { - @Override - public void onUserSwitching(int newUserId) - throws RemoteException { - synchronized(mMethodMap) { - switchUserLocked(newUserId); - } - } - - @Override - public void onUserSwitchComplete(int newUserId) throws RemoteException { - } - - @Override - public void onForegroundProfileSwitch(int newProfileId) { - // Ignore. - } - }); userId = ActivityManagerNative.getDefault().getCurrentUser().id; } catch (RemoteException e) { Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e); diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java index e3af7e377733..3f453dc64646 100644 --- a/services/core/java/com/android/server/TextServicesManagerService.java +++ b/services/core/java/com/android/server/TextServicesManagerService.java @@ -1051,6 +1051,30 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { mCurrentUserId = userId; } + private void putString(final String key, final String str) { + Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId); + } + + private String getString(final String key) { + return Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId); + } + + private void putInt(final String key, final int value) { + Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId); + } + + private int getInt(final String key, final int defaultValue) { + return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId); + } + + private void putBoolean(final String key, final boolean value) { + putInt(key, value ? 1 : 0); + } + + private boolean getBoolean(final String key, final boolean defaultValue) { + return getInt(key, defaultValue ? 1 : 0) == 1; + } + public void setCurrentProfileIds(int[] currentProfileIds) { synchronized (mLock) { mCurrentProfileIds = currentProfileIds; @@ -1073,34 +1097,27 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { } public void putSelectedSpellChecker(String sciId) { - Settings.Secure.putStringForUser(mResolver, - Settings.Secure.SELECTED_SPELL_CHECKER, sciId, mCurrentUserId); + putString(Settings.Secure.SELECTED_SPELL_CHECKER, sciId); } public void putSelectedSpellCheckerSubtype(int hashCode) { - Settings.Secure.putStringForUser(mResolver, - Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, String.valueOf(hashCode), - mCurrentUserId); + putString(Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, String.valueOf(hashCode)); } public void setSpellCheckerEnabled(boolean enabled) { - Settings.Secure.putIntForUser(mResolver, - Settings.Secure.SPELL_CHECKER_ENABLED, enabled ? 1 : 0, mCurrentUserId); + putBoolean(Settings.Secure.SPELL_CHECKER_ENABLED, enabled); } public String getSelectedSpellChecker() { - return Settings.Secure.getStringForUser(mResolver, - Settings.Secure.SELECTED_SPELL_CHECKER, mCurrentUserId); + return getString(Settings.Secure.SELECTED_SPELL_CHECKER); } public String getSelectedSpellCheckerSubtype() { - return Settings.Secure.getStringForUser(mResolver, - Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, mCurrentUserId); + return getString(Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE); } public boolean isSpellCheckerEnabled() { - return Settings.Secure.getIntForUser(mResolver, - Settings.Secure.SPELL_CHECKER_ENABLED, 1, mCurrentUserId) == 1; + return getBoolean(Settings.Secure.SPELL_CHECKER_ENABLED, true); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 7e2e63e7a980..eddc4419ba5f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2156,15 +2156,20 @@ public final class ActivityManagerService extends ActivityManagerNative } break; case VR_MODE_CHANGE_MSG: { VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class); - final boolean vrMode = msg.arg1 != 0; - vrService.setVrMode(vrMode); - - if (mInVrMode != vrMode) { - synchronized (ActivityManagerService.this) { + final ActivityRecord r = (ActivityRecord) msg.obj; + boolean vrMode; + ComponentName requestedPackage; + int userId; + synchronized (ActivityManagerService.this) { + vrMode = r.requestedVrComponent != null; + requestedPackage = r.requestedVrComponent; + userId = r.userId; + if (mInVrMode != vrMode) { mInVrMode = vrMode; mShowDialogs = shouldShowDialogs(mConfiguration, mInVrMode); } } + vrService.setVrMode(vrMode, requestedPackage, userId); } break; } } @@ -2937,7 +2942,7 @@ public final class ActivityManagerService extends ActivityManagerNative final void applyUpdateVrModeLocked(ActivityRecord r) { mHandler.sendMessage( - mHandler.obtainMessage(VR_MODE_CHANGE_MSG, (r.isVrActivity) ? 1 : 0, 0)); + mHandler.obtainMessage(VR_MODE_CHANGE_MSG, 0, 0, r)); } final void showAskCompatModeDialogLocked(ActivityRecord r) { @@ -12030,23 +12035,49 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override - public void setVrMode(IBinder token, boolean enabled) { + public int setVrMode(IBinder token, boolean enabled, ComponentName packageName) { if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) { throw new UnsupportedOperationException("VR mode not supported on this device!"); } + final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class); + + ActivityRecord r; + synchronized (this) { + r = ActivityRecord.isInStackLocked(token); + } + + if (r == null) { + throw new IllegalArgumentException(); + } + + int err; + if ((err = vrService.hasVrPackage(packageName, r.userId)) != + VrManagerInternal.NO_ERROR) { + return err; + } + synchronized(this) { - final ActivityRecord r = ActivityRecord.isInStackLocked(token); - if (r == null) { - throw new IllegalArgumentException(); - } - r.isVrActivity = enabled; + r.requestedVrComponent = (enabled) ? packageName : null; // Update associated state if this activity is currently focused if (r == mFocusedActivity) { applyUpdateVrModeLocked(r); } + return 0; + } + } + + @Override + public boolean isVrModePackageEnabled(ComponentName packageName) { + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) { + throw new UnsupportedOperationException("VR mode not supported on this device!"); } + + final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class); + + return vrService.hasVrPackage(packageName, UserHandle.getCallingUserId()) == + VrManagerInternal.NO_ERROR; } public boolean isTopActivityImmersive() { diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index fa59e6ab5957..5219827d2f8e 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -123,7 +123,7 @@ final class ActivityRecord { final boolean stateNotNeeded; // As per ActivityInfo.flags boolean fullscreen; // covers the full screen? final boolean noDisplay; // activity is not displayed? - final boolean componentSpecified; // did caller specifiy an explicit component? + final boolean componentSpecified; // did caller specify an explicit component? final boolean rootVoiceInteraction; // was this the root activity of a voice interaction? static final int APPLICATION_ACTIVITY_TYPE = 0; @@ -190,7 +190,7 @@ final class ActivityRecord { boolean forceNewConfig; // force re-create with new config next time int launchCount; // count of launches since last state long lastLaunchTime; // time of last launch of this activity - boolean isVrActivity; // is the activity running in VR mode? + ComponentName requestedVrComponent; // the requested component for handling VR mode. ArrayList<ActivityContainer> mChildContainers = new ArrayList<>(); String stringName; // for caching of toString(). @@ -354,7 +354,11 @@ final class ActivityRecord { pw.print(" forceNewConfig="); pw.println(forceNewConfig); pw.print(prefix); pw.print("mActivityType="); pw.println(activityTypeToString(mActivityType)); - pw.print(prefix); pw.print("vrMode="); pw.println(isVrActivity); + if (requestedVrComponent != null) { + pw.print(prefix); + pw.print("requestedVrComponent="); + pw.println(requestedVrComponent); + } if (displayStartTime != 0 || startTime != 0) { pw.print(prefix); pw.print("displayStartTime="); if (displayStartTime == 0) pw.print("0"); @@ -718,7 +722,6 @@ final class ActivityRecord { } immersive = (aInfo.flags & ActivityInfo.FLAG_IMMERSIVE) != 0; - isVrActivity = (aInfo.flags & ActivityInfo.FLAG_ENABLE_VR_MODE) != 0; } else { realActivity = null; taskAffinity = null; @@ -730,7 +733,6 @@ final class ActivityRecord { noDisplay = false; mActivityType = APPLICATION_ACTIVITY_TYPE; immersive = false; - isVrActivity = false; } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index f2a9c2c9bff7..d919737c28e2 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -58,7 +58,7 @@ import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioManagerInternal; import android.media.AudioPort; -import android.media.AudioRecordConfiguration; +import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; import android.media.IAudioFocusDispatcher; import android.media.IAudioRoutesObserver; @@ -6287,8 +6287,8 @@ public class AudioService extends IAudioService.Stub { mRecordMonitor.unregisterRecordingCallback(rcdb); } - public AudioRecordConfiguration[] getActiveRecordConfigurations() { - return mRecordMonitor.getActiveRecordConfigurations(); + public AudioRecordingConfiguration[] getActiveRecordingConfigurations() { + return mRecordMonitor.getActiveRecordingConfigurations(); } //====================== diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java index 86dcd0f1f0f2..7a085e143599 100644 --- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java +++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java @@ -18,7 +18,7 @@ package com.android.server.audio; import android.media.AudioFormat; import android.media.AudioManager; -import android.media.AudioRecordConfiguration; +import android.media.AudioRecordingConfiguration; import android.media.AudioSystem; import android.media.IRecordingConfigDispatcher; import android.media.MediaRecorder; @@ -39,8 +39,8 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin private ArrayList<RecMonitorClient> mClients = new ArrayList<RecMonitorClient>(); - private HashMap<Integer, AudioRecordConfiguration> mRecordConfigs = - new HashMap<Integer, AudioRecordConfiguration>(); + private HashMap<Integer, AudioRecordingConfiguration> mRecordConfigs = + new HashMap<Integer, AudioRecordingConfiguration>(); RecordingActivityMonitor() { RecMonitorClient.sMonitor = this; @@ -54,7 +54,7 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin if (MediaRecorder.isSystemOnlyAudioSource(source)) { return; } - final AudioRecordConfiguration[] configs = + final AudioRecordingConfiguration[] configs = updateSnapshot(event, session, source, recordingInfo); if (configs != null){ synchronized(mClients) { @@ -104,9 +104,9 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin } } - AudioRecordConfiguration[] getActiveRecordConfigurations() { + AudioRecordingConfiguration[] getActiveRecordingConfigurations() { synchronized(mRecordConfigs) { - return mRecordConfigs.values().toArray(new AudioRecordConfiguration[0]); + return mRecordConfigs.values().toArray(new AudioRecordingConfiguration[0]); } } @@ -121,10 +121,10 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin * @return null if the list of active recording sessions has not been modified, an array * with the current active configurations otherwise. */ - private AudioRecordConfiguration[] updateSnapshot(int event, int session, int source, + private AudioRecordingConfiguration[] updateSnapshot(int event, int session, int source, int[] recordingInfo) { final boolean configChanged; - final AudioRecordConfiguration[] configs; + final AudioRecordingConfiguration[] configs; synchronized(mRecordConfigs) { switch (event) { case AudioManager.RECORD_CONFIG_EVENT_STOP: @@ -147,8 +147,8 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin final int patchHandle = recordingInfo[6]; final Integer sessionKey = new Integer(session); if (mRecordConfigs.containsKey(sessionKey)) { - final AudioRecordConfiguration updatedConfig = - new AudioRecordConfiguration(session, source, + final AudioRecordingConfiguration updatedConfig = + new AudioRecordingConfiguration(session, source, clientFormat, deviceFormat, patchHandle); if (updatedConfig.equals(mRecordConfigs.get(sessionKey))) { configChanged = false; @@ -160,7 +160,7 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin } } else { mRecordConfigs.put(sessionKey, - new AudioRecordConfiguration(session, source, + new AudioRecordingConfiguration(session, source, clientFormat, deviceFormat, patchHandle)); configChanged = true; } @@ -171,7 +171,7 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin configChanged = false; } if (configChanged) { - configs = mRecordConfigs.values().toArray(new AudioRecordConfiguration[0]); + configs = mRecordConfigs.values().toArray(new AudioRecordingConfiguration[0]); } else { configs = null; } diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index 9820a1261684..c19b51f54a31 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -16,6 +16,7 @@ package com.android.server.notification; +import android.app.AutomaticZenRule; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -44,7 +45,6 @@ import java.util.Arrays; public class ConditionProviders extends ManagedServices { private final ArrayList<ConditionRecord> mRecords = new ArrayList<>(); - private final ArrayMap<IBinder, IConditionListener> mListeners = new ArrayMap<>(); private final ArraySet<String> mSystemConditionProviderNames; private final ArraySet<SystemConditionProviderService> mSystemConditionProviders = new ArraySet<>(); @@ -103,12 +103,6 @@ public class ConditionProviders extends ManagedServices { } } } - if (filter == null) { - pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):"); - for (int i = 0; i < mListeners.size(); i++) { - pw.print(" "); pw.println(mListeners.keyAt(i)); - } - } pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviderNames); for (int i = 0; i < mSystemConditionProviders.size(); i++) { mSystemConditionProviders.valueAt(i).dump(pw, filter); @@ -173,16 +167,12 @@ public class ConditionProviders extends ManagedServices { } } - private Condition[] validateConditions(String pkg, Condition[] conditions) { + private Condition[] removeDuplicateConditions(String pkg, Condition[] conditions) { if (conditions == null || conditions.length == 0) return null; final int N = conditions.length; final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N); for (int i = 0; i < N; i++) { final Uri id = conditions[i].id; - if (!Condition.isValidId(id, pkg)) { - Slog.w(TAG, "Ignoring condition from " + pkg + " for invalid id: " + id); - continue; - } if (valid.containsKey(id)) { Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id); continue; @@ -219,16 +209,9 @@ public class ConditionProviders extends ManagedServices { synchronized(mMutex) { if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions=" + (conditions == null ? null : Arrays.asList(conditions))); - conditions = validateConditions(pkg, conditions); + conditions = removeDuplicateConditions(pkg, conditions); if (conditions == null || conditions.length == 0) return; final int N = conditions.length; - for (IConditionListener listener : mListeners.values()) { - try { - listener.onConditionsReceived(conditions); - } catch (RemoteException e) { - Slog.w(TAG, "Error sending conditions to listener " + listener, e); - } - } for (int i = 0; i < N; i++) { final Condition c = conditions[i]; final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/); diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 0d6e3e5c947c..17313b685ae9 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -16,6 +16,7 @@ package com.android.server.notification; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -301,7 +302,9 @@ abstract public class ManagedServices { * */ public void registerGuestService(ManagedServiceInfo guest) { checkNotNull(guest.service); - checkType(guest.service); + if (!checkType(guest.service)) { + throw new IllegalArgumentException(); + } if (registerServiceImpl(guest) != null) { onServiceAdded(guest); } @@ -920,9 +923,9 @@ abstract public class ManagedServices { public static class UserProfiles { // Profiles of the current user. - private final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); + private final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>(); - public void updateCache(Context context) { + public void updateCache(@NonNull Context context) { UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); if (userManager != null) { int currentUserId = ActivityManager.getCurrentUser(); @@ -954,12 +957,12 @@ abstract public class ManagedServices { } } - protected static class Config { - String caption; - String serviceInterface; - String secureSettingName; - String bindPermission; - String settingsAction; - int clientLabel; + public static class Config { + public String caption; + public String serviceInterface; + public String secureSettingName; + public String bindPermission; + public String settingsAction; + public int clientLabel; } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 385557981376..24ffe1fcae4b 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -16,21 +16,21 @@ package com.android.server.notification; -import static android.service.notification.NotificationAssistantService.REASON_APP_CANCEL; -import static android.service.notification.NotificationAssistantService.REASON_APP_CANCEL_ALL; -import static android.service.notification.NotificationAssistantService.REASON_DELEGATE_CANCEL; -import static android.service.notification.NotificationAssistantService.REASON_DELEGATE_CANCEL_ALL; -import static android.service.notification.NotificationAssistantService.REASON_DELEGATE_CLICK; -import static android.service.notification.NotificationAssistantService.REASON_DELEGATE_ERROR; -import static android.service.notification.NotificationAssistantService.REASON_GROUP_OPTIMIZATION; -import static android.service.notification.NotificationAssistantService.REASON_GROUP_SUMMARY_CANCELED; -import static android.service.notification.NotificationAssistantService.REASON_LISTENER_CANCEL; -import static android.service.notification.NotificationAssistantService.REASON_LISTENER_CANCEL_ALL; -import static android.service.notification.NotificationAssistantService.REASON_PACKAGE_BANNED; -import static android.service.notification.NotificationAssistantService.REASON_PACKAGE_CHANGED; -import static android.service.notification.NotificationAssistantService.REASON_PACKAGE_SUSPENDED; -import static android.service.notification.NotificationAssistantService.REASON_PROFILE_TURNED_OFF; -import static android.service.notification.NotificationAssistantService.REASON_USER_STOPPED; +import static android.service.notification.NotificationRankerService.REASON_APP_CANCEL; +import static android.service.notification.NotificationRankerService.REASON_APP_CANCEL_ALL; +import static android.service.notification.NotificationRankerService.REASON_DELEGATE_CANCEL; +import static android.service.notification.NotificationRankerService.REASON_DELEGATE_CANCEL_ALL; +import static android.service.notification.NotificationRankerService.REASON_DELEGATE_CLICK; +import static android.service.notification.NotificationRankerService.REASON_DELEGATE_ERROR; +import static android.service.notification.NotificationRankerService.REASON_GROUP_OPTIMIZATION; +import static android.service.notification.NotificationRankerService.REASON_GROUP_SUMMARY_CANCELED; +import static android.service.notification.NotificationRankerService.REASON_LISTENER_CANCEL; +import static android.service.notification.NotificationRankerService.REASON_LISTENER_CANCEL_ALL; +import static android.service.notification.NotificationRankerService.REASON_PACKAGE_BANNED; +import static android.service.notification.NotificationRankerService.REASON_PACKAGE_CHANGED; +import static android.service.notification.NotificationRankerService.REASON_PACKAGE_SUSPENDED; +import static android.service.notification.NotificationRankerService.REASON_PROFILE_TURNED_OFF; +import static android.service.notification.NotificationRankerService.REASON_USER_STOPPED; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF; import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON; @@ -100,7 +100,7 @@ import android.service.notification.Condition; import android.service.notification.IConditionProvider; import android.service.notification.INotificationListener; import android.service.notification.IStatusBarNotificationHolder; -import android.service.notification.NotificationAssistantService; +import android.service.notification.NotificationRankerService; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationRankingUpdate; import android.service.notification.StatusBarNotification; @@ -128,10 +128,9 @@ import com.android.server.SystemService; import com.android.server.lights.Light; import com.android.server.lights.LightsManager; import com.android.server.notification.ManagedServices.ManagedServiceInfo; -import com.android.server.notification.ManagedServices.UserProfiles; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.vr.VrManagerInternal; -import com.android.server.vr.VrStateListener; +import com.android.server.notification.ManagedServices.UserProfiles; import libcore.io.IoUtils; @@ -220,14 +219,11 @@ public class NotificationManagerService extends SystemService { StatusBarManagerInternal mStatusBar; Vibrator mVibrator; private VrManagerInternal mVrManagerInternal; - private final NotificationVrListener mVrListener = new NotificationVrListener(); final IBinder mForegroundToken = new Binder(); private WorkerHandler mHandler; private final HandlerThread mRankingThread = new HandlerThread("ranker", Process.THREAD_PRIORITY_BACKGROUND); - private final HandlerThread mAssistantThread = new HandlerThread("assistant", - Process.THREAD_PRIORITY_BACKGROUND); private Light mNotificationLight; Light mAttentionLight; @@ -293,14 +289,13 @@ public class NotificationManagerService extends SystemService { private final UserProfiles mUserProfiles = new UserProfiles(); private NotificationListeners mListeners; - private NotificationAssistant mAssistant; + private NotificationRanker mRankerServices; private ConditionProviders mConditionProviders; private NotificationUsageStats mUsageStats; private static final int MY_UID = Process.myUid(); private static final int MY_PID = Process.myPid(); private RankingHandler mRankingHandler; - private Handler mAssistantHandler; private static class Archive { final int mBufferSize; @@ -758,7 +753,7 @@ public class NotificationManagerService extends SystemService { } } mListeners.onPackagesChanged(queryReplace, pkgList); - mAssistant.onPackagesChanged(queryReplace, pkgList); + mRankerServices.onPackagesChanged(queryReplace, pkgList); mConditionProviders.onPackagesChanged(queryReplace, pkgList); mRankingHelper.onPackagesChanged(queryReplace, pkgList); } @@ -807,7 +802,7 @@ public class NotificationManagerService extends SystemService { // Refresh managed services mConditionProviders.onUserSwitched(user); mListeners.onUserSwitched(user); - mAssistant.onUserSwitched(user); + mRankerServices.onUserSwitched(user); mZenModeHelper.onUserSwitched(user); } else if (action.equals(Intent.ACTION_USER_ADDED)) { mUserProfiles.updateCache(context); @@ -818,7 +813,7 @@ public class NotificationManagerService extends SystemService { final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); mConditionProviders.onUserUnlocked(user); mListeners.onUserUnlocked(user); - mAssistant.onUserUnlocked(user); + mRankerServices.onUserUnlocked(user); mZenModeHelper.onUserUnlocked(user); } } @@ -856,14 +851,6 @@ public class NotificationManagerService extends SystemService { } } - private final class NotificationVrListener extends VrStateListener { - @Override - public void onVrStateChanged(final boolean enabled) { - mListeners.setCategoryState(NotificationListenerService.CATEGORY_VR_NOTIFICATIONS, - enabled); - } - } - private SettingsObserver mSettingsObserver; private ZenModeHelper mZenModeHelper; @@ -902,7 +889,6 @@ public class NotificationManagerService extends SystemService { mHandler = new WorkerHandler(); mRankingThread.start(); - mAssistantThread.start(); String[] extractorNames; try { extractorNames = resources.getStringArray(R.array.config_notificationSignalExtractors); @@ -911,7 +897,6 @@ public class NotificationManagerService extends SystemService { } mUsageStats = new NotificationUsageStats(getContext()); mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper()); - mAssistantHandler = new Handler(mAssistantThread.getLooper()); mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mUsageStats, @@ -947,7 +932,7 @@ public class NotificationManagerService extends SystemService { importOldBlockDb(); mListeners = new NotificationListeners(); - mAssistant = new NotificationAssistant(); + mRankerServices = new NotificationRanker(); mStatusBar = getLocalService(StatusBarManagerInternal.class); mStatusBar.setNotificationDelegate(mNotificationDelegate); @@ -1064,14 +1049,13 @@ public class NotificationManagerService extends SystemService { mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); mAudioManagerInternal = getLocalService(AudioManagerInternal.class); mVrManagerInternal = getLocalService(VrManagerInternal.class); - mVrManagerInternal.registerListener(mVrListener); mZenModeHelper.onSystemReady(); } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { // This observer will force an update when observe is called, causing us to // bind to listener services. mSettingsObserver.observe(); mListeners.onBootPhaseAppsCanStart(); - mAssistant.onBootPhaseAppsCanStart(); + mRankerServices.onBootPhaseAppsCanStart(); mConditionProviders.onBootPhaseAppsCanStart(); } } @@ -1426,17 +1410,30 @@ public class NotificationManagerService extends SystemService { */ @Override public void registerListener(final INotificationListener listener, - final ComponentName component, final int userid) { + final ComponentName component, final int userid, boolean asRanker) { enforceSystemOrSystemUI("INotificationManager.registerListener"); - mListeners.registerService(listener, component, userid); + if (asRanker) { + mRankerServices.registerService(listener, component, userid); + } else { + mListeners.registerService(listener, component, userid); + } } /** * Remove a listener binder directly */ @Override - public void unregisterListener(INotificationListener listener, int userid) { - mListeners.unregisterService(listener, userid); + public void unregisterListener(INotificationListener token, int userid) { + final long identity = Binder.clearCallingIdentity(); + try { + if(mRankerServices.checkServiceTokenLocked(token) != null) { + mRankerServices.unregisterService(token, userid); + } else { + mListeners.unregisterService(token, userid); + } + } finally { + Binder.restoreCallingIdentity(identity); + } } /** @@ -1489,8 +1486,8 @@ public class NotificationManagerService extends SystemService { checkCallerIsSystemOrSameApp(component.getPackageName()); long identity = Binder.clearCallingIdentity(); try { - ManagedServices manager = mAssistant.isComponentEnabledForCurrentProfiles(component) - ? mAssistant + ManagedServices manager = mRankerServices.isComponentEnabledForCurrentProfiles(component) + ? mRankerServices : mListeners; manager.setComponentState(component, true); } finally { @@ -1983,7 +1980,7 @@ public class NotificationManagerService extends SystemService { } @Override - public void setImportanceFromAssistant(INotificationListener token, String key, + public void setImportanceFromRankerService(INotificationListener token, String key, int importance, CharSequence explanation) throws RemoteException { if (importance == IMPORTANCE_NONE) { throw new IllegalArgumentException("blocking not allowed: key=" + key); @@ -1991,7 +1988,7 @@ public class NotificationManagerService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { synchronized (mNotificationList) { - mAssistant.checkServiceTokenLocked(token); + mRankerServices.checkServiceTokenLocked(token); NotificationRecord n = mNotificationsByKey.get(key); n.setImportance(importance, explanation); mRankingHandler.requestSort(); @@ -2141,8 +2138,8 @@ public class NotificationManagerService extends SystemService { pw.print(listener.component); } pw.println(')'); - pw.println("\n Notification assistant:"); - mAssistant.dump(pw, filter); + pw.println("\n Notification ranker services:"); + mRankerServices.dump(pw, filter); } pw.println("\n Policy access:"); pw.print(" mPolicyAccess: "); pw.println(mPolicyAccess); @@ -2369,9 +2366,9 @@ public class NotificationManagerService extends SystemService { } } - // tell the assistant about the notification - if (mAssistant.isEnabled()) { - mAssistant.onNotificationEnqueued(r); + // tell the ranker service about the notification + if (mRankerServices.isEnabled()) { + mRankerServices.onNotificationEnqueued(r); // TODO delay the code below here for 100ms or until there is an answer } @@ -3488,21 +3485,21 @@ public class NotificationManagerService extends SystemService { } } - public class NotificationAssistant extends ManagedServices { + public class NotificationRanker extends ManagedServices { - public NotificationAssistant() { + public NotificationRanker() { super(getContext(), mHandler, mNotificationList, mUserProfiles); } @Override protected Config getConfig() { Config c = new Config(); - c.caption = "notification assistant"; - c.serviceInterface = NotificationAssistantService.SERVICE_INTERFACE; - c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT; - c.bindPermission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE; + c.caption = "notification ranker service"; + c.serviceInterface = NotificationRankerService.SERVICE_INTERFACE; + c.secureSettingName = null; + c.bindPermission = Manifest.permission.BIND_NOTIFICATION_RANKER_SERVICE; c.settingsAction = Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS; - c.clientLabel = R.string.notification_assistant_binding_label; + c.clientLabel = R.string.notification_ranker_binding_label; return c; } @@ -3530,10 +3527,10 @@ public class NotificationManagerService extends SystemService { final StatusBarNotification sbn = r.sbn; TrimCache trimCache = new TrimCache(sbn); - // mServices is the list inside ManagedServices of all the assistants, + // mServices is the list inside ManagedServices of all the rankers, // There should be only one, but it's a list, so while we enforce // singularity elsewhere, we keep it general here, to avoid surprises. - for (final ManagedServiceInfo info : NotificationAssistant.this.mServices) { + for (final ManagedServiceInfo info : NotificationRanker.this.mServices) { boolean sbnVisible = isVisibleToListener(sbn, info); if (!sbnVisible) { continue; @@ -3542,7 +3539,7 @@ public class NotificationManagerService extends SystemService { final int importance = r.getImportance(); final boolean fromUser = r.isImportanceFromUser(); final StatusBarNotification sbnToPost = trimCache.ForListener(info); - mAssistantHandler.post(new Runnable() { + mHandler.post(new Runnable() { @Override public void run() { notifyEnqueued(info, sbnToPost, importance, fromUser); @@ -3553,12 +3550,12 @@ public class NotificationManagerService extends SystemService { private void notifyEnqueued(final ManagedServiceInfo info, final StatusBarNotification sbn, int importance, boolean fromUser) { - final INotificationListener assistant = (INotificationListener) info.service; + final INotificationListener ranker = (INotificationListener) info.service; StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn); try { - assistant.onNotificationEnqueued(sbnHolder, importance, fromUser); + ranker.onNotificationEnqueued(sbnHolder, importance, fromUser); } catch (RemoteException ex) { - Log.e(TAG, "unable to notify assistant (enqueued): " + assistant, ex); + Log.e(TAG, "unable to notify ranker (enqueued): " + ranker, ex); } } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 93dcc72c0ae8..206a1438bd61 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -166,6 +166,10 @@ public final class Installer extends SystemService { mInstaller.execute("rmpackagedir", packageDir); } + public void rmProfiles(String pkgName) throws InstallerException { + mInstaller.execute("rmprofiles", pkgName); + } + public void createUserConfig(int userid) throws InstallerException { mInstaller.execute("mkuserconfig", userid); } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 8d75f603928e..902a3d932bae 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.app.AppGlobals; import android.content.ComponentName; import android.content.Context; @@ -27,8 +29,12 @@ import android.content.pm.IOnAppsChangedListener; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutServiceInternal; +import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; import android.content.pm.UserInfo; import android.graphics.Rect; import android.net.Uri; @@ -44,15 +50,16 @@ import android.util.Log; import android.util.Slog; import com.android.internal.content.PackageMonitor; +import com.android.internal.util.Preconditions; +import com.android.server.LocalServices; import com.android.server.SystemService; import java.util.List; /** * Service that manages requests and callbacks for launchers that support - * managed profiles. + * managed profiles. */ - public class LauncherAppsService extends SystemService { private final LauncherAppsImpl mLauncherAppsImpl; @@ -67,21 +74,25 @@ public class LauncherAppsService extends SystemService { publishBinderService(Context.LAUNCHER_APPS_SERVICE, mLauncherAppsImpl); } - class LauncherAppsImpl extends ILauncherApps.Stub { + static class LauncherAppsImpl extends ILauncherApps.Stub { private static final boolean DEBUG = false; private static final String TAG = "LauncherAppsService"; private final Context mContext; private final PackageManager mPm; private final UserManager mUm; + private final ShortcutServiceInternal mShortcutServiceInternal; private final PackageCallbackList<IOnAppsChangedListener> mListeners = new PackageCallbackList<IOnAppsChangedListener>(); - private MyPackageMonitor mPackageMonitor = new MyPackageMonitor(); + private final MyPackageMonitor mPackageMonitor = new MyPackageMonitor(); public LauncherAppsImpl(Context context) { mContext = context; mPm = mContext.getPackageManager(); mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mShortcutServiceInternal = Preconditions.checkNotNull( + LocalServices.getService(ShortcutServiceInternal.class)); + mShortcutServiceInternal.addListener(mPackageMonitor); } /* @@ -174,6 +185,20 @@ public class LauncherAppsService extends SystemService { } } + private void verifyCallingPackage(String callingPackage) { + int packageUid = -1; + try { + packageUid = mPm.getPackageUid(callingPackage, + PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE + | PackageManager.MATCH_UNINSTALLED_PACKAGES); + } catch (NameNotFoundException e) { + Log.e(TAG, "Package not found: " + callingPackage); + } + if (packageUid != Binder.getCallingUid()) { + throw new SecurityException("Calling package name mismatch"); + } + } + /** * Checks if the user is enabled. */ @@ -264,6 +289,57 @@ public class LauncherAppsService extends SystemService { } } + private void enforceShortcutPermission(UserHandle user) { + ensureInUserProfiles(user, "Cannot start activity for unrelated profile " + user); + // STOPSHIP Implement it + } + + @Override + public ParceledListSlice getShortcuts(String callingPackage, long changedSince, + String packageName, ComponentName componentName, int flags, UserHandle user) + throws RemoteException { + enforceShortcutPermission(user); + verifyCallingPackage(callingPackage); + + return new ParceledListSlice<>( + mShortcutServiceInternal.getShortcuts(callingPackage, changedSince, packageName, + componentName, flags, user.getIdentifier())); + } + + @Override + public ParceledListSlice getShortcutInfo(String callingPackage, String packageName, + List<String> ids, UserHandle user) throws RemoteException { + enforceShortcutPermission(user); + verifyCallingPackage(callingPackage); + + return new ParceledListSlice<>( + mShortcutServiceInternal.getShortcutInfo(callingPackage, packageName, + ids, user.getIdentifier())); + } + + @Override + public void pinShortcuts(String callingPackage, String packageName, List<String> ids, + UserHandle user) throws RemoteException { + enforceShortcutPermission(user); + verifyCallingPackage(callingPackage); + + mShortcutServiceInternal.pinShortcuts(callingPackage, packageName, + ids, user.getIdentifier()); + } + + @Override + public void startShortcut(String callingPackage, ShortcutInfo shortcut, Rect sourceBounds, + Bundle startActivityOptions, UserHandle user) throws RemoteException { + enforceShortcutPermission(user); + verifyCallingPackage(callingPackage); + + final Intent intent = mShortcutServiceInternal.createShortcutIntent(callingPackage, + shortcut, user.getIdentifier()); + // TODO + Slog.e(TAG, "startShortcut() not implemented yet, but the intent is " + intent); + throw new RuntimeException("not implemented yet"); + } + @Override public boolean isActivityEnabled(ComponentName component, UserHandle user) throws RemoteException { @@ -355,7 +431,7 @@ public class LauncherAppsService extends SystemService { } - private class MyPackageMonitor extends PackageMonitor { + private class MyPackageMonitor extends PackageMonitor implements ShortcutChangeListener { /** Checks if user is a profile of or same as listeningUser. * and the user is enabled. */ @@ -390,6 +466,8 @@ public class LauncherAppsService extends SystemService { } } + // TODO Simplify with lambdas. + @Override public void onPackageAdded(String packageName, int uid) { UserHandle user = new UserHandle(getChangingUserId()); @@ -523,6 +601,25 @@ public class LauncherAppsService extends SystemService { super.onPackagesUnsuspended(packages); } + @Override + public void onShortcutChanged(@NonNull String packageName, + @NonNull List<ShortcutInfo> shortcuts, @UserIdInt int userId) { + final UserHandle user = UserHandle.of(userId); + + final int n = mListeners.beginBroadcast(); + for (int i = 0; i < n; i++) { + IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); + UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); + if (!isEnabledProfileOf(user, listeningUser, "onShortcutChanged")) continue; + try { + listener.onShortcutChanged(user, packageName, + new ParceledListSlice<>(shortcuts)); + } catch (RemoteException re) { + Slog.d(TAG, "Callback failed ", re); + } + } + mListeners.finishBroadcast(); + } } class PackageCallbackList<T extends IInterface> extends RemoteCallbackList<T> { diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index c9613b4313c6..561682c5c478 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -191,7 +191,6 @@ class PackageDexOptimizer { throw new IllegalStateException("Invalid dexopt:" + dexoptNeeded); } - Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg=" + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8d4c9e576162..e180e0501d98 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4443,6 +4443,13 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + public List<String> getAllPackages() { + synchronized (mPackages) { + return new ArrayList<String>(mPackages.keySet()); + } + } + + @Override public String[] getPackagesForUid(int uid) { uid = UserHandle.getAppId(uid); // reader @@ -15147,6 +15154,16 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + public void clearApplicationProfileData(String packageName) { + enforceSystemOrRoot("Only the system can clear all profile data"); + try { + mInstaller.rmProfiles(packageName); + } catch (InstallerException ex) { + Log.e(TAG, "Could not clear profile data of package " + packageName); + } + } + + @Override public void clearApplicationUserData(final String packageName, final IPackageDataObserver observer, final int userId) { mContext.enforceCallingOrSelfPermission( diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index ccbd8235a0d0..28d34a84250f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -233,16 +233,29 @@ class PackageManagerShellCommand extends ShellCommand { boolean useJitProfiles = false; boolean extractOnly = false; boolean forceCompilation = false; + boolean allPackages = false; + boolean clearProfileData = false; String compilationMode = "default"; String opt; while ((opt = getNextOption()) != null) { switch (opt) { + case "-a": + allPackages = true; + break; + case "-c": + clearProfileData = true; + break; + case "-f": + forceCompilation = true; + break; case "-m": compilationMode = getNextArgRequired(); break; - case "-f": + case "--reset": forceCompilation = true; + clearProfileData = true; + compilationMode = "extract"; break; default: pw.println("Error: Unknown option: " + opt); @@ -255,7 +268,7 @@ class PackageManagerShellCommand extends ShellCommand { useJitProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false); extractOnly = false; break; - case "all": + case "full": useJitProfiles = false; extractOnly = false; break; @@ -272,19 +285,49 @@ class PackageManagerShellCommand extends ShellCommand { return 1; } - String packageName = getNextArg(); - if (packageName == null) { - pw.println("Error: package name not specified"); - return 1; + List<String> packageNames = null; + if (allPackages) { + packageNames = mInterface.getAllPackages(); + } else { + String packageName = getNextArg(); + if (packageName == null) { + pw.println("Error: package name not specified"); + return 1; + } + packageNames = Collections.singletonList(packageName); + } + + List<String> failedPackages = new ArrayList<>(); + for (String packageName : packageNames) { + if (clearProfileData) { + mInterface.clearApplicationProfileData(packageName); + } + + boolean result = mInterface.performDexOpt(packageName, null /* instructionSet */, + useJitProfiles, extractOnly, forceCompilation); + if (!result) { + failedPackages.add(packageName); + } } - boolean success = mInterface.performDexOpt(packageName, null /* instructionSet */, - useJitProfiles, extractOnly, forceCompilation); - if (success) { + if (failedPackages.isEmpty()) { pw.println("Success"); return 0; + } else if (failedPackages.size() == 1) { + pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled"); + return 1; } else { - pw.println("Failure: package " + packageName + " could not be compiled"); + pw.print("Failure: the following packages could not be compiled: "); + boolean is_first = true; + for (String packageName : failedPackages) { + if (is_first) { + is_first = false; + } else { + pw.print(", "); + } + pw.print(packageName); + } + pw.println(); return 1; } } @@ -1135,12 +1178,17 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" help"); pw.println(" Print this help text."); pw.println(""); - pw.println(" compile [-m MODE] [-f] TARGET-PACKAGE"); - pw.println(" Trigger compilation of TARGET-PACKAGE."); + pw.println(" compile [-m MODE] [-f] [-c] [--reset] (-a | TARGET-PACKAGE)"); + pw.println(" Trigger compilation of TARGET-PACKAGE or all packages if \"-a\"."); pw.println(" Options:"); - pw.println(" -m: select compilation mode"); - pw.println(" MODE can be one of \"default\", \"all\", \"profile\", and \"extract\""); + pw.println(" -a: compile all packages"); + pw.println(" -c: clear profile data before compiling"); pw.println(" -f: force compilation even if not needed"); + pw.println(" -m: select compilation mode"); + pw.println(" MODE can be one of \"default\", \"full\", \"profile\"," + + " and \"extract\""); + pw.println(" --reset: restore package to its post-install state"); + pw.println(" shorthand for \"-c -f -m extract\""); pw.println(" list features"); pw.println(" Prints all features of the system."); pw.println(" list instrumentation [-f] [TARGET-PACKAGE]"); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java new file mode 100644 index 000000000000..5382ff158282 --- /dev/null +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -0,0 +1,1529 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.IShortcutService; +import android.content.pm.LauncherApps; +import android.content.pm.LauncherApps.ShortcutQuery; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ParceledListSlice; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutServiceInternal; +import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; +import android.graphics.drawable.Icon; +import android.os.Binder; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.PersistableBundle; +import android.os.Process; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCommand; +import android.os.UserHandle; +import android.text.TextUtils; +import android.text.format.Time; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.SparseArray; +import android.util.Xml; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.Preconditions; +import com.android.server.LocalServices; +import com.android.server.SystemService; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +/** + * TODO: + * - Make save async + * + * - Add Bitmap support + * + * - Implement updateShortcuts + * + * - Listen to PACKAGE_*, remove orphan info, update timestamp for icon res + * + * - Pinned per each launcher package (multiple launchers) + * + * - Dev option to reset all counts for QA (for now use "adb shell cmd shortcut reset-throttling") + * + * - Load config from settings + */ +public class ShortcutService extends IShortcutService.Stub { + private static final String TAG = "ShortcutService"; + + private static final boolean DEBUG = true; // STOPSHIP if true + private static final boolean DEBUG_LOAD = true; // STOPSHIP if true + + private static final int DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day + private static final int DEFAULT_MAX_DAILY_UPDATES = 10; + private static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5; + + private static final int SAVE_DELAY_MS = 5000; // in milliseconds. + + @VisibleForTesting + static final String FILENAME_BASE_STATE = "shortcut_service.xml"; + + @VisibleForTesting + static final String DIRECTORY_PER_USER = "shortcut_service"; + + @VisibleForTesting + static final String FILENAME_USER_PACKAGES = "shortcuts.xml"; + + private static final String DIRECTORY_BITMAPS = "bitmaps"; + + private static final String TAG_ROOT = "root"; + private static final String TAG_LAST_RESET_TIME = "last_reset_time"; + private static final String ATTR_VALUE = "value"; + + private final Context mContext; + + private final Object mLock = new Object(); + + private final Handler mHandler; + + @GuardedBy("mLock") + private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1); + + @GuardedBy("mLock") + private long mRawLastResetTime; + + /** + * All the information relevant to shortcuts from a single package (per-user). + * + * TODO Move the persisting code to this class. + */ + private static class PackageShortcuts { + /** + * All the shortcuts from the package, keyed on IDs. + */ + final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>(); + + /** + * # of dynamic shortcuts. + */ + private int mDynamicShortcutCount = 0; + + /** + * # of times the package has called rate-limited APIs. + */ + private int mApiCallCountInner; + + /** + * When {@link #mApiCallCountInner} was reset last time. + */ + private long mLastResetTime; + + /** + * @return the all shortcuts. Note DO NOT add/remove or touch the flags of the result + * directly, which would cause {@link #mDynamicShortcutCount} to be out of sync. + */ + @GuardedBy("mLock") + public ArrayMap<String, ShortcutInfo> getShortcuts() { + return mShortcuts; + } + + /** + * Add a shortcut, or update one with the same ID, with taking over existing flags. + * + * It checks the max number of dynamic shortcuts. + */ + @GuardedBy("mLock") + public void updateShortcutWithCapping(@NonNull ShortcutService s, + @NonNull ShortcutInfo newShortcut) { + final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId()); + + int oldFlags = 0; + int newDynamicCount = mDynamicShortcutCount; + + if (oldShortcut != null) { + oldFlags = oldShortcut.getFlags(); + if (oldShortcut.isDynamic()) { + newDynamicCount--; + } + } + if (newShortcut.isDynamic()) { + newDynamicCount++; + } + // Make sure there's still room. + s.enforceMaxDynamicShortcuts(newDynamicCount); + + // Okay, make it dynamic and add. + newShortcut.addFlags(oldFlags); + + mShortcuts.put(newShortcut.getId(), newShortcut); + mDynamicShortcutCount = newDynamicCount; + } + + @GuardedBy("mLock") + public void deleteAllDynamicShortcuts() { + ArrayList<String> removeList = null; // Lazily initialize. + + for (int i = mShortcuts.size() - 1; i >= 0; i--) { + final ShortcutInfo si = mShortcuts.valueAt(i); + + if (!si.isDynamic()) { + continue; + } + if (si.isPinned()) { + // Still pinned, so don't remove; just make it non-dynamic. + si.clearFlags(ShortcutInfo.FLAG_DYNAMIC); + } else { + if (removeList == null) { + removeList = new ArrayList<>(); + } + removeList.add(si.getId()); + } + } + if (removeList != null) { + for (int i = removeList.size() - 1 ; i >= 0; i--) { + mShortcuts.remove(removeList.get(i)); + } + } + mDynamicShortcutCount = 0; + } + + @GuardedBy("mLock") + public void deleteDynamicWithId(@NonNull String shortcutId) { + final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId); + + if (oldShortcut == null) { + return; + } + if (oldShortcut.isDynamic()) { + mDynamicShortcutCount--; + } + if (oldShortcut.isPinned()) { + oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC); + } else { + mShortcuts.remove(shortcutId); + } + } + + @GuardedBy("mLock") + public void pinAll(List<String> shortcutIds) { + for (int i = shortcutIds.size() - 1; i >= 0; i--) { + final ShortcutInfo shortcut = mShortcuts.get(shortcutIds.get(i)); + if (shortcut != null) { + shortcut.addFlags(ShortcutInfo.FLAG_PINNED); + } + } + } + + /** + * Number of calls that the caller has made, since the last reset. + */ + @GuardedBy("mLock") + public int getApiCallCount(@NonNull ShortcutService s) { + final long last = s.getLastResetTimeLocked(); + + // If not reset yet, then reset. + if (mLastResetTime < last) { + mApiCallCountInner = 0; + mLastResetTime = last; + } + return mApiCallCountInner; + } + + /** + * If the caller app hasn't been throttled yet, increment {@link #mApiCallCountInner} + * and return true. Otherwise just return false. + */ + @GuardedBy("mLock") + public boolean tryApiCall(@NonNull ShortcutService s) { + if (getApiCallCount(s) >= s.mMaxDailyUpdates) { + return false; + } + mApiCallCountInner++; + return true; + } + + @GuardedBy("mLock") + public void resetRateLimitingForCommandLine() { + mApiCallCountInner = 0; + mLastResetTime = 0; + } + + /** + * Find all shortcuts that match {@code query}. + */ + @GuardedBy("mLock") + public void findAll(@NonNull List<ShortcutInfo> result, + @Nullable Predicate<ShortcutInfo> query, int cloneFlag) { + for (int i = 0; i < mShortcuts.size(); i++) { + final ShortcutInfo si = mShortcuts.valueAt(i); + if (query == null || query.test(si)) { + result.add(si.clone(cloneFlag)); + } + } + } + } + + /** + * User ID -> package name -> list of ShortcutInfos. + */ + @GuardedBy("mLock") + private final SparseArray<ArrayMap<String, PackageShortcuts>> mShortcuts = + new SparseArray<>(); + + /** + * Max number of dynamic shortcuts that each application can have at a time. + */ + @GuardedBy("mLock") + private int mMaxDynamicShortcuts; + + /** + * Max number of updating API calls that each application can make a day. + */ + @GuardedBy("mLock") + private int mMaxDailyUpdates; + + /** + * Actual throttling-reset interval. By default it's a day. + */ + @GuardedBy("mLock") + private long mResetInterval; + + public ShortcutService(Context context) { + mContext = Preconditions.checkNotNull(context); + LocalServices.addService(ShortcutServiceInternal.class, new LocalService()); + mHandler = new Handler(BackgroundThread.get().getLooper()); + } + + /** + * System service lifecycle. + */ + public static final class Lifecycle extends SystemService { + final ShortcutService mService; + + public Lifecycle(Context context) { + super(context); + mService = new ShortcutService(context); + } + + @Override + public void onStart() { + publishBinderService(Context.SHORTCUT_SERVICE, mService); + } + + @Override + public void onBootPhase(int phase) { + mService.onBootPhase(phase); + } + + @Override + public void onCleanupUser(int userHandle) { + synchronized (mService.mLock) { + mService.onCleanupUserInner(userHandle); + } + } + + @Override + public void onStartUser(int userId) { + synchronized (mService.mLock) { + mService.onStartUserLocked(userId); + } + } + } + + /** lifecycle event */ + void onBootPhase(int phase) { + if (DEBUG) { + Slog.d(TAG, "onBootPhase: " + phase); + } + switch (phase) { + case SystemService.PHASE_LOCK_SETTINGS_READY: + initialize(); + break; + } + } + + /** lifecycle event */ + void onStartUserLocked(int userId) { + // Preload + getUserShortcutsLocked(userId); + } + + /** lifecycle event */ + void onCleanupUserInner(int userId) { + // Unload + mShortcuts.delete(userId); + } + + /** Return the base state file name */ + private AtomicFile getBaseStateFile() { + final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE); + path.mkdirs(); + return new AtomicFile(path); + } + + /** + * Init the instance. (load the state file, etc) + */ + private void initialize() { + synchronized (mLock) { + injectLoadConfigurationLocked(); + loadBaseStateLocked(); + } + } + + // Test overrides it to inject different values. + @VisibleForTesting + void injectLoadConfigurationLocked() { + mResetInterval = DEFAULT_RESET_INTERVAL_SEC * 1000L; + mMaxDailyUpdates = DEFAULT_MAX_DAILY_UPDATES; + mMaxDynamicShortcuts = DEFAULT_MAX_SHORTCUTS_PER_APP; + } + + // === Persistings === + + @Nullable + private String parseStringAttribute(XmlPullParser parser, String attribute) { + return parser.getAttributeValue(null, attribute); + } + + private long parseLongAttribute(XmlPullParser parser, String attribute) { + final String value = parseStringAttribute(parser, attribute); + if (TextUtils.isEmpty(value)) { + return 0; + } + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + Slog.e(TAG, "Error parsing long " + value); + return 0; + } + } + + @Nullable + private ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) { + final String value = parseStringAttribute(parser, attribute); + if (TextUtils.isEmpty(value)) { + return null; + } + return ComponentName.unflattenFromString(value); + } + + @Nullable + private Intent parseIntentAttribute(XmlPullParser parser, String attribute) { + final String value = parseStringAttribute(parser, attribute); + if (TextUtils.isEmpty(value)) { + return null; + } + try { + return Intent.parseUri(value, /* flags =*/ 0); + } catch (URISyntaxException e) { + Slog.e(TAG, "Error parsing intent", e); + return null; + } + } + + private void writeTagValue(XmlSerializer out, String tag, String value) throws IOException { + if (TextUtils.isEmpty(value)) return; + + out.startTag(null, tag); + out.attribute(null, ATTR_VALUE, value); + out.endTag(null, tag); + } + + private void writeTagValue(XmlSerializer out, String tag, long value) throws IOException { + writeTagValue(out, tag, Long.toString(value)); + } + + private void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle) + throws IOException, XmlPullParserException { + if (bundle == null) return; + + out.startTag(null, tag); + bundle.saveToXml(out); + out.endTag(null, tag); + } + + private void writeAttr(XmlSerializer out, String name, String value) throws IOException { + if (TextUtils.isEmpty(value)) return; + + out.attribute(null, name, value); + } + + private void writeAttr(XmlSerializer out, String name, long value) throws IOException { + writeAttr(out, name, String.valueOf(value)); + } + + private void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException { + if (comp == null) return; + writeAttr(out, name, comp.flattenToString()); + } + + private void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException { + if (intent == null) return; + + writeAttr(out, name, intent.toUri(/* flags =*/ 0)); + } + + @VisibleForTesting + void saveBaseStateLocked() { + final AtomicFile file = getBaseStateFile(); + if (DEBUG) { + Slog.i(TAG, "Saving to " + file.getBaseFile()); + } + + FileOutputStream outs = null; + try { + outs = file.startWrite(); + + // Write to XML + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(outs, StandardCharsets.UTF_8.name()); + out.startDocument(null, true); + out.startTag(null, TAG_ROOT); + + // Body. + writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime); + + // Epilogue. + out.endTag(null, TAG_ROOT); + out.endDocument(); + + // Close. + file.finishWrite(outs); + } catch (IOException e) { + Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); + file.failWrite(outs); + } + } + + private void loadBaseStateLocked() { + mRawLastResetTime = 0; + + final AtomicFile file = getBaseStateFile(); + if (DEBUG) { + Slog.i(TAG, "Loading from " + file.getBaseFile()); + } + try (FileInputStream in = file.openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, StandardCharsets.UTF_8.name()); + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + // Check the root tag + final String tag = parser.getName(); + if (depth == 1) { + if (!TAG_ROOT.equals(tag)) { + Slog.e(TAG, "Invalid root tag: " + tag); + return; + } + continue; + } + // Assume depth == 2 + switch (tag) { + case TAG_LAST_RESET_TIME: + mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE); + break; + default: + Slog.e(TAG, "Invalid tag: " + tag); + break; + } + } + } catch (FileNotFoundException e) { + // Use the default + } catch (IOException|XmlPullParserException e) { + Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); + + mRawLastResetTime = 0; + } + // Adjust the last reset time. + getLastResetTimeLocked(); + } + + private void saveUserLocked(@UserIdInt int userId) { + final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); + if (DEBUG) { + Slog.i(TAG, "Saving to " + path); + } + path.mkdirs(); + final AtomicFile file = new AtomicFile(path); + FileOutputStream outs = null; + try { + outs = file.startWrite(); + + // Write to XML + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(outs, StandardCharsets.UTF_8.name()); + out.startDocument(null, true); + out.startTag(null, TAG_ROOT); + + final ArrayMap<String, PackageShortcuts> packages = getUserShortcutsLocked(userId); + + // Body. + for (int i = 0; i < packages.size(); i++) { + final String packageName = packages.keyAt(i); + final PackageShortcuts shortcuts = packages.valueAt(i); + + // TODO Move this to PackageShortcuts. + + out.startTag(null, "package"); + + writeAttr(out, "name", packageName); + writeAttr(out, "dynamic-count", shortcuts.mDynamicShortcutCount); + writeAttr(out, "call-count", shortcuts.mApiCallCountInner); + writeAttr(out, "last-reset", shortcuts.mLastResetTime); + + final int size = shortcuts.getShortcuts().size(); + for (int j = 0; j < size; j++) { + saveShortcut(out, shortcuts.getShortcuts().valueAt(j)); + } + + out.endTag(null, "package"); + } + + // Epilogue. + out.endTag(null, TAG_ROOT); + out.endDocument(); + + // Close. + file.finishWrite(outs); + } catch (IOException|XmlPullParserException e) { + Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); + file.failWrite(outs); + } + } + + private void saveShortcut(XmlSerializer out, ShortcutInfo si) + throws IOException, XmlPullParserException { + out.startTag(null, "shortcut"); + writeAttr(out, "id", si.getId()); + // writeAttr(out, "package", si.getPackageName()); // not needed + writeAttr(out, "activity", si.getActivityComponent()); + // writeAttr(out, "icon", si.getIcon()); // We don't save it. + writeAttr(out, "title", si.getTitle()); + writeAttr(out, "intent", si.getIntent()); + writeAttr(out, "weight", si.getWeight()); + writeAttr(out, "timestamp", si.getLastChangedTimestamp()); + writeAttr(out, "flags", si.getFlags()); + writeAttr(out, "icon-res", si.getIconResourceId()); + writeAttr(out, "bitmap-path", si.getBitmapPath()); + + writeTagExtra(out, "intent-extras", si.getIntentPersistableExtras()); + writeTagExtra(out, "extras", si.getExtras()); + + out.endTag(null, "shortcut"); + } + + private static IOException throwForInvalidTag(int depth, String tag) throws IOException { + throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth)); + } + + @Nullable + private ArrayMap<String, PackageShortcuts> loadUserLocked(@UserIdInt int userId) { + final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); + if (DEBUG) { + Slog.i(TAG, "Loading from " + path); + } + path.mkdirs(); + final AtomicFile file = new AtomicFile(path); + + final FileInputStream in; + try { + in = file.openRead(); + } catch (FileNotFoundException e) { + if (DEBUG) { + Slog.i(TAG, "Not found " + path); + } + return null; + } + final ArrayMap<String, PackageShortcuts> ret = new ArrayMap<String, PackageShortcuts>(); + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, StandardCharsets.UTF_8.name()); + + String packageName = null; + PackageShortcuts shortcuts = null; + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + + // TODO Move some of this to PackageShortcuts. + + final String tag = parser.getName(); + if (DEBUG_LOAD) { + Slog.d(TAG, String.format("depth=%d type=%d name=%s", + depth, type, tag)); + } + switch (depth) { + case 1: { + if (TAG_ROOT.equals(tag)) { + continue; + } + break; + } + case 2: { + switch (tag) { + case "package": + packageName = parseStringAttribute(parser, "name"); + shortcuts = new PackageShortcuts(); + ret.put(packageName, shortcuts); + + shortcuts.mDynamicShortcutCount = + (int) parseLongAttribute(parser, "dynamic-count"); + shortcuts.mApiCallCountInner = + (int) parseLongAttribute(parser, "call-count"); + shortcuts.mLastResetTime = parseLongAttribute(parser, "last-reset"); + continue; + } + break; + } + case 3: { + switch (tag) { + case "shortcut": + final ShortcutInfo si = parseShortcut(parser, packageName); + shortcuts.mShortcuts.put(si.getId(), si); + continue; + } + break; + } + } + throwForInvalidTag(depth, tag); + } + return ret; + } catch (IOException|XmlPullParserException e) { + Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); + return null; + } finally { + IoUtils.closeQuietly(in); + } + } + + private ShortcutInfo parseShortcut(XmlPullParser parser, String packgeName) + throws IOException, XmlPullParserException { + String id; + ComponentName activityComponent; + Icon icon; + String title; + Intent intent; + PersistableBundle intentPersistableExtras = null; + int weight; + PersistableBundle extras = null; + long lastChangedTimestamp; + int flags; + int iconRes; + String bitmapPath; + + id = parseStringAttribute(parser, "id"); + activityComponent = parseComponentNameAttribute(parser, "activity"); + title = parseStringAttribute(parser, "title"); + intent = parseIntentAttribute(parser, "intent"); + weight = (int) parseLongAttribute(parser, "weight"); + lastChangedTimestamp = (int) parseLongAttribute(parser, "timestamp"); + flags = (int) parseLongAttribute(parser, "flags"); + iconRes = (int) parseLongAttribute(parser, "icon-res"); + bitmapPath = parseStringAttribute(parser, "bitmap-path"); + + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + final String tag = parser.getName(); + if (DEBUG_LOAD) { + Slog.d(TAG, String.format(" depth=%d type=%d name=%s", + depth, type, tag)); + } + switch (tag) { + case "intent-extras": + intentPersistableExtras = PersistableBundle.restoreFromXml(parser); + continue; + case "extras": + extras = PersistableBundle.restoreFromXml(parser); + continue; + } + throw throwForInvalidTag(depth, tag); + } + return new ShortcutInfo( + id, packgeName, activityComponent, /* icon =*/ null, title, intent, + intentPersistableExtras, weight, extras, lastChangedTimestamp, flags, + iconRes, bitmapPath); + } + + // TODO Actually make it async. + private void scheduleSaveBaseState() { + synchronized (mLock) { + saveBaseStateLocked(); + } + } + + // TODO Actually make it async. + private void scheduleSaveUser(@UserIdInt int userId) { + synchronized (mLock) { + saveUserLocked(userId); + } + } + + /** Return the last reset time. */ + long getLastResetTimeLocked() { + updateTimes(); + return mRawLastResetTime; + } + + /** Return the next reset time. */ + long getNextResetTimeLocked() { + updateTimes(); + return mRawLastResetTime + mResetInterval; + } + + /** + * Update the last reset time. + */ + private void updateTimes() { + + final long now = injectCurrentTimeMillis(); + + final long prevLastResetTime = mRawLastResetTime; + + if (mRawLastResetTime == 0) { // first launch. + // TODO Randomize?? + mRawLastResetTime = now; + } else if (now < mRawLastResetTime) { + // Clock rewound. + // TODO Randomize?? + mRawLastResetTime = now; + } else { + // TODO Do it properly. + while ((mRawLastResetTime + mResetInterval) <= now) { + mRawLastResetTime += mResetInterval; + } + } + if (prevLastResetTime != mRawLastResetTime) { + scheduleSaveBaseState(); + } + } + + /** Return the per-user state. */ + @GuardedBy("mLock") + @NonNull + private ArrayMap<String, PackageShortcuts> getUserShortcutsLocked(@UserIdInt int userId) { + ArrayMap<String, PackageShortcuts> userPackages = mShortcuts.get(userId); + if (userPackages == null) { + userPackages = loadUserLocked(userId); + if (userPackages == null) { + userPackages = new ArrayMap<>(); + } + mShortcuts.put(userId, userPackages); + } + return userPackages; + } + + /** Return the per-user per-package state. */ + @GuardedBy("mLock") + @NonNull + private PackageShortcuts getPackageShortcutsLocked( + @NonNull String packageName, @UserIdInt int userId) { + final ArrayMap<String, PackageShortcuts> userPackages = getUserShortcutsLocked(userId); + PackageShortcuts shortcuts = userPackages.get(packageName); + if (shortcuts == null) { + shortcuts = new PackageShortcuts(); + userPackages.put(packageName, shortcuts); + } + return shortcuts; + } + + // === Caller validation === + + private boolean isCallerSystem() { + final int callingUid = injectBinderCallingUid(); + return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID); + } + + private boolean isCallerShell() { + final int callingUid = injectBinderCallingUid(); + return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID; + } + + private void enforceSystemOrShell() { + Preconditions.checkState(isCallerSystem() || isCallerShell(), + "Caller must be system or shell"); + } + + private void enforceShell() { + Preconditions.checkState(isCallerShell(), "Caller must be shell"); + } + + private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) { + Preconditions.checkStringNotEmpty(packageName, "packageName"); + + if (isCallerSystem()) { + return; // no check + } + + final int callingUid = injectBinderCallingUid(); + + // Otherwise, make sure the arguments are valid. + if (UserHandle.getUserId(callingUid) != userId) { + throw new SecurityException("Invalid user-ID"); + } + verifyCallingPackage(packageName); + } + + private void verifyCallingPackage(@NonNull String packageName) { + Preconditions.checkStringNotEmpty(packageName, "packageName"); + + if (isCallerSystem()) { + return; // no check + } + + if (injectGetPackageUid(packageName) == injectBinderCallingUid()) { + return; // Caller is valid. + } + throw new SecurityException("Caller UID= doesn't own " + packageName); + } + + // Test overrides it. + int injectGetPackageUid(String packageName) { + try { + + // TODO Is MATCH_UNINSTALLED_PACKAGES correct to get SD card app info? + + return mContext.getPackageManager().getPackageUid(packageName, + PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE + | PackageManager.MATCH_UNINSTALLED_PACKAGES); + } catch (NameNotFoundException e) { + return -1; + } + } + + /** + * Throw if {@code numShortcuts} is bigger than {@link #mMaxDynamicShortcuts}. + */ + void enforceMaxDynamicShortcuts(int numShortcuts) { + if (numShortcuts > mMaxDynamicShortcuts) { + throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded"); + } + } + + /** + * - Sends a notification to LauncherApps + * - Write to file + */ + private void userPackageChanged(@NonNull String packageName, @UserIdInt int userId) { + notifyListeners(packageName, userId); + scheduleSaveUser(userId); + } + + private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) { + final ArrayList<ShortcutChangeListener> copy; + final List<ShortcutInfo> shortcuts = new ArrayList<>(); + synchronized (mLock) { + copy = new ArrayList<>(mListeners); + + getPackageShortcutsLocked(packageName, userId) + .findAll(shortcuts, /* query =*/ null, ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); + } + for (int i = copy.size() - 1; i >= 0; i--) { + copy.get(i).onShortcutChanged(packageName, shortcuts, userId); + } + } + + /** + * Clean up / validate an incoming shortcut. + * - Make sure all mandatory fields are set. + * - Make sure the intent's extras are persistable, and them to set + * {@link ShortcutInfo#mIntentPersistableExtras}. Also clear its extras. + * - Clear flags. + */ + private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut) { + Preconditions.checkNotNull(shortcut, "Null shortcut detected"); + if (shortcut.getActivityComponent() != null) { + Preconditions.checkState( + shortcut.getPackageName().equals( + shortcut.getActivityComponent().getPackageName()), + "Activity package name mismatch"); + } + + shortcut.enforceMandatoryFields(); + + final Intent intent = shortcut.getIntent(); + final Bundle intentExtras = intent.getExtras(); + if (intentExtras != null && intentExtras.size() > 0) { + intent.replaceExtras((Bundle) null); + + // PersistableBundle's constructor will throw IllegalArgumentException if original + // extras contain something not persistable. + shortcut.setIntentPersistableExtras(new PersistableBundle(intentExtras)); + } + + // TODO Save the icon + shortcut.setIcon(null); + + shortcut.setFlags(0); + } + + // === APIs === + + @Override + public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, + @UserIdInt int userId) { + verifyCaller(packageName, userId); + + final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); + final int size = newShortcuts.size(); + + synchronized (mLock) { + final PackageShortcuts ps = getPackageShortcutsLocked(packageName, userId); + + // Throttling. + if (!ps.tryApiCall(this)) { + return false; + } + enforceMaxDynamicShortcuts(size); + + // Validate the shortcuts. + for (int i = 0; i < size; i++) { + fixUpIncomingShortcutInfo(newShortcuts.get(i)); + } + + // First, remove all un-pinned; dynamic shortcuts + ps.deleteAllDynamicShortcuts(); + + // Then, add/update all. We need to make sure to take over "pinned" flag. + for (int i = 0; i < size; i++) { + final ShortcutInfo newShortcut = newShortcuts.get(i); + newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC); + ps.updateShortcutWithCapping(this, newShortcut); + } + } + userPackageChanged(packageName, userId); + + return true; + } + + @Override + public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, + @UserIdInt int userId) { + verifyCaller(packageName, userId); + + final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); + + synchronized (mLock) { + + if (true) { + throw new RuntimeException("not implemented yet"); + } + + // TODO Similar to setDynamicShortcuts, but don't add new ones, and don't change flags. + // Update non-null fields only. + } + userPackageChanged(packageName, userId); + + return true; + } + + @Override + public boolean addDynamicShortcut(String packageName, ShortcutInfo newShortcut, + @UserIdInt int userId) { + verifyCaller(packageName, userId); + + synchronized (mLock) { + final PackageShortcuts ps = getPackageShortcutsLocked(packageName, userId); + + // Throttling. + if (!ps.tryApiCall(this)) { + return false; + } + + // Validate the shortcut. + fixUpIncomingShortcutInfo(newShortcut); + + // Add it. + newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC); + ps.updateShortcutWithCapping(this, newShortcut); + } + userPackageChanged(packageName, userId); + + return true; + } + + @Override + public void deleteDynamicShortcut(String packageName, String shortcutId, + @UserIdInt int userId) { + verifyCaller(packageName, userId); + Preconditions.checkStringNotEmpty(shortcutId, "shortcutId must be provided"); + + synchronized (mLock) { + getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(shortcutId); + } + userPackageChanged(packageName, userId); + } + + @Override + public void deleteAllDynamicShortcuts(String packageName, @UserIdInt int userId) { + verifyCaller(packageName, userId); + + synchronized (mLock) { + getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(); + } + userPackageChanged(packageName, userId); + } + + @Override + public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName, + @UserIdInt int userId) { + verifyCaller(packageName, userId); + synchronized (mLock) { + return getShortcutsWithQueryLocked( + packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, + ShortcutInfo::isDynamic); + } + } + + @Override + public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName, + @UserIdInt int userId) { + verifyCaller(packageName, userId); + synchronized (mLock) { + return getShortcutsWithQueryLocked( + packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, + ShortcutInfo::isPinned); + } + } + + private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName, + @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) { + + final ArrayList<ShortcutInfo> ret = new ArrayList<>(); + + getPackageShortcutsLocked(packageName, userId).findAll(ret, query, cloneFlags); + + return new ParceledListSlice<>(ret); + } + + @Override + public int getMaxDynamicShortcutCount(String packageName, @UserIdInt int userId) + throws RemoteException { + verifyCaller(packageName, userId); + + return mMaxDynamicShortcuts; + } + + @Override + public int getRemainingCallCount(String packageName, @UserIdInt int userId) { + verifyCaller(packageName, userId); + + synchronized (mLock) { + return mMaxDailyUpdates + - getPackageShortcutsLocked(packageName, userId).getApiCallCount(this); + } + } + + @Override + public long getRateLimitResetTime(String packageName, @UserIdInt int userId) { + verifyCaller(packageName, userId); + + synchronized (mLock) { + return getNextResetTimeLocked(); + } + } + + /** + * Reset all throttling, for developer options and command line. Only system/shell can call it. + */ + @Override + public void resetThrottling() { + enforceSystemOrShell(); + + resetThrottlingInner(); + } + + @VisibleForTesting + void resetThrottlingInner() { + synchronized (mLock) { + mRawLastResetTime = injectCurrentTimeMillis(); + } + scheduleSaveBaseState(); + } + + /** + * Entry point from {@link LauncherApps}. + */ + private class LocalService extends ShortcutServiceInternal { + @Override + public List<ShortcutInfo> getShortcuts( + @NonNull String callingPackage, long changedSince, + @Nullable String packageName, @Nullable ComponentName componentName, + int queryFlags, int userId) { + final ArrayList<ShortcutInfo> ret = new ArrayList<>(); + final int cloneFlag = + ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) == 0) + ? ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER + : ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO; + + synchronized (mLock) { + if (packageName != null) { + getShortcutsInnerLocked(packageName, changedSince, componentName, queryFlags, + userId, ret, cloneFlag); + } else { + final ArrayMap<String, PackageShortcuts> packages = + getUserShortcutsLocked(userId); + for (int i = 0; i < packages.size(); i++) { + getShortcutsInnerLocked( + packages.keyAt(i), + changedSince, componentName, queryFlags, userId, ret, cloneFlag); + } + } + } + return ret; + } + + private void getShortcutsInnerLocked(@Nullable String packageName,long changedSince, + @Nullable ComponentName componentName, int queryFlags, + int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) { + getPackageShortcutsLocked(packageName, userId).findAll(ret, + (ShortcutInfo si) -> { + if (si.getLastChangedTimestamp() < changedSince) { + return false; + } + if (componentName != null + && !componentName.equals(si.getActivityComponent())) { + return false; + } + final boolean matchDynamic = + ((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0) + && si.isDynamic(); + final boolean matchPinned = + ((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0) + && si.isPinned(); + return matchDynamic || matchPinned; + }, cloneFlag); + } + + @Override + public List<ShortcutInfo> getShortcutInfo( + @NonNull String callingPackage, + @NonNull String packageName, @Nullable List<String> ids, int userId) { + // Calling permission must be checked by LauncherAppsImpl. + Preconditions.checkStringNotEmpty(packageName, "packageName"); + + final ArrayList<ShortcutInfo> ret = new ArrayList<>(ids.size()); + final ArraySet<String> idSet = new ArraySet<>(ids); + synchronized (mLock) { + getPackageShortcutsLocked(packageName, userId).findAll(ret, + (ShortcutInfo si) -> idSet.contains(si.getId()), + ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); + } + return ret; + } + + @Override + public void pinShortcuts(@NonNull String callingPackage, @NonNull String packageName, + @NonNull List<String> shortcutIds, int userId) { + // Calling permission must be checked by LauncherAppsImpl. + Preconditions.checkStringNotEmpty(packageName, "packageName"); + Preconditions.checkNotNull(shortcutIds, "shortcutIds"); + + synchronized (mLock) { + getPackageShortcutsLocked(packageName, userId).pinAll(shortcutIds); + } + userPackageChanged(packageName, userId); + } + + @Override + public Intent createShortcutIntent(@NonNull String callingPackage, + @NonNull ShortcutInfo shortcut, int userId) { + // Calling permission must be checked by LauncherAppsImpl. + Preconditions.checkNotNull(shortcut, "shortcut"); + + synchronized (mLock) { + final ShortcutInfo fullShortcut = + getPackageShortcutsLocked(shortcut.getPackageName(), userId) + .getShortcuts().get(shortcut.getId()); + if (fullShortcut == null) { + return null; + } else { + final Intent intent = fullShortcut.getIntent(); + final PersistableBundle extras = fullShortcut.getIntentPersistableExtras(); + if (extras != null) { + intent.replaceExtras(new Bundle(extras)); + } + + return intent; + } + } + } + + @Override + public void addListener(@NonNull ShortcutChangeListener listener) { + synchronized (mLock) { + mListeners.add(Preconditions.checkNotNull(listener)); + } + } + } + + // === Dump === + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump UserManager from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " without permission " + + android.Manifest.permission.DUMP); + return; + } + dumpInner(pw); + } + + @VisibleForTesting + void dumpInner(PrintWriter pw) { + synchronized (mLock) { + final long now = injectCurrentTimeMillis(); + pw.print("Now: ["); + pw.print(now); + pw.print("] "); + pw.print(formatTime(now)); + pw.print(" Raw last reset: ["); + pw.print(mRawLastResetTime); + pw.print("] "); + pw.print(formatTime(mRawLastResetTime)); + + final long last = getLastResetTimeLocked(); + final long next = getNextResetTimeLocked(); + pw.print(" Last reset: ["); + pw.print(last); + pw.print("] "); + pw.print(formatTime(last)); + + pw.print(" Next reset: ["); + pw.print(next); + pw.print("] "); + pw.print(formatTime(next)); + pw.println(); + + pw.println(); + + for (int i = 0; i < mShortcuts.size(); i++) { + dumpUserLocked(pw, mShortcuts.keyAt(i)); + } + + } + } + + private void dumpUserLocked(PrintWriter pw, int userId) { + pw.print(" User: "); + pw.print(userId); + pw.println(); + + final ArrayMap<String, PackageShortcuts> packages = mShortcuts.get(userId); + if (packages == null) { + return; + } + for (int j = 0; j < packages.size(); j++) { + dumpPackageLocked(pw, userId, packages.keyAt(j)); + } + pw.println(); + } + + private void dumpPackageLocked(PrintWriter pw, int userId, String packageName) { + final PackageShortcuts shortcuts = mShortcuts.get(userId).get(packageName); + if (shortcuts == null) { + return; + } + + pw.print(" Package: "); + pw.print(packageName); + pw.println(); + + pw.print(" Calls: "); + pw.print(shortcuts.getApiCallCount(this)); + pw.println(); + + // This should be after getApiCallCount(), which may update it. + pw.print(" Last reset: ["); + pw.print(shortcuts.mLastResetTime); + pw.print("] "); + pw.print(formatTime(shortcuts.mLastResetTime)); + pw.println(); + + pw.println(" Shortcuts:"); + final int size = shortcuts.getShortcuts().size(); + for (int i = 0; i < size; i++) { + pw.print(" "); + pw.println(shortcuts.getShortcuts().valueAt(i).toInsecureString()); + } + } + + private static String formatTime(long time) { + Time tobj = new Time(); + tobj.set(time); + return tobj.format("%Y-%m-%d %H:%M:%S"); + } + + // === Shell support === + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ResultReceiver resultReceiver) throws RemoteException { + + enforceShell(); + + (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver); + } + + /** + * Handle "adb shell cmd". + */ + private class MyShellCommand extends ShellCommand { + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + final PrintWriter pw = getOutPrintWriter(); + switch(cmd) { + case "reset-package-throttling": + return handleResetPackageThrottling(); + case "reset-throttling": + return handleResetThrottling(); + default: + return handleDefaultCommands(cmd); + } + } + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.println("Usage: cmd shortcut COMMAND [options ...]"); + pw.println(); + pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE"); + pw.println(" Reset throttling for a package"); + pw.println(); + pw.println("cmd shortcut reset-throttling"); + pw.println(" Reset throttling for all packages and users"); + pw.println(); + } + + private int handleResetThrottling() { + resetThrottling(); + return 0; + } + + private int handleResetPackageThrottling() { + final PrintWriter pw = getOutPrintWriter(); + + int userId = UserHandle.USER_SYSTEM; + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "--user": + userId = UserHandle.parseUserArg(getNextArgRequired()); + break; + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + final String packageName = getNextArgRequired(); + + synchronized (mLock) { + getPackageShortcutsLocked(packageName, userId).resetRateLimitingForCommandLine(); + saveUserLocked(userId); + } + + return 0; + } + } + + // === Unit test support === + + // Injection point. + long injectCurrentTimeMillis() { + return System.currentTimeMillis(); + } + + // Injection point. + int injectBinderCallingUid() { + return getCallingUid(); + } + + File injectSystemDataPath() { + return Environment.getDataSystemDirectory(); + } + + File injectUserDataPath(@UserIdInt int userId) { + return new File(Environment.getDataSystemDeDirectory(userId), DIRECTORY_PER_USER); + } + + @VisibleForTesting + SparseArray<ArrayMap<String, PackageShortcuts>> getShortcutsForTest() { + return mShortcuts; + } + + @VisibleForTesting + void setMaxDynamicShortcutsForTest(int max) { + mMaxDynamicShortcuts = max; + } + + @VisibleForTesting + void setMaxDailyUpdatesForTest(int max) { + mMaxDailyUpdates = max; + } + + @VisibleForTesting + public void setResetIntervalForTest(long interval) { + mResetInterval = interval; + } +} diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index e51a2e1aa526..83a5ef53e68c 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3360,8 +3360,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { throws RemoteException { synchronized (mLock) { IShortcutService service = mShortcutKeyServices.get(shortcutCode); - if (service != null && service.asBinder().isBinderAlive()) { - throw new RemoteException("Key already exists."); + if (service != null && service.asBinder().pingBinder()) { + throw new RemoteException("Key already exists."); } mShortcutKeyServices.put(shortcutCode, shortcutService); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 6218c4e61b59..91d86710a1a9 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -50,6 +50,7 @@ import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; import android.provider.Settings.Secure; +import android.provider.Settings.SettingNotFoundException; import android.service.dreams.DreamManagerInternal; import android.util.EventLog; import android.util.Slog; @@ -57,6 +58,7 @@ import android.util.SparseIntArray; import android.util.TimeUtils; import android.view.Display; import android.view.WindowManagerPolicy; + import com.android.internal.app.IAppOpsService; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BackgroundThread; @@ -537,6 +539,8 @@ public final class PowerManagerService extends SystemService } } mBootCompletedRunnables = null; + + incrementBootCount(); } } } @@ -772,7 +776,7 @@ public final class PowerManagerService extends SystemService } } - void updateLowPowerModeLocked() { + private void updateLowPowerModeLocked() { if (mIsPowered && mLowPowerModeSetting) { if (DEBUG_SPEW) { Slog.d(TAG, "updateLowPowerModeLocked: powered, turning setting off"); @@ -2912,6 +2916,20 @@ public final class PowerManagerService extends SystemService return suspendBlocker; } + private void incrementBootCount() { + synchronized (mLock) { + int count; + try { + count = Settings.Global.getInt( + getContext().getContentResolver(), Settings.Global.BOOT_COUNT); + } catch (SettingNotFoundException e) { + count = 0; + } + Settings.Global.putInt( + getContext().getContentResolver(), Settings.Global.BOOT_COUNT, count + 1); + } + } + private static WorkSource copyWorkSource(WorkSource workSource) { return workSource != null ? new WorkSource(workSource) : null; } diff --git a/services/core/java/com/android/server/utils/ManagedApplicationService.java b/services/core/java/com/android/server/utils/ManagedApplicationService.java new file mode 100644 index 000000000000..a64570130e45 --- /dev/null +++ b/services/core/java/com/android/server/utils/ManagedApplicationService.java @@ -0,0 +1,220 @@ +/** + * Copyright (c) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.utils; + +import android.annotation.NonNull; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.IInterface; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Slog; + +import java.util.Objects; + +/** + * Manages the lifecycle of an application-provided service bound from system server. + * + * @hide + */ +public class ManagedApplicationService { + private final String TAG = getClass().getSimpleName(); + + private final Context mContext; + private final int mUserId; + private final ComponentName mComponent; + private final int mClientLabel; + private final String mSettingsAction; + private final BinderChecker mChecker; + + private final DeathRecipient mDeathRecipient = new DeathRecipient() { + @Override + public void binderDied() { + synchronized (mLock) { + mBoundInterface = null; + } + } + }; + + private final Object mLock = new Object(); + + // State protected by mLock + private ServiceConnection mPendingConnection; + private ServiceConnection mConnection; + private IInterface mBoundInterface; + + + private ManagedApplicationService(final Context context, final ComponentName component, + final int userId, int clientLabel, String settingsAction, + BinderChecker binderChecker) { + mContext = context; + mComponent = component; + mUserId = userId; + mClientLabel = clientLabel; + mSettingsAction = settingsAction; + mChecker = binderChecker; + } + + /** + * Implement to validate returned IBinder instance. + */ + public interface BinderChecker { + IInterface asInterface(IBinder binder); + boolean checkType(IInterface service); + } + + /** + * Create a new ManagedApplicationService object but do not yet bind to the user service. + * + * @param context a Context to use for binding the application service. + * @param component the {@link ComponentName} of the application service to bind. + * @param userId the user ID of user to bind the application service as. + * @param clientLabel the resource ID of a label displayed to the user indicating the + * binding service. + * @param settingsAction an action that can be used to open the Settings UI to enable/disable + * binding to these services. + * @param binderChecker an interface used to validate the returned binder object. + * @return a ManagedApplicationService instance. + */ + public static ManagedApplicationService build(@NonNull final Context context, + @NonNull final ComponentName component, final int userId, @NonNull int clientLabel, + @NonNull String settingsAction, @NonNull BinderChecker binderChecker) { + return new ManagedApplicationService(context, component, userId, clientLabel, + settingsAction, binderChecker); + } + + /** + * @return the user ID of the user that owns the bound service. + */ + public int getUserId() { + return mUserId; + } + + /** + * @return the component of the bound service. + */ + public ComponentName getComponent() { + return mComponent; + } + + /** + * Asynchronously unbind from the application service if the bound service component and user + * does not match the given signature. + * + * @param componentName the component that must match. + * @param userId the user ID that must match. + * @return {@code true} if not matching. + */ + public boolean disconnectIfNotMatching(final ComponentName componentName, final int userId) { + if (matches(componentName, userId)) { + return false; + } + disconnect(); + return true; + } + + /** + * Asynchronously unbind from the application service if bound. + */ + public void disconnect() { + synchronized (mLock) { + // Wipe out pending connections + mPendingConnection = null; + + // Unbind existing connection, if it exists + if (mConnection != null) { + mContext.unbindService(mConnection); + mConnection = null; + } + + mBoundInterface = null; + } + } + + /** + * Asynchronously bind to the application service if not bound. + */ + public void connect() { + synchronized (mLock) { + if (mConnection != null || mPendingConnection != null) { + // We're already connected or are trying to connect + return; + } + + final PendingIntent pendingIntent = PendingIntent.getActivity( + mContext, 0, new Intent(mSettingsAction), 0); + final Intent intent = new Intent().setComponent(mComponent). + putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel). + putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent); + + final ServiceConnection serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + synchronized (mLock) { + if (mPendingConnection == this) { + // No longer pending, remove from pending connection + mPendingConnection = null; + mConnection = this; + } else { + // Service connection wasn't pending, must have been disconnected + mContext.unbindService(this); + } + + try { + iBinder.linkToDeath(mDeathRecipient, 0); + mBoundInterface = mChecker.asInterface(iBinder); + if (!mChecker.checkType(mBoundInterface)) { + // Received an invalid binder, disconnect + mContext.unbindService(this); + mBoundInterface = null; + } + } catch (RemoteException e) { + // DOA + Slog.w(TAG, "Unable to bind service: " + intent, e); + mBoundInterface = null; + } + } + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + Slog.w(TAG, "Service disconnected: " + intent); + } + }; + + mPendingConnection = serviceConnection; + + try { + if (!mContext.bindServiceAsUser(intent, serviceConnection, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, + new UserHandle(mUserId))) { + Slog.w(TAG, "Unable to bind service: " + intent); + } + } catch (SecurityException e) { + Slog.w(TAG, "Unable to bind service: " + intent, e); + } + } + } + + private boolean matches(final ComponentName component, final int userId) { + return Objects.equals(mComponent, component) && mUserId == userId; + } +} diff --git a/services/core/java/com/android/server/vr/EnabledComponentsObserver.java b/services/core/java/com/android/server/vr/EnabledComponentsObserver.java new file mode 100644 index 000000000000..1363fb977134 --- /dev/null +++ b/services/core/java/com/android/server/vr/EnabledComponentsObserver.java @@ -0,0 +1,282 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.vr; + +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.content.PackageMonitor; +import com.android.server.vr.SettingsObserver.SettingChangeListener; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * Detects changes in packages, settings, and current users that may affect whether components + * implementing a given service can be run. + * + * @hide + */ +public class EnabledComponentsObserver implements SettingChangeListener { + + private static final String TAG = EnabledComponentsObserver.class.getSimpleName(); + private static final String ENABLED_SERVICES_SEPARATOR = ":"; + + public static final int NO_ERROR = 0; + public static final int DISABLED = -1; + public static final int NOT_INSTALLED = -2; + + private final Object mLock; + private final Context mContext; + private final String mSettingName; + private final String mServiceName; + private final String mServicePermission; + private final SparseArray<ArraySet<ComponentName>> mInstalledSet = new SparseArray<>(); + private final SparseArray<ArraySet<ComponentName>> mEnabledSet = new SparseArray<>(); + private final Set<EnabledComponentChangeListener> mEnabledComponentListeners = new ArraySet<>(); + + /** + * Implement this to receive callbacks when relevant changes to the allowed components occur. + */ + public interface EnabledComponentChangeListener { + + /** + * Called when a change in the allowed components occurs. + */ + void onEnabledComponentChanged(); + } + + private EnabledComponentsObserver(@NonNull Context context, @NonNull String settingName, + @NonNull String servicePermission, @NonNull String serviceName, @NonNull Object lock, + @NonNull Collection<EnabledComponentChangeListener> listeners) { + mLock = lock; + mContext = context; + mSettingName = settingName; + mServiceName = serviceName; + mServicePermission = servicePermission; + mEnabledComponentListeners.addAll(listeners); + } + + /** + * Create a EnabledComponentObserver instance. + * + * @param context the context to query for changes. + * @param handler a handler to receive lifecycle events from system services on. + * @param settingName the name of a setting to monitor for a list of enabled components. + * @param looper a {@link Looper} to use for receiving package callbacks. + * @param servicePermission the permission required by the components to be bound. + * @param serviceName the intent action implemented by the tracked components. + * @param lock a lock object used to guard instance state in all callbacks and method calls. + * @return an EnableComponentObserver instance. + */ + public static EnabledComponentsObserver build(@NonNull Context context, + @NonNull Handler handler, @NonNull String settingName, @NonNull Looper looper, + @NonNull String servicePermission, @NonNull String serviceName, + @NonNull final Object lock, + @NonNull Collection<EnabledComponentChangeListener> listeners) { + + SettingsObserver s = SettingsObserver.build(context, handler, settingName); + + final EnabledComponentsObserver o = new EnabledComponentsObserver(context, settingName, + servicePermission, serviceName, lock, listeners); + + PackageMonitor packageMonitor = new PackageMonitor() { + @Override + public void onSomePackagesChanged() { + o.onPackagesChanged(); + + } + + @Override + public void onPackageDisappeared(String packageName, int reason) { + o.onPackagesChanged(); + + } + + @Override + public void onPackageModified(String packageName) { + o.onPackagesChanged(); + + } + + @Override + public boolean onHandleForceStop(Intent intent, String[] packages, int uid, + boolean doit) { + o.onPackagesChanged(); + + return super.onHandleForceStop(intent, packages, uid, doit); + } + }; + + packageMonitor.register(context, looper, UserHandle.ALL, true); + + s.addListener(o); + + return o; + + } + + public void onPackagesChanged() { + rebuildAll(); + } + + @Override + public void onSettingChanged() { + rebuildAll(); + } + + @Override + public void onSettingRestored(String prevValue, String newValue, int userId) { + rebuildAll(); + } + + public void onUsersChanged() { + rebuildAll(); + } + + /** + * Rebuild the sets of allowed components for each current user profile. + */ + public void rebuildAll() { + synchronized (mLock) { + mInstalledSet.clear(); + mEnabledSet.clear(); + final int[] userIds = getCurrentProfileIds(); + for (int i : userIds) { + ArraySet<ComponentName> implementingPackages = loadComponentNamesForUser(i); + ArraySet<ComponentName> packagesFromSettings = + loadComponentNamesFromSetting(mSettingName, i); + packagesFromSettings.retainAll(implementingPackages); + + mInstalledSet.put(i, implementingPackages); + mEnabledSet.put(i, packagesFromSettings); + + } + } + sendSettingChanged(); + } + + /** + * Check whether a given component is present and enabled for the given user. + * + * @param component the component to check. + * @param userId the user ID for the component to check. + * @return {@code true} if present and enabled. + */ + public int isValid(ComponentName component, int userId) { + synchronized (mLock) { + ArraySet<ComponentName> installedComponents = mInstalledSet.get(userId); + if (installedComponents == null || !installedComponents.contains(component)) { + return NOT_INSTALLED; + } + ArraySet<ComponentName> validComponents = mEnabledSet.get(userId); + if (validComponents == null || !validComponents.contains(component)) { + return DISABLED; + } + return NO_ERROR; + } + } + + private int[] getCurrentProfileIds() { + UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + if (userManager == null) { + return null; + } + int currentUserId = ActivityManager.getCurrentUser(); + List<UserInfo> profiles = userManager.getProfiles(currentUserId); + if (profiles == null) { + return null; + } + final int s = profiles.size(); + int[] userIds = new int[s]; + int ctr = 0; + for (UserInfo info : profiles) { + userIds[ctr++] = info.id; + } + return userIds; + } + + private ArraySet<ComponentName> loadComponentNamesForUser(int userId) { + ArraySet<ComponentName> installed = new ArraySet<>(); + PackageManager pm = mContext.getPackageManager(); + Intent queryIntent = new Intent(mServiceName); + List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser( + queryIntent, + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, + userId); + if (installedServices != null) { + for (int i = 0, count = installedServices.size(); i < count; i++) { + ResolveInfo resolveInfo = installedServices.get(i); + ServiceInfo info = resolveInfo.serviceInfo; + + ComponentName component = new ComponentName(info.packageName, info.name); + if (!mServicePermission.equals(info.permission)) { + Slog.w(TAG, "Skipping service " + info.packageName + "/" + info.name + + ": it does not require the permission " + + mServicePermission); + continue; + } + installed.add(component); + } + } + return installed; + } + + private ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName, + int userId) { + final ContentResolver cr = mContext.getContentResolver(); + String settingValue = Settings.Secure.getStringForUser( + cr, + settingName, + userId); + if (TextUtils.isEmpty(settingValue)) + return new ArraySet<>(); + String[] restored = settingValue.split(ENABLED_SERVICES_SEPARATOR); + ArraySet<ComponentName> result = new ArraySet<>(restored.length); + for (int i = 0; i < restored.length; i++) { + ComponentName value = ComponentName.unflattenFromString(restored[i]); + if (null != value) { + result.add(value); + } + } + return result; + } + + private void sendSettingChanged() { + for (EnabledComponentChangeListener l : mEnabledComponentListeners) { + l.onEnabledComponentChanged(); + } + } + +} diff --git a/services/core/java/com/android/server/vr/SettingsObserver.java b/services/core/java/com/android/server/vr/SettingsObserver.java new file mode 100644 index 000000000000..ce76863b0c16 --- /dev/null +++ b/services/core/java/com/android/server/vr/SettingsObserver.java @@ -0,0 +1,145 @@ +/** + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.vr; + +import android.annotation.NonNull; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.ArraySet; + +import java.util.Objects; +import java.util.Set; + +/** + * Detects changes in a given setting. + * + * @hide + */ +public class SettingsObserver { + + private final String mSecureSettingName; + private final BroadcastReceiver mSettingRestorReceiver; + private final ContentObserver mContentObserver; + private final Set<SettingChangeListener> mSettingsListeners = new ArraySet<>(); + + /** + * Implement this to receive callbacks when the setting tracked by this observer changes. + */ + public interface SettingChangeListener { + + /** + * Called when the tracked setting has changed. + */ + void onSettingChanged(); + + + /** + * Called when the tracked setting has been restored for a particular user. + * + * @param prevValue the previous value of the setting. + * @param newValue the new value of the setting. + * @param userId the user ID for which this setting has been restored. + */ + void onSettingRestored(String prevValue, String newValue, int userId); + } + + private SettingsObserver(@NonNull final Context context, @NonNull final Handler handler, + @NonNull final Uri settingUri, @NonNull final String secureSettingName) { + + mSecureSettingName = secureSettingName; + mSettingRestorReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_SETTING_RESTORED.equals(intent.getAction())) { + String element = intent.getStringExtra(Intent.EXTRA_SETTING_NAME); + if (Objects.equals(element, secureSettingName)) { + String prevValue = intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE); + String newValue = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE); + sendSettingRestored(prevValue, newValue, getSendingUserId()); + } + } + } + }; + + mContentObserver = new ContentObserver(handler) { + @Override + public void onChange(boolean selfChange, Uri uri) { + if (uri == null || settingUri.equals(uri)) { + sendSettingChanged(); + } + } + }; + + ContentResolver resolver = context.getContentResolver(); + resolver.registerContentObserver(settingUri, false, mContentObserver, + UserHandle.USER_ALL); + } + + /** + * Create a SettingsObserver instance. + * + * @param context the context to query for settings changes. + * @param handler the handler to use for a settings ContentObserver. + * @param settingName the setting to track. + * @return a SettingsObserver instance. + */ + public static SettingsObserver build(@NonNull Context context, @NonNull Handler handler, + @NonNull String settingName) { + Uri settingUri = Settings.Secure.getUriFor(settingName); + + return new SettingsObserver(context, handler, settingUri, settingName); + } + + /** + * Add a listener for setting changes. + * + * @param listener a {@link SettingChangeListener} instance. + */ + public void addListener(@NonNull SettingChangeListener listener) { + mSettingsListeners.add(listener); + + } + + /** + * Remove a listener for setting changes. + * + * @param listener a {@link SettingChangeListener} instance. + */ + public void removeListener(@NonNull SettingChangeListener listener) { + mSettingsListeners.remove(listener); + + } + + private void sendSettingChanged() { + for (SettingChangeListener l : mSettingsListeners) { + l.onSettingChanged(); + } + } + + private void sendSettingRestored(final String prevValue, final String newValue, final int userId) { + for (SettingChangeListener l : mSettingsListeners) { + l.onSettingRestored(prevValue, newValue, userId); + } + } + +} diff --git a/services/core/java/com/android/server/vr/VrManagerInternal.java b/services/core/java/com/android/server/vr/VrManagerInternal.java index 42db364e6acc..6b5523f0510b 100644 --- a/services/core/java/com/android/server/vr/VrManagerInternal.java +++ b/services/core/java/com/android/server/vr/VrManagerInternal.java @@ -1,4 +1,4 @@ -/* +/** * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,14 +15,22 @@ */ package com.android.server.vr; +import android.annotation.NonNull; +import android.content.ComponentName; + /** - * VR mode local system service interface. + * Service for accessing the VR mode manager. * * @hide Only for use within system server. */ public abstract class VrManagerInternal { /** + * The error code returned on success. + */ + public static final int NO_ERROR = 0; + + /** * Return current VR mode state. * * @return {@code true} if VR mode is enabled. @@ -33,8 +41,11 @@ public abstract class VrManagerInternal { * Set the current VR mode state. * * @param enabled {@code true} to enable VR mode. + * @param packageName The package name of the requested VrListenerService to bind. + * @param userId the user requesting the VrListenerService component. */ - public abstract void setVrMode(boolean enabled); + public abstract void setVrMode(boolean enabled, @NonNull ComponentName packageName, + int userId); /** * Add a listener for VR mode state changes. @@ -43,13 +54,23 @@ public abstract class VrManagerInternal { * </p> * @param listener the listener instance to add. */ - public abstract void registerListener(VrStateListener listener); + public abstract void registerListener(@NonNull VrStateListener listener); /** * Remove the listener from the current set of listeners. * * @param listener the listener to remove. */ - public abstract void unregisterListener(VrStateListener listener); + public abstract void unregisterListener(@NonNull VrStateListener listener); + + /** + * Return NO_ERROR if the given package is installed on the device and enabled as a + * VrListenerService for the given current user, or a negative error code indicating a failure. + * + * @param packageName the name of the package to check, or null to select the default package. + * @return NO_ERROR if the given package is installed and is enabled, or a negative error code + * given in {@link android.service.vr.VrModeException} on failure. + */ + public abstract int hasVrPackage(@NonNull ComponentName packageName, int userId); } diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index 7611527d2b99..f5914fafccf7 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -1,4 +1,4 @@ -/* +/** * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,26 +16,53 @@ package com.android.server.vr; import android.app.AppOpsManager; +import android.annotation.NonNull; import android.content.Context; +import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; +import android.os.IInterface; +import android.os.Looper; import android.os.UserHandle; +import android.provider.Settings; +import android.service.vr.IVrListener; +import android.service.vr.VrListenerService; import android.util.ArraySet; import android.util.Slog; +import com.android.internal.R; import com.android.server.SystemService; +import com.android.server.vr.EnabledComponentsObserver.EnabledComponentChangeListener; +import com.android.server.utils.ManagedApplicationService; +import com.android.server.utils.ManagedApplicationService.BinderChecker; import java.util.ArrayList; +import java.util.Set; /** - * Service tracking whether VR mode is active, and notifying listening system services of state - * changes. + * Service tracking whether VR mode is active, and notifying listening services of state changes. + * <p/> + * Services running in system server may modify the state of VrManagerService via the interface in + * VrManagerInternal, and may register to receive callbacks when the system VR mode changes via the + * interface given in VrStateListener. + * <p/> + * Device vendors may choose to receive VR state changes by implementing the VR mode HAL, e.g.: + * hardware/libhardware/modules/vr + * <p/> + * In general applications may enable or disable VR mode by calling + * {@link android.app.Activity#setVrMode)}. An application may also implement a service to be run + * while in VR mode by implementing {@link android.service.vr.VrListenerService}. + * + * @see {@link android.service.vr.VrListenerService} + * @see {@link com.android.server.vr.VrManagerInternal} + * @see {@link com.android.server.vr.VrStateListener} * - * {@hide} + * @hide */ -public class VrManagerService extends SystemService { +public class VrManagerService extends SystemService implements EnabledComponentChangeListener{ public static final String TAG = "VrManagerService"; @@ -46,9 +73,45 @@ public class VrManagerService extends SystemService { private final IBinder mOverlayToken = new Binder(); + // State protected by mLock private boolean mVrModeEnabled = false; - private ArraySet<VrStateListener> mListeners = new ArraySet<>(); + private final Set<VrStateListener> mListeners = new ArraySet<>(); + private EnabledComponentsObserver mComponentObserver; + private ManagedApplicationService mCurrentVrService; + private Context mContext; + + private static final BinderChecker sBinderChecker = new BinderChecker() { + @Override + public IInterface asInterface(IBinder binder) { + return IVrListener.Stub.asInterface(binder); + } + + @Override + public boolean checkType(IInterface service) { + return service instanceof IVrListener; + } + }; + + /** + * Called when a user, package, or setting changes that could affect whether or not the + * currently bound VrListenerService is changed. + */ + @Override + public void onEnabledComponentChanged() { + synchronized (mLock) { + if (mCurrentVrService == null) { + return; // No active services + } + + // There is an active service, update it if needed + updateCurrentVrServiceLocked(mVrModeEnabled, mCurrentVrService.getComponent(), + mCurrentVrService.getUserId()); + } + } + /** + * Implementation of VrManagerInternal. Callable only from system services. + */ private final class LocalService extends VrManagerInternal { @Override public boolean isInVrMode() { @@ -56,8 +119,8 @@ public class VrManagerService extends SystemService { } @Override - public void setVrMode(boolean enabled) { - VrManagerService.this.setVrMode(enabled); + public void setVrMode(boolean enabled, ComponentName packageName, int userId) { + VrManagerService.this.setVrMode(enabled, packageName, userId); } @Override @@ -69,6 +132,11 @@ public class VrManagerService extends SystemService { public void unregisterListener(VrStateListener listener) { VrManagerService.this.removeListener(listener); } + + @Override + public int hasVrPackage(ComponentName packageName, int userId) { + return VrManagerService.this.hasVrPackage(packageName, userId); + } } public VrManagerService(Context context) { @@ -79,33 +147,57 @@ public class VrManagerService extends SystemService { public void onStart() { synchronized(mLock) { initializeNative(); + mContext = getContext(); } publishLocalService(VrManagerInternal.class, new LocalService()); } - private void addListener(VrStateListener listener) { + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { + synchronized (mLock) { + Looper looper = Looper.getMainLooper(); + Handler handler = new Handler(looper); + ArrayList<EnabledComponentChangeListener> listeners = new ArrayList<>(); + listeners.add(this); + mComponentObserver = EnabledComponentsObserver.build(mContext, handler, + Settings.Secure.ENABLED_VR_LISTENERS, looper, + android.Manifest.permission.BIND_VR_LISTENER_SERVICE, + VrListenerService.SERVICE_INTERFACE, mLock, listeners); + + mComponentObserver.rebuildAll(); + } + } + } + + @Override + public void onStartUser(int userHandle) { synchronized (mLock) { - mListeners.add(listener); + mComponentObserver.onUsersChanged(); } } - private void removeListener(VrStateListener listener) { + @Override + public void onSwitchUser(int userHandle) { synchronized (mLock) { - mListeners.remove(listener); + mComponentObserver.onUsersChanged(); + } + + } + + @Override + public void onStopUser(int userHandle) { + synchronized (mLock) { + mComponentObserver.onUsersChanged(); } + } - private void setVrMode(boolean enabled) { + @Override + public void onCleanupUser(int userHandle) { synchronized (mLock) { - if (mVrModeEnabled != enabled) { - mVrModeEnabled = enabled; - // Log mode change event. - Slog.i(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled")); - setVrModeNative(mVrModeEnabled); - updateOverlayStateLocked(); - onVrModeChangedLocked(); - } + mComponentObserver.onUsersChanged(); } } @@ -122,18 +214,134 @@ public class VrManagerService extends SystemService { } } - private boolean getVrMode() { - synchronized (mLock) { - return mVrModeEnabled; + /** + * Send VR mode changes (if the mode state has changed), and update the bound/unbound state of + * the currently selected VR listener service. If the component selected for the VR listener + * service has changed, unbind the previous listener and bind the new listener (if enabled). + * <p/> + * Note: Must be called while holding {@code mLock}. + * + * @param enabled new state for VR mode. + * @param component new component to be bound as a VR listener. + * @param userId user owning the component to be bound. + * + * @return {@code true} if the component/user combination specified is valid. + */ + private boolean updateCurrentVrServiceLocked(boolean enabled, + @NonNull ComponentName component, int userId) { + + // Always send mode change events. + changeVrModeLocked(enabled); + + boolean validUserComponent = (mComponentObserver.isValid(component, userId) == + EnabledComponentsObserver.NO_ERROR); + + if (!enabled || !validUserComponent) { + // Unbind whatever is running + if (mCurrentVrService != null) { + Slog.i(TAG, "Disconnecting " + mCurrentVrService.getComponent() + " for user " + + mCurrentVrService.getUserId()); + mCurrentVrService.disconnect(); + mCurrentVrService = null; + } + return validUserComponent; + } + + if (mCurrentVrService != null) { + // Unbind any running service that doesn't match the component/user selection + if (mCurrentVrService.disconnectIfNotMatching(component, userId)) { + Slog.i(TAG, "Disconnecting " + mCurrentVrService.getComponent() + " for user " + + mCurrentVrService.getUserId()); + mCurrentVrService = VrManagerService.create(mContext, component, userId); + mCurrentVrService.connect(); + Slog.i(TAG, "Connecting " + mCurrentVrService.getComponent() + " for user " + + mCurrentVrService.getUserId()); + } + // The service with the correct component/user is bound + } else { + // Nothing was previously running, bind a new service + mCurrentVrService = VrManagerService.create(mContext, component, userId); + mCurrentVrService.connect(); + Slog.i(TAG, "Connecting " + mCurrentVrService.getComponent() + " for user " + + mCurrentVrService.getUserId()); + } + + return validUserComponent; + } + + /** + * Send VR mode change callbacks to HAL and system services if mode has actually changed. + * <p/> + * Note: Must be called while holding {@code mLock}. + * + * @param enabled new state of the VR mode. + */ + private void changeVrModeLocked(boolean enabled) { + if (mVrModeEnabled != enabled) { + mVrModeEnabled = enabled; + + // Log mode change event. + Slog.i(TAG, "VR mode " + ((mVrModeEnabled) ? "enabled" : "disabled")); + setVrModeNative(mVrModeEnabled); + + updateOverlayStateLocked(); + onVrModeChangedLocked(); } } /** * Notify system services of VR mode change. + * <p/> + * Note: Must be called while holding {@code mLock}. */ private void onVrModeChangedLocked() { for (VrStateListener l : mListeners) { l.onVrStateChanged(mVrModeEnabled); } } + + /** + * Helper function for making ManagedApplicationService instances. + */ + private static ManagedApplicationService create(@NonNull Context context, + @NonNull ComponentName component, int userId) { + return ManagedApplicationService.build(context, component, userId, + R.string.vr_listener_binding_label, Settings.ACTION_VR_LISTENER_SETTINGS, + sBinderChecker); + } + + /* + * Implementation of VrManagerInternal calls. These are callable from system services. + */ + + private boolean setVrMode(boolean enabled, @NonNull ComponentName targetPackageName, + int userId) { + synchronized (mLock) { + return updateCurrentVrServiceLocked(enabled, targetPackageName, userId); + } + } + + private boolean getVrMode() { + synchronized (mLock) { + return mVrModeEnabled; + } + } + + private void addListener(VrStateListener listener) { + synchronized (mLock) { + mListeners.add(listener); + } + } + + private void removeListener(VrStateListener listener) { + synchronized (mLock) { + mListeners.remove(listener); + } + } + + private int hasVrPackage(@NonNull ComponentName targetPackageName, int userId) { + synchronized (mLock) { + return mComponentObserver.isValid(targetPackageName, userId); + } + } } diff --git a/services/core/java/com/android/server/vr/VrStateListener.java b/services/core/java/com/android/server/vr/VrStateListener.java index b8af4b2d4425..b0603c80cc38 100644 --- a/services/core/java/com/android/server/vr/VrStateListener.java +++ b/services/core/java/com/android/server/vr/VrStateListener.java @@ -1,4 +1,4 @@ -/* +/** * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +16,9 @@ package com.android.server.vr; /** - * Listener for state changes in VrManagerService, + * Listener for state changes in VrManagerService. + * + * @hide Only for use within system server. */ public abstract class VrStateListener { diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java index a81fba00f7a0..55b3c7baf4a2 100644 --- a/services/core/java/com/android/server/wm/AppWindowAnimator.java +++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java @@ -190,7 +190,8 @@ public class AppWindowAnimator { public void clearThumbnail() { if (thumbnail != null) { - thumbnail.destroy(); + thumbnail.hide(); + mService.mWindowPlacerLocked.destroyAfterTransaction(thumbnail); thumbnail = null; } deferThumbnailDestruction = false; diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java index fc5d8ce53257..95be233e44b2 100644 --- a/services/core/java/com/android/server/wm/DimLayer.java +++ b/services/core/java/com/android/server/wm/DimLayer.java @@ -84,10 +84,13 @@ public class DimLayer { /** The user of this dim layer. */ private final DimLayerUser mUser; - DimLayer(WindowManagerService service, DimLayerUser user, int displayId) { + private final String mName; + + DimLayer(WindowManagerService service, DimLayerUser user, int displayId, String name) { mUser = user; mDisplayId = displayId; mService = service; + mName = name; if (DEBUG_DIM_LAYER) Slog.v(TAG, "Ctor: displayId=" + displayId); } @@ -100,7 +103,7 @@ public class DimLayer { 16, 16, PixelFormat.OPAQUE, SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN); } else { - mDimSurface = new SurfaceControl(service.mFxSession, TAG, + mDimSurface = new SurfaceControl(service.mFxSession, mName, 16, 16, PixelFormat.OPAQUE, SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN); } diff --git a/services/core/java/com/android/server/wm/DimLayerController.java b/services/core/java/com/android/server/wm/DimLayerController.java index 6d1cec4a9f10..97d0ae0f8546 100644 --- a/services/core/java/com/android/server/wm/DimLayerController.java +++ b/services/core/java/com/android/server/wm/DimLayerController.java @@ -10,6 +10,8 @@ import android.util.ArrayMap; import android.util.Slog; import android.util.TypedValue; +import com.android.server.wm.DimLayer.DimLayerUser; + import java.io.PrintWriter; /** @@ -18,7 +20,8 @@ import java.io.PrintWriter; * as well as other use cases (such as dimming above a dead window). */ class DimLayerController { - private static final String TAG = TAG_WITH_CLASS_NAME ? "DimLayerController" : TAG_WM; + private static final String TAG_LOCAL = "DimLayerController"; + private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM; /** Amount of time in milliseconds to animate the dim surface from one value to another, * when no window animation is driving it. */ @@ -63,7 +66,8 @@ class DimLayerController { newDimLayer = state.dimLayer; } else { // Create new full screen dim layer. - newDimLayer = new DimLayer(mDisplayContent.mService, dimLayerUser, displayId); + newDimLayer = new DimLayer(mDisplayContent.mService, dimLayerUser, displayId, + getDimLayerTag(dimLayerUser)); } dimLayerUser.getDimBounds(mTmpBounds); newDimLayer.setBounds(mTmpBounds); @@ -73,7 +77,8 @@ class DimLayerController { } } else { newDimLayer = (state.dimLayer == null || previousFullscreen) - ? new DimLayer(mDisplayContent.mService, dimLayerUser, displayId) + ? new DimLayer(mDisplayContent.mService, dimLayerUser, displayId, + getDimLayerTag(dimLayerUser)) : state.dimLayer; dimLayerUser.getDimBounds(mTmpBounds); newDimLayer.setBounds(mTmpBounds); @@ -81,6 +86,10 @@ class DimLayerController { state.dimLayer = newDimLayer; } + private static String getDimLayerTag(DimLayerUser dimLayerUser) { + return TAG_LOCAL + "/" + dimLayerUser.toShortString(); + } + private DimLayerState getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser) { if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser=" + dimLayerUser.toShortString()); diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index b6aa3f2ed4b0..9bceee72b8dc 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -16,6 +16,17 @@ package com.android.server.wm; +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.view.WindowManager.DOCKED_BOTTOM; +import static android.view.WindowManager.DOCKED_LEFT; +import static android.view.WindowManager.DOCKED_RIGHT; +import static android.view.WindowManager.DOCKED_TOP; +import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION; +import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + import android.content.Context; import android.graphics.Rect; import android.os.RemoteCallbackList; @@ -30,19 +41,6 @@ import android.view.animation.Interpolator; import com.android.server.wm.DimLayer.DimLayerUser; -import java.util.ArrayList; - -import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; -import static android.view.WindowManager.DOCKED_BOTTOM; -import static android.view.WindowManager.DOCKED_LEFT; -import static android.view.WindowManager.DOCKED_RIGHT; -import static android.view.WindowManager.DOCKED_TOP; -import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION; -import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; - /** * Keeps information about the docked stack divider. */ @@ -106,7 +104,8 @@ public class DockedStackDividerController implements DimLayerUser { com.android.internal.R.dimen.docked_stack_divider_thickness); mDividerInsets = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.docked_stack_divider_insets); - mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId()); + mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId(), + "DockedStackDim"); mMinimizedDockInterpolator = AnimationUtils.loadInterpolator( context, android.R.interpolator.fast_out_slow_in); } @@ -247,8 +246,9 @@ public class DockedStackDividerController implements DimLayerUser { void setResizeDimLayer(boolean visible, int targetStackId, float alpha) { SurfaceControl.openTransaction(); - TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(targetStackId); - boolean visibleAndValid = visible && stack != null; + final TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(targetStackId); + final TaskStack dockedStack = mDisplayContent.getDockedStackLocked(); + boolean visibleAndValid = visible && stack != null && dockedStack != null; if (visibleAndValid) { stack.getDimBounds(mTmpRect); if (mTmpRect.height() > 0 && mTmpRect.width() > 0) { diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java index f7035c502cc2..92701dee699d 100644 --- a/services/core/java/com/android/server/wm/TaskPositioner.java +++ b/services/core/java/com/android/server/wm/TaskPositioner.java @@ -60,7 +60,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; class TaskPositioner implements DimLayer.DimLayerUser { - private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskPositioner" : TAG_WM; + private static final String TAG_LOCAL = "TaskPositioner"; + private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM; // The margin the pointer position has to be within the side of the screen to be // considered at the side of the screen. @@ -287,7 +288,7 @@ class TaskPositioner implements DimLayer.DimLayerUser { } mService.pauseRotationLocked(); - mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId()); + mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId(), TAG_LOCAL); mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics); mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics); mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics); diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 86327f71efa2..676b90fd4d38 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -585,7 +585,8 @@ public class TaskStack implements DimLayer.DimLayerUser, } mDisplayContent = displayContent; - mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId()); + mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId(), + "animation background stackId=" + mStackId); Rect bounds = null; final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID); diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 85bddee5e1dc..4698e4e173ae 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -771,6 +771,7 @@ public class WindowAnimator { } mService.destroyPreservedSurfaceLocked(); + mService.mWindowPlacerLocked.destroyPendingSurfaces(); if (DEBUG_WINDOW_TRACE) { Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java index ccba88ddeb17..0979cd32a1e2 100644 --- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java +++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java @@ -35,7 +35,7 @@ public class WindowManagerDebugConfig { static final boolean DEBUG_RESIZE = false; static final boolean DEBUG = false; - static final boolean DEBUG_ADD_REMOVE = true; + static final boolean DEBUG_ADD_REMOVE = false; static final boolean DEBUG_FOCUS = false; static final boolean DEBUG_FOCUS_LIGHT = DEBUG_FOCUS || false; static final boolean DEBUG_ANIM = false; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4e3dd9c0b7fe..a998bc3be63b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -24,6 +24,9 @@ import android.app.ActivityManagerInternal; import android.app.ActivityManagerNative; import android.app.AppOpsManager; import android.app.IActivityManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -118,6 +121,7 @@ import android.view.animation.Animation; import android.view.inputmethod.InputMethodManagerInternal; import android.widget.Toast; +import com.android.internal.R; import com.android.internal.app.IAssistScreenshotReceiver; import com.android.internal.os.IResultReceiver; import com.android.internal.policy.IShortcutService; @@ -7469,6 +7473,35 @@ public class WindowManagerService extends IWindowManager.Stub return mCurrentFocus; } + private void showAuditSafeModeNotification() { + PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, + new Intent(Intent.ACTION_VIEW, + Uri.parse("https://support.google.com/nexus/answer/2852139")), 0); + + String title = mContext.getString(R.string.audit_safemode_notification); + + Notification notification = new Notification.Builder(mContext) + .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning) + .setWhen(0) + .setOngoing(true) + .setTicker(title) + .setLocalOnly(true) + .setPriority(Notification.PRIORITY_HIGH) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setColor(mContext.getColor( + com.android.internal.R.color.system_notification_accent_color)) + .setContentTitle(title) + .setContentText(mContext.getString(R.string.audit_safemode_notification_details)) + .setContentIntent(pendingIntent) + .build(); + + NotificationManager notificationManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + + notificationManager.notifyAsUser(null, R.string.audit_safemode_notification, notification, + UserHandle.ALL); + } + public boolean detectSafeMode() { if (!mInputMonitor.waitForInputDevicesReady( INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS)) { @@ -7501,6 +7534,7 @@ public class WindowManagerService extends IWindowManager.Stub if (auditSafeMode >= buildDate) { mSafeMode = true; + showAuditSafeModeNotification(); } else { SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, ""); SystemProperties.set(ShutdownThread.AUDIT_SAFEMODE_PROPERTY, ""); diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index ba4e1f10887f..8ada2f1a1646 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -46,7 +46,6 @@ import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Debug; -import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; @@ -125,6 +124,8 @@ class WindowSurfacePlacer { } private final LayerAndToken mTmpLayerAndToken = new LayerAndToken(); + private final ArrayList<SurfaceControl> mPendingDestroyingSurfaces = new ArrayList<>(); + public WindowSurfacePlacer(WindowManagerService service) { mService = service; mWallpaperControllerLocked = mService.mWallpaperControllerLocked; @@ -542,6 +543,7 @@ class WindowSurfacePlacer { mService.enableScreenIfNeededLocked(); mService.scheduleAnimationLocked(); + mService.mWindowPlacerLocked.destroyPendingSurfaces(); if (DEBUG_WINDOW_TRACE) Slog.e(TAG, "performSurfacePlacementInner exit: animating=" + mService.mAnimator.isAnimating()); @@ -1625,6 +1627,25 @@ class WindowSurfacePlacer { } } + /** + * Puts the {@param surface} into a pending list to be destroyed after the current transaction + * has been committed. + */ + void destroyAfterTransaction(SurfaceControl surface) { + mPendingDestroyingSurfaces.add(surface); + } + + /** + * Destroys any surfaces that have been put into the pending list with + * {@link #destroyAfterTransaction}. + */ + void destroyPendingSurfaces() { + for (int i = mPendingDestroyingSurfaces.size() - 1; i >= 0; i--) { + mPendingDestroyingSurfaces.get(i).destroy(); + } + mPendingDestroyingSurfaces.clear(); + } + public void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mTraversalScheduled="); pw.println(mTraversalScheduled); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index d97967515caf..6ab4804c3a80 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -33,6 +33,7 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.accounts.AccountManager; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerNative; @@ -3642,7 +3643,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (count == 0 || count > admin.maximumFailedPasswordsForWipe || (count == admin.maximumFailedPasswordsForWipe && - mUserManager.getUserInfo(userId).isPrimary())) { + getUserInfo(userId).isPrimary())) { count = admin.maximumFailedPasswordsForWipe; strictestAdmin = admin; } @@ -3650,6 +3651,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return strictestAdmin; } + private UserInfo getUserInfo(@UserIdInt int userId) { + final long token = mInjector.binderClearCallingIdentity(); + try { + return mUserManager.getUserInfo(userId); + } finally { + mInjector.binderRestoreCallingIdentity(token); + } + } + @Override public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException { if (!mHasFeature) { @@ -5913,7 +5923,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * - adb if there are not accounts. */ private void enforceCanSetProfileOwnerLocked(int userHandle) { - UserInfo info = mUserManager.getUserInfo(userHandle); + UserInfo info = getUserInfo(userHandle); if (info == null) { // User doesn't exist. throw new IllegalArgumentException( @@ -6065,12 +6075,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private boolean isManagedProfile(int userHandle) { - long ident = mInjector.binderClearCallingIdentity(); - try { - return mUserManager.getUserInfo(userHandle).isManagedProfile(); - } finally { - mInjector.binderRestoreCallingIdentity(ident); - } + return getUserInfo(userHandle).isManagedProfile(); } private void enableIfNecessary(String packageName, int userId) { @@ -6409,7 +6414,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { try { // If we have an enabled packages list for a managed profile the packages // we should check are installed for the parent user. - UserInfo user = mUserManager.getUserInfo(userIdToCheck); + UserInfo user = getUserInfo(userIdToCheck); if (user.isManagedProfile()) { userIdToCheck = user.profileGroupId; } @@ -6455,7 +6460,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { List<AccessibilityServiceInfo> enabledServices = null; long id = mInjector.binderClearCallingIdentity(); try { - UserInfo user = mUserManager.getUserInfo(userId); + UserInfo user = getUserInfo(userId); if (user.isManagedProfile()) { userId = user.profileGroupId; } @@ -6537,7 +6542,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (result != null) { long id = mInjector.binderClearCallingIdentity(); try { - UserInfo user = mUserManager.getUserInfo(userId); + UserInfo user = getUserInfo(userId); if (user.isManagedProfile()) { userId = user.profileGroupId; } @@ -6591,7 +6596,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long token = mInjector.binderClearCallingIdentity(); try { UserInfo currentUser; - UserInfo callingUser = mUserManager.getUserInfo(callingUserId); + UserInfo callingUser = getUserInfo(callingUserId); try { currentUser = mInjector.getIActivityManager().getCurrentUser(); } catch (RemoteException e) { @@ -8223,13 +8228,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); } final int callingUserId = mInjector.userHandleGetCallingUserId(); - final UserInfo user; - long ident = mInjector.binderClearCallingIdentity(); - try { - user = mUserManager.getUserInfo(callingUserId); - } finally { - mInjector.binderRestoreCallingIdentity(ident); - } + final UserInfo user = getUserInfo(callingUserId); return user != null && user.isManagedProfile(); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 68fd0f681aa5..b316cbd96453 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -494,7 +494,6 @@ class Owners { abstract void writeInner(XmlSerializer out) throws IOException; abstract boolean readInner(XmlPullParser parser, int depth, String tag); - } private class DeviceOwnerReadWriter extends FileReadWriter { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index a2d5259b2758..59f8284a5200 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -83,6 +83,7 @@ import com.android.server.pm.Installer; import com.android.server.pm.LauncherAppsService; import com.android.server.pm.OtaDexoptService; import com.android.server.pm.PackageManagerService; +import com.android.server.pm.ShortcutService; import com.android.server.pm.UserManagerService; import com.android.server.power.PowerManagerService; import com.android.server.power.ShutdownThread; @@ -725,6 +726,9 @@ public final class SystemServer { // Always start the Device Policy Manager, so that the API is compatible with // API8. mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class); + +// TODO is this a good place? + mSystemServiceManager.startService(ShortcutService.Lifecycle.class); } if (!disableSystemUI) { diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java index a9ab1d74b614..8cfc4a3ee8e0 100644 --- a/services/print/java/com/android/server/print/PrintManagerService.java +++ b/services/print/java/com/android/server/print/PrintManagerService.java @@ -612,43 +612,79 @@ public final class PrintManagerService extends SystemService { private void registerBroadcastReceivers() { PackageMonitor monitor = new PackageMonitor() { - private void updateServices(String packageName) { - if (!mUserManager.isUserUnlocked(getChangingUserId())) return; - synchronized (mLock) { - // A background user/profile's print jobs are running but there is - // no UI shown. Hence, if the packages of such a user change we need - // to handle it as the change may affect ongoing print jobs. - boolean servicesChanged = false; - UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + /** + * Checks if the package contains a print service. + * + * @param packageName The name of the package + * + * @return true iff the package contains a print service + */ + private boolean hasPrintService(String packageName) { + Intent intent = new Intent(android.printservice.PrintService.SERVICE_INTERFACE); + intent.setPackage(packageName); - List<PrintServiceInfo> installedServices = userState - .getPrintServices(PrintManager.ALL_SERVICES); - final int numInstalledServices = installedServices.size(); - for (int i = 0; i < numInstalledServices; i++) { - if (installedServices.get(i).getResolveInfo().serviceInfo.packageName - .equals(packageName)) { - servicesChanged = true; - break; - } - } - if (servicesChanged) { - userState.updateIfNeededLocked(); + List<ResolveInfo> installedServices = mContext.getPackageManager() + .queryIntentServicesAsUser(intent, + GET_SERVICES | MATCH_DEBUG_TRIAGED_MISSING, + getChangingUserId()); + + return installedServices != null && !installedServices.isEmpty(); + } + + /** + * Checks if there is a print service currently registered for this package. + * + * @param userState The userstate for the current user + * @param packageName The name of the package + * + * @return true iff the package contained (and might still contain) a print service + */ + private boolean hadPrintService(@NonNull UserState userState, String packageName) { + List<PrintServiceInfo> installedServices = userState + .getPrintServices(PrintManager.ALL_SERVICES); + + if (installedServices == null) { + return false; + } + + final int numInstalledServices = installedServices.size(); + for (int i = 0; i < numInstalledServices; i++) { + if (installedServices.get(i).getResolveInfo().serviceInfo.packageName + .equals(packageName)) { + return true; } } + + return false; } @Override public void onPackageModified(String packageName) { if (!mUserManager.isUserUnlocked(getChangingUserId())) return; - updateServices(packageName); - getOrCreateUserStateLocked(getChangingUserId()).prunePrintServices(); + UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + + synchronized (mLock) { + if (hadPrintService(userState, packageName) + || hasPrintService(packageName)) { + userState.updateIfNeededLocked(); + } + } + + userState.prunePrintServices(); } @Override public void onPackageRemoved(String packageName, int uid) { if (!mUserManager.isUserUnlocked(getChangingUserId())) return; - updateServices(packageName); - getOrCreateUserStateLocked(getChangingUserId()).prunePrintServices(); + UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + + synchronized (mLock) { + if (hadPrintService(userState, packageName)) { + userState.updateIfNeededLocked(); + } + } + + userState.prunePrintServices(); } @Override @@ -692,21 +728,11 @@ public final class PrintManagerService extends SystemService { @Override public void onPackageAdded(String packageName, int uid) { if (!mUserManager.isUserUnlocked(getChangingUserId())) return; - - // A background user/profile's print jobs are running but there is - // no UI shown. Hence, if the packages of such a user change we need - // to handle it as the change may affect ongoing print jobs. - Intent intent = new Intent(android.printservice.PrintService.SERVICE_INTERFACE); - intent.setPackage(packageName); - - List<ResolveInfo> installedServices = mContext.getPackageManager() - .queryIntentServicesAsUser(intent, - GET_SERVICES | MATCH_DEBUG_TRIAGED_MISSING, - getChangingUserId()); - - if (installedServices != null) { - UserState userState = getOrCreateUserStateLocked(getChangingUserId()); - userState.updateIfNeededLocked(); + synchronized (mLock) { + if (hasPrintService(packageName)) { + UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + userState.updateIfNeededLocked(); + } } } }; diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 3ae10729f741..23f186c42ce3 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -106,6 +106,10 @@ <service android:name="com.android.server.job.MockPriorityJobService" android:permission="android.permission.BIND_JOB_SERVICE" /> + + <activity android:name="com.android.server.pm.ShortcutManagerTest$ShortcutActivity" /> + <activity android:name="com.android.server.pm.ShortcutManagerTest$ShortcutActivity2" /> + <activity android:name="com.android.server.pm.ShortcutManagerTest$ShortcutActivity3" /> </application> <instrumentation diff --git a/services/tests/servicestests/res/drawable/icon1.png b/services/tests/servicestests/res/drawable/icon1.png Binary files differnew file mode 100644 index 000000000000..64eb294363d2 --- /dev/null +++ b/services/tests/servicestests/res/drawable/icon1.png diff --git a/services/tests/servicestests/res/drawable/icon2.png b/services/tests/servicestests/res/drawable/icon2.png Binary files differnew file mode 100644 index 000000000000..75024841d327 --- /dev/null +++ b/services/tests/servicestests/res/drawable/icon2.png diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java new file mode 100644 index 000000000000..eb16a1db5876 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java @@ -0,0 +1,44 @@ + +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.pm; + +import android.content.pm.ShortcutInfo; +import android.test.AndroidTestCase; + +import com.android.server.testutis.TestUtils; + +/** + * Tests for {@link ShortcutInfo}. + + m FrameworksServicesTests && + adb install \ + -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk && + adb shell am instrument -e class com.android.server.pm.ShortcutInfoTest \ + -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner + + */ +public class ShortcutInfoTest extends AndroidTestCase { + + public void testNoId() { + TestUtils.assertExpectException( + IllegalArgumentException.class, + "ID must be provided", + () -> new ShortcutInfo.Builder(mContext).build()); + } + + // TODO Add more tests. +} diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java new file mode 100644 index 000000000000..a9b6684a874c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java @@ -0,0 +1,1206 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.pm; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.LauncherApps.ShortcutQuery; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.content.pm.ShortcutServiceInternal; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.os.FileUtils; +import android.os.UserHandle; +import android.test.AndroidTestCase; +import android.test.mock.MockContext; +import android.util.Log; + +import com.android.frameworks.servicestests.R; +import com.android.internal.util.Preconditions; +import com.android.server.LocalServices; +import com.android.server.SystemService; + +import org.junit.Assert; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileReader; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +/** + * Tests for ShortcutService and ShortcutManager. + * + m FrameworksServicesTests && + adb install \ + -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk && + adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest \ + -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner + */ +public class ShortcutManagerTest extends AndroidTestCase { + private static final String TAG = "ShortcutManagerTest"; + + /** + * Whether to enable dump or not. Should be only true when debugging to avoid bugs where + * dump affecting the behavior. + */ + private static final boolean ENABLE_DUMP = true; // DO NOT SUBMIT WITH true + + /** Context used in the client side */ + private final class ClientContext extends MockContext { + @Override + public String getPackageName() { + return mInjectedClientPackage; + } + } + + /** Context used in the service side */ + private final class ServiceContext extends MockContext { + } + + /** ShortcutService with injection override methods. */ + private final class ShortcutServiceTestable extends ShortcutService { + public ShortcutServiceTestable(Context context) { + super(context); + + } + + @Override + void injectLoadConfigurationLocked() { + setResetIntervalForTest(INTERVAL); + setMaxDynamicShortcutsForTest(MAX_SHORTCUTS); + setMaxDailyUpdatesForTest(MAX_DAILY_UPDATES); + } + + @Override + long injectCurrentTimeMillis() { + return mInjectedCurrentTimeLillis; + } + + @Override + int injectBinderCallingUid() { + return mInjectedCallingUid; + } + + @Override + int injectGetPackageUid(String packageName) { + Integer uid = mInjectedPackageUidMap.get(packageName); + return uid != null ? uid : -1; + } + + @Override + File injectSystemDataPath() { + return new File(mInjectedFilePathRoot, "system"); + } + + @Override + File injectUserDataPath(@UserIdInt int userId) { + return new File(mInjectedFilePathRoot, "user-" + userId); + } + } + + /** ShortcutManager with injection override methods. */ + private final class ShortcutManagerTestable extends ShortcutManager { + public ShortcutManagerTestable(Context context, ShortcutServiceTestable service) { + super(context, service); + } + + @Override + protected int injectMyUserId() { + return UserHandle.getUserId(mInjectedCallingUid); + } + } + + + public static class ShortcutActivity extends Activity { + } + + public static class ShortcutActivity2 extends Activity { + } + + public static class ShortcutActivity3 extends Activity { + } + + private ServiceContext mServiceContext; + private ClientContext mClientContext; + + private ShortcutServiceTestable mService; + private ShortcutManagerTestable mManager; + private ShortcutServiceInternal mInternal; + + private File mInjectedFilePathRoot; + + private long mInjectedCurrentTimeLillis; + + private int mInjectedCallingUid; + private String mInjectedClientPackage; + + private Map<String, Integer> mInjectedPackageUidMap; + + private static final String CALLING_PACKAGE_1 = "com.android.test.1"; + private static final int CALLING_UID_1 = 10001; + + private static final String CALLING_PACKAGE_2 = "com.android.test.2"; + private static final int CALLING_UID_2 = 10002; + + private static final String CALLING_PACKAGE_3 = "com.android.test.3"; + private static final int CALLING_UID_3 = 10003; + + private static final String LAUNCHER_1 = "com.android.launcher.1"; + private static final int LAUNCHER_UID_1 = 10011; + + private static final String LAUNCHER_2 = "com.android.launcher.2"; + private static final int LAUNCHER_UID_2 = 10012; + + private static final long START_TIME = 1234560000000L; + + private static final long INTERVAL = 10000; + + private static final int MAX_SHORTCUTS = 5; + + private static final int MAX_DAILY_UPDATES = 3; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mServiceContext = new ServiceContext(); + mClientContext = new ClientContext(); + + // Prepare injection values. + + mInjectedCurrentTimeLillis = START_TIME; + + mInjectedPackageUidMap = new HashMap<>(); + mInjectedPackageUidMap.put(CALLING_PACKAGE_1, CALLING_UID_1); + mInjectedPackageUidMap.put(CALLING_PACKAGE_2, CALLING_UID_2); + mInjectedPackageUidMap.put(CALLING_PACKAGE_3, CALLING_UID_3); + mInjectedPackageUidMap.put(LAUNCHER_1, LAUNCHER_UID_1); + mInjectedPackageUidMap.put(LAUNCHER_2, LAUNCHER_UID_2); + + mInjectedFilePathRoot = new File(getContext().getCacheDir(), "test-files"); + + // Empty the data directory. + if (mInjectedFilePathRoot.exists()) { + Assert.assertTrue("failed to delete dir", + FileUtils.deleteContents(mInjectedFilePathRoot)); + } + mInjectedFilePathRoot.mkdirs(); + + initService(); + setCaller(CALLING_PACKAGE_1); + } + + /** (Re-) init the manager and the service. */ + private void initService() { + LocalServices.removeServiceForTest(ShortcutServiceInternal.class); + + // Instantiate targets. + mService = new ShortcutServiceTestable(mServiceContext); + mManager = new ShortcutManagerTestable(mClientContext, mService); + + mInternal = LocalServices.getService(ShortcutServiceInternal.class); + + // Load the setting file. + mService.onBootPhase(SystemService.PHASE_LOCK_SETTINGS_READY); + } + + /** Replace the current calling package */ + private void setCaller(String packageName) { + mInjectedClientPackage = packageName; + mInjectedCallingUid = Preconditions.checkNotNull(mInjectedPackageUidMap.get(packageName)); + } + + private String getCallingPackage() { + return mInjectedClientPackage; + } + + private int getCallingUserId() { + return UserHandle.getUserId(mInjectedCallingUid); + } + + /** For debugging */ + private void dumpsysOnLogcat() { + if (!ENABLE_DUMP) return; + + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final PrintWriter pw = new PrintWriter(out); + mService.dumpInner(pw); + pw.close(); + + Log.e(TAG, "Dumping ShortcutService:"); + for (String line : out.toString().split("\n")) { + Log.e(TAG, line); + } + } + + /** + * For debugging, dump arbitrary file on logcat. + */ + private void dumpFileOnLogcat(String path) { + if (!ENABLE_DUMP) return; + + Log.i(TAG, "Dumping file: " + path); + final StringBuilder sb = new StringBuilder(); + try (BufferedReader br = new BufferedReader(new FileReader(path))) { + String line; + while ((line = br.readLine()) != null) { + Log.i(TAG, line); + } + } catch (Exception e) { + Log.e(TAG, "Couldn't read file", e); + fail("Exception " + e); + } + } + + /** + * For debugging, dump the main state file on logcat. + */ + private void dumpBaseStateFile() { + dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath() + + "/system/" + ShortcutService.FILENAME_BASE_STATE); + } + + /** + * For debugging, dump per-user state file on logcat. + */ + private void dumpUserFile(int userId) { + dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath() + + "/user-" + userId + + "/" + ShortcutService.FILENAME_USER_PACKAGES); + } + + private static Bundle makeBundle(Object... keysAndValues) { + Preconditions.checkState((keysAndValues.length % 2) == 0); + + if (keysAndValues.length == 0) { + return null; + } + final Bundle ret = new Bundle(); + + for (int i = keysAndValues.length - 2; i >= 0; i -= 2) { + final String key = keysAndValues[i].toString(); + final Object value = keysAndValues[i + 1]; + + if (value == null) { + ret.putString(key, null); + } else if (value instanceof Integer) { + ret.putInt(key, (Integer) value); + } else if (value instanceof String) { + ret.putString(key, (String) value); + } else if (value instanceof Bundle) { + ret.putBundle(key, (Bundle) value); + } else { + fail("Type not supported yet: " + value.getClass().getName()); + } + } + return ret; + } + + /** + * Make a shortcut with an ID. + */ + private ShortcutInfo makeShortcut(String id) { + return makeShortcut( + id, "Title-" + id, /* activity =*/ null, /* icon =*/ null, + makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0); + } + + /** + * Make a shortcut with an ID and timestamp. + */ + private ShortcutInfo makeShortcutWithTimestamp(String id, long timestamp) { + final ShortcutInfo s = makeShortcut( + id, "Title-" + id, /* activity =*/ null, /* icon =*/ null, + makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0); + s.setTimestamp(timestamp); + return s; + } + + /** + * Make multiple shortcuts with IDs. + */ + private List<ShortcutInfo> makeShortcuts(String... ids) { + final ArrayList<ShortcutInfo> ret = new ArrayList(); + for (String id : ids) { + ret.add(makeShortcut(id)); + } + return ret; + } + + /** + * Make a shortcut with details. + */ + private ShortcutInfo makeShortcut(String id, String title, ComponentName activity, + Icon icon, Intent intent, int weight) { + final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext) + .setId(id) + .setTitle(title) + .setWeight(weight) + .setIntent(intent); + if (icon != null) { + b.setIcon(icon); + } + if (activity != null) { + b.setActivityComponent(activity); + } + final ShortcutInfo s = b.build(); + + s.setTimestamp(mInjectedCurrentTimeLillis); // HACK + + return s; + } + + /** + * Make an intent. + */ + private Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) { + final Intent intent = new Intent(action); + intent.setComponent(makeComponent(clazz)); + intent.replaceExtras(makeBundle(bundleKeysAndValues)); + return intent; + } + + /** + * Make an component name, with the client context. + */ + @NonNull + private ComponentName makeComponent(Class<?> clazz) { + return new ComponentName(mClientContext, clazz); + } + + @NonNull + private ShortcutInfo findById(List<ShortcutInfo> list, String id) { + for (ShortcutInfo s : list) { + if (s.getId().equals(id)) { + return s; + } + } + fail("Shortcut with id " + id + " not found"); + return null; + } + + private void assertResetTimes(long expectedLastResetTime, long expectedNextResetTime) { + assertEquals(expectedLastResetTime, mService.getLastResetTimeLocked()); + assertEquals(expectedNextResetTime, mService.getNextResetTimeLocked()); + } + + @NonNull + private List<ShortcutInfo> assertShortcutIds(@NonNull List<ShortcutInfo> actualShortcuts, + String... expectedIds) { + final HashSet<String> expected = new HashSet<>(Arrays.asList(expectedIds)); + final HashSet<String> actual = new HashSet<>(); + for (ShortcutInfo s : actualShortcuts) { + actual.add(s.getId()); + } + + // Compare the sets. + assertEquals(expected, actual); + return actualShortcuts; + } + + @NonNull + private List<ShortcutInfo> assertAllHaveIntents( + @NonNull List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertNotNull("ID " + s.getId(), s.getIntent()); + } + return actualShortcuts; + } + + @NonNull + private List<ShortcutInfo> assertAllNotHaveIntents( + @NonNull List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertNull("ID " + s.getId(), s.getIntent()); + } + return actualShortcuts; + } + + @NonNull + private List<ShortcutInfo> assertAllHaveTitle( + @NonNull List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertNotNull("ID " + s.getId(), s.getTitle()); + } + return actualShortcuts; + } + + @NonNull + private List<ShortcutInfo> assertAllNotHaveTitle( + @NonNull List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertNull("ID " + s.getId(), s.getTitle()); + } + return actualShortcuts; + } + + @NonNull + private List<ShortcutInfo> assertAllHaveFlags(@NonNull List<ShortcutInfo> actualShortcuts, + int shortcutFlags) { + for (ShortcutInfo s : actualShortcuts) { + assertTrue("ID " + s.getId(), s.hasFlags(shortcutFlags)); + } + return actualShortcuts; + } + + @NonNull + private List<ShortcutInfo> assertAllDynamic(@NonNull List<ShortcutInfo> actualShortcuts) { + return assertAllHaveFlags(actualShortcuts, ShortcutInfo.FLAG_DYNAMIC); + } + + @NonNull + private List<ShortcutInfo> assertAllPinned(@NonNull List<ShortcutInfo> actualShortcuts) { + return assertAllHaveFlags(actualShortcuts, ShortcutInfo.FLAG_PINNED); + } + + @NonNull + private List<ShortcutInfo> assertAllDynamicOrPinned( + @NonNull List<ShortcutInfo> actualShortcuts) { + for (ShortcutInfo s : actualShortcuts) { + assertTrue("ID " + s.getId(), s.isDynamic() || s.isPinned()); + } + return actualShortcuts; + } + + /** + * Test for the first launch path, no settings file available. + */ + public void testFirstInitialize() { + assertResetTimes(START_TIME, START_TIME + INTERVAL); + } + + /** + * Test for {@link ShortcutService#updateTimes()} + */ + public void testUpdateAndGetNextResetTimeLocked() { + assertResetTimes(START_TIME, START_TIME + INTERVAL); + + // Advance clock. + mInjectedCurrentTimeLillis += 100; + + // Shouldn't have changed. + assertResetTimes(START_TIME, START_TIME + INTERVAL); + + // Advance clock, almost the reset time. + mInjectedCurrentTimeLillis = START_TIME + INTERVAL - 1; + + // Shouldn't have changed. + assertResetTimes(START_TIME, START_TIME + INTERVAL); + + // Advance clock. + mInjectedCurrentTimeLillis += 1; + + assertResetTimes(START_TIME + INTERVAL, START_TIME + 2 * INTERVAL); + + // Advance further; 4 days since start. + mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL + 50; + + assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL); + } + + /** + * Test for the restoration from saved file. + */ + public void testInitializeFromSavedFile() { + + mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL + 50; + assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL); + + mService.saveBaseStateLocked(); + + dumpBaseStateFile(); + + // Restore. + initService(); + + assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL); + } + + /** + * Test for the restoration from restored file. + */ + public void testLoadFromBrokenFile() { + // TODO Add various broken cases. + } + + // === Test for app side APIs === + + /** Test for {@link android.content.pm.ShortcutManager#getMaxDynamicShortcutCount()} */ + public void testGetMaxDynamicShortcutCount() { + assertEquals(MAX_SHORTCUTS, mManager.getMaxDynamicShortcutCount()); + } + + /** Test for {@link android.content.pm.ShortcutManager#getRemainingCallCount()} */ + public void testGetRemainingCallCount() { + assertEquals(MAX_DAILY_UPDATES, mManager.getRemainingCallCount()); + } + + /** Test for {@link android.content.pm.ShortcutManager#getRateLimitResetTime()} */ + public void testGetRateLimitResetTime() { + assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime()); + + mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL + 50; + + assertEquals(START_TIME + 5 * INTERVAL, mManager.getRateLimitResetTime()); + } + + public void testSetDynamicShortcuts() { + final Icon icon1 = Icon.createWithResource(mContext, R.drawable.icon1); + final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource( + mContext.getResources(), R.drawable.icon2)); + + final ShortcutInfo si1 = makeShortcut( + "shortcut1", + "Title 1", + makeComponent(ShortcutActivity.class), + icon1, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class, + "key1", "val1", "nest", makeBundle("key", 123)), + /* weight */ 10); + + final ShortcutInfo si2 = makeShortcut( + "shortcut2", + "Title 2", + /* activity */ null, + icon2, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class), + /* weight */ 12); + final ShortcutInfo si3 = makeShortcut("shortcut3"); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2))); + assertEquals(2, mManager.getDynamicShortcuts().size()); + assertEquals(2, mManager.getRemainingCallCount()); + + // TODO: Check fields + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(1, mManager.getDynamicShortcuts().size()); + assertEquals(1, mManager.getRemainingCallCount()); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList())); + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertEquals(0, mManager.getRemainingCallCount()); + + dumpsysOnLogcat(); + + mInjectedCurrentTimeLillis++; // Need to advance the clock for reset to work. + mService.resetThrottlingInner(); + + dumpsysOnLogcat(); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2, si3))); + assertEquals(2, mManager.getDynamicShortcuts().size()); + + // TODO Check max number + } + + public void testAddDynamicShortcuts() { + final ShortcutInfo si1 = makeShortcut("shortcut1"); + final ShortcutInfo si2 = makeShortcut("shortcut2"); + final ShortcutInfo si3 = makeShortcut("shortcut3"); + + assertEquals(3, mManager.getRemainingCallCount()); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(2, mManager.getRemainingCallCount()); + assertEquals(1, mManager.getDynamicShortcuts().size()); + + assertTrue(mManager.addDynamicShortcut(si2)); + assertEquals(1, mManager.getRemainingCallCount()); + assertEquals(2, mManager.getDynamicShortcuts().size()); + + // Add with the same ID + assertTrue(mManager.addDynamicShortcut(makeShortcut("shortcut1"))); + assertEquals(0, mManager.getRemainingCallCount()); + assertEquals(2, mManager.getDynamicShortcuts().size()); // Still 2 + + // TODO Check max number + + // TODO Check fields. + } + + public void testDeleteDynamicShortcut() { + final ShortcutInfo si1 = makeShortcut("shortcut1"); + final ShortcutInfo si2 = makeShortcut("shortcut2"); + final ShortcutInfo si3 = makeShortcut("shortcut3"); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2, si3))); + assertEquals(3, mManager.getDynamicShortcuts().size()); + + assertEquals(2, mManager.getRemainingCallCount()); + + mManager.deleteDynamicShortcut("shortcut1"); + assertEquals(2, mManager.getDynamicShortcuts().size()); + + mManager.deleteDynamicShortcut("shortcut1"); + assertEquals(2, mManager.getDynamicShortcuts().size()); + + mManager.deleteDynamicShortcut("shortcutXXX"); + assertEquals(2, mManager.getDynamicShortcuts().size()); + + mManager.deleteDynamicShortcut("shortcut2"); + assertEquals(1, mManager.getDynamicShortcuts().size()); + + mManager.deleteDynamicShortcut("shortcut3"); + assertEquals(0, mManager.getDynamicShortcuts().size()); + + // Still 2 calls left. + assertEquals(2, mManager.getRemainingCallCount()); + + // TODO Make sure pinned shortcuts won't be deleted. + } + + public void testDeleteAllDynamicShortcuts() { + final ShortcutInfo si1 = makeShortcut("shortcut1"); + final ShortcutInfo si2 = makeShortcut("shortcut2"); + final ShortcutInfo si3 = makeShortcut("shortcut3"); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2, si3))); + assertEquals(3, mManager.getDynamicShortcuts().size()); + + assertEquals(2, mManager.getRemainingCallCount()); + + mManager.deleteAllDynamicShortcuts(); + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertEquals(2, mManager.getRemainingCallCount()); + + // Note delete shouldn't affect throttling, so... + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertEquals(0, mManager.getDynamicShortcuts().size()); + assertEquals(0, mManager.getDynamicShortcuts().size()); + + // This should still work. + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2, si3))); + assertEquals(3, mManager.getDynamicShortcuts().size()); + + // Still 1 call left + assertEquals(1, mManager.getRemainingCallCount()); + + // TODO Make sure pinned shortcuts won't be deleted. + } + + public void testThrottling() { + final ShortcutInfo si1 = makeShortcut("shortcut1"); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(2, mManager.getRemainingCallCount()); + + mInjectedCurrentTimeLillis++; + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(1, mManager.getRemainingCallCount()); + + mInjectedCurrentTimeLillis++; + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(0, mManager.getRemainingCallCount()); + + // Reached the max + + mInjectedCurrentTimeLillis++; + assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(0, mManager.getRemainingCallCount()); + + // Still throttled + mInjectedCurrentTimeLillis = START_TIME + INTERVAL - 1; + assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(0, mManager.getRemainingCallCount()); + + // Now it should work. + mInjectedCurrentTimeLillis++; + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(2, mManager.getRemainingCallCount()); + + mInjectedCurrentTimeLillis++; + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(1, mManager.getRemainingCallCount()); + + mInjectedCurrentTimeLillis++; + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(0, mManager.getRemainingCallCount()); + + mInjectedCurrentTimeLillis++; + assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(0, mManager.getRemainingCallCount()); + + // 4 days later... + mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL; + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(2, mManager.getRemainingCallCount()); + + // Make sure getRemainingCallCount() itself gets reset withou calling setDynamicShortcuts(). + mInjectedCurrentTimeLillis = START_TIME + 8 * INTERVAL; + assertEquals(3, mManager.getRemainingCallCount()); + } + + public void testThrottling_perPackage() { + final ShortcutInfo si1 = makeShortcut("shortcut1"); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(2, mManager.getRemainingCallCount()); + + mInjectedCurrentTimeLillis++; + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(1, mManager.getRemainingCallCount()); + + mInjectedCurrentTimeLillis++; + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(0, mManager.getRemainingCallCount()); + + // Reached the max + + mInjectedCurrentTimeLillis++; + assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1))); + + // Try from a different caller. + mInjectedClientPackage = CALLING_PACKAGE_2; + mInjectedCallingUid = CALLING_UID_2; + + // Need to create a new one wit the updated package name. + final ShortcutInfo si2 = makeShortcut("shortcut1"); + + assertEquals(3, mManager.getRemainingCallCount()); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2))); + assertEquals(2, mManager.getRemainingCallCount()); + + mInjectedCurrentTimeLillis++; + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2))); + assertEquals(1, mManager.getRemainingCallCount()); + + // Back to the original caller, still throttled. + mInjectedClientPackage = CALLING_PACKAGE_1; + mInjectedCallingUid = CALLING_UID_1; + + mInjectedCurrentTimeLillis = START_TIME + INTERVAL - 1; + assertEquals(0, mManager.getRemainingCallCount()); + assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertEquals(0, mManager.getRemainingCallCount()); + + // Now it should work. + mInjectedCurrentTimeLillis++; + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + + mInjectedCurrentTimeLillis++; + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + + mInjectedCurrentTimeLillis++; + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + + mInjectedCurrentTimeLillis++; + assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1))); + + mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL; + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1))); + assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si1))); + + mInjectedClientPackage = CALLING_PACKAGE_2; + mInjectedCallingUid = CALLING_UID_2; + + assertEquals(3, mManager.getRemainingCallCount()); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2))); + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2))); + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si2))); + assertFalse(mManager.setDynamicShortcuts(Arrays.asList(si2))); + } + + // TODO: updateShortcuts() + // TODO: getPinnedShortcuts() + + // === Test for launcher side APIs === + + public void testGetShortcuts() { + + // Set up shortcuts. + + setCaller(CALLING_PACKAGE_1); + final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 5000); + final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 1000); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s1_1, s1_2))); + + setCaller(CALLING_PACKAGE_2); + final ShortcutInfo s2_2 = makeShortcutWithTimestamp("s2", 1500); + final ShortcutInfo s2_3 = makeShortcutWithTimestamp("s3", 3000); + final ShortcutInfo s2_4 = makeShortcutWithTimestamp("s4", 500); + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s2_2, s2_3, s2_4))); + + setCaller(CALLING_PACKAGE_3); + final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s3", 5000); + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s3_2))); + + setCaller(LAUNCHER_1); + + // Get dynamic + assertAllDynamic(assertAllHaveTitle(assertAllNotHaveIntents(assertShortcutIds( + mInternal.getShortcuts(getCallingPackage(), /* time =*/ 0, CALLING_PACKAGE_1, + /* activity =*/ null, + ShortcutQuery.FLAG_GET_DYNAMIC, getCallingUserId()), + "s1", "s2")))); + + // Get pinned + assertShortcutIds( + mInternal.getShortcuts(getCallingPackage(), /* time =*/ 0, CALLING_PACKAGE_1, + /* activity =*/ null, + ShortcutQuery.FLAG_GET_PINNED, getCallingUserId()) + /* none */); + + // Get both, with timestamp + assertAllDynamic(assertAllHaveTitle(assertAllNotHaveIntents(assertShortcutIds( + mInternal.getShortcuts(getCallingPackage(), /* time =*/ 1000, CALLING_PACKAGE_2, + /* activity =*/ null, + ShortcutQuery.FLAG_GET_PINNED | ShortcutQuery.FLAG_GET_DYNAMIC, + getCallingUserId()), + "s2", "s3")))); + + // FLAG_GET_KEY_FIELDS_ONLY + assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds( + mInternal.getShortcuts(getCallingPackage(), /* time =*/ 1000, CALLING_PACKAGE_2, + /* activity =*/ null, + ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY, + getCallingUserId()), + "s2", "s3")))); + + // Pin some shortcuts. + mInternal.pinShortcuts(getCallingPackage(), CALLING_PACKAGE_2, + Arrays.asList("s3", "s4"), getCallingUserId()); + + // Pinned ones only + assertAllPinned(assertAllHaveTitle(assertAllNotHaveIntents(assertShortcutIds( + mInternal.getShortcuts(getCallingPackage(), /* time =*/ 1000, CALLING_PACKAGE_2, + /* activity =*/ null, + ShortcutQuery.FLAG_GET_PINNED, + getCallingUserId()), + "s3")))); + + // All packages. + assertShortcutIds( + mInternal.getShortcuts(getCallingPackage(), + /* time =*/ 5000, /* package= */ null, + /* activity =*/ null, + ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED, + getCallingUserId()), + "s1", "s3"); + + // TODO More tests: pinned but dynamic, filter by activity + } + + public void testGetShortcutInfo() { + // Create shortcuts. + setCaller(CALLING_PACKAGE_1); + final ShortcutInfo s1_1 = makeShortcut( + "s1", + "Title 1", + makeComponent(ShortcutActivity.class), + /* icon =*/ null, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class, + "key1", "val1", "nest", makeBundle("key", 123)), + /* weight */ 10); + + final ShortcutInfo s1_2 = makeShortcut( + "s2", + "Title 2", + /* activity */ null, + /* icon =*/ null, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class), + /* weight */ 12); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s1_1, s1_2))); + dumpsysOnLogcat(); + + setCaller(CALLING_PACKAGE_2); + final ShortcutInfo s2_1 = makeShortcut( + "s1", + "ABC", + makeComponent(ShortcutActivity2.class), + /* icon =*/ null, + makeIntent(Intent.ACTION_ANSWER, ShortcutActivity2.class, + "key1", "val1", "nest", makeBundle("key", 123)), + /* weight */ 10); + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s2_1))); + dumpsysOnLogcat(); + + // Pin some. + setCaller(LAUNCHER_1); + + mInternal.pinShortcuts(getCallingPackage(), CALLING_PACKAGE_1, + Arrays.asList("s2"), getCallingUserId()); + + dumpsysOnLogcat(); + + // Delete some. + setCaller(CALLING_PACKAGE_1); + assertShortcutIds(mManager.getPinnedShortcuts(), "s2"); + mManager.deleteDynamicShortcut("s2"); + assertShortcutIds(mManager.getPinnedShortcuts(), "s2"); + + dumpsysOnLogcat(); + + setCaller(LAUNCHER_1); + List<ShortcutInfo> list; + + // Note we don't guarantee the orders. + list = assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents( + mInternal.getShortcutInfo(getCallingPackage(), CALLING_PACKAGE_1, + Arrays.asList("s2", "s1", "s3", null), getCallingUserId()))), + "s1", "s2"); + assertEquals("Title 1", findById(list, "s1").getTitle()); + assertEquals("Title 2", findById(list, "s2").getTitle()); + + assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents( + mInternal.getShortcutInfo(getCallingPackage(), CALLING_PACKAGE_1, + Arrays.asList("s3"), getCallingUserId()))) + /* none */); + + list = assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents( + mInternal.getShortcutInfo(getCallingPackage(), CALLING_PACKAGE_2, + Arrays.asList("s1", "s2", "s3"), getCallingUserId()))), + "s1"); + assertEquals("ABC", findById(list, "s1").getTitle()); + } + + public void testPinShortcutAndGetPinnedShortcuts() { + // Create some shortcuts. + setCaller(CALLING_PACKAGE_1); + final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000); + final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s1_1, s1_2))); + + setCaller(CALLING_PACKAGE_2); + final ShortcutInfo s2_2 = makeShortcutWithTimestamp("s2", 1500); + final ShortcutInfo s2_3 = makeShortcutWithTimestamp("s3", 3000); + final ShortcutInfo s2_4 = makeShortcutWithTimestamp("s4", 500); + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s2_2, s2_3, s2_4))); + + setCaller(CALLING_PACKAGE_3); + final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s2", 1000); + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s2_2))); + + // Pin some. + setCaller(LAUNCHER_1); + + mInternal.pinShortcuts(getCallingPackage(), CALLING_PACKAGE_1, + Arrays.asList("s2", "s3"), getCallingUserId()); + + mInternal.pinShortcuts(getCallingPackage(), CALLING_PACKAGE_2, + Arrays.asList("s3", "s4", "s5"), getCallingUserId()); + + mInternal.pinShortcuts(getCallingPackage(), CALLING_PACKAGE_3, + Arrays.asList("s3"), getCallingUserId()); // Note ID doesn't exist + + // Delete some. + setCaller(CALLING_PACKAGE_1); + assertShortcutIds(mManager.getPinnedShortcuts(), "s2"); + mManager.deleteDynamicShortcut("s2"); + assertShortcutIds(mManager.getPinnedShortcuts(), "s2"); + + setCaller(CALLING_PACKAGE_2); + assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4"); + mManager.deleteDynamicShortcut("s3"); + assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4"); + + setCaller(CALLING_PACKAGE_3); + assertShortcutIds(mManager.getPinnedShortcuts() /* none */); + mManager.deleteDynamicShortcut("s2"); + assertShortcutIds(mManager.getPinnedShortcuts() /* none */); + + // Get pinned shortcuts from launcher + setCaller(LAUNCHER_1); + + // CALLING_PACKAGE_1 deleted s2, but it's pinned, so it still exists. + assertShortcutIds(assertAllPinned( + mInternal.getShortcuts(getCallingPackage(), /* time =*/ 0, CALLING_PACKAGE_1, + /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED, getCallingUserId())), + "s2"); + + assertShortcutIds(assertAllPinned( + mInternal.getShortcuts(getCallingPackage(), /* time =*/ 0, CALLING_PACKAGE_2, + /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED, getCallingUserId())), + "s3", "s4"); + + assertShortcutIds(assertAllPinned( + mInternal.getShortcuts(getCallingPackage(), /* time =*/ 0, CALLING_PACKAGE_3, + /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED, getCallingUserId())) + /* none */); + } + + public void testCreateShortcutIntent() { + // Create some shortcuts. + setCaller(CALLING_PACKAGE_1); + final ShortcutInfo s1_1 = makeShortcut( + "s1", + "Title 1", + makeComponent(ShortcutActivity.class), + /* icon =*/ null, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class, + "key1", "val1", "nest", makeBundle("key", 123)), + /* weight */ 10); + + final ShortcutInfo s1_2 = makeShortcut( + "s2", + "Title 2", + /* activity */ null, + /* icon =*/ null, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class), + /* weight */ 12); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s1_1, s1_2))); + + setCaller(CALLING_PACKAGE_2); + final ShortcutInfo s2_1 = makeShortcut( + "s1", + "ABC", + makeComponent(ShortcutActivity.class), + /* icon =*/ null, + makeIntent(Intent.ACTION_ANSWER, ShortcutActivity.class, + "key1", "val1", "nest", makeBundle("key", 123)), + /* weight */ 10); + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(s2_1))); + + // Pin all. + setCaller(LAUNCHER_1); + mInternal.pinShortcuts(getCallingPackage(), CALLING_PACKAGE_1, + Arrays.asList("s1", "s2"), getCallingUserId()); + + mInternal.pinShortcuts(getCallingPackage(), CALLING_PACKAGE_2, + Arrays.asList("s1"), getCallingUserId()); + + // Just to make it complicated, delete some. + setCaller(CALLING_PACKAGE_1); + mManager.deleteDynamicShortcut("s2"); + + // intent and check. + setCaller(LAUNCHER_1); + Intent intent; + intent = mInternal.createShortcutIntent(getCallingPackage(), + s1_1.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO), getCallingUserId()); + assertEquals(ShortcutActivity2.class.getName(), intent.getComponent().getClassName()); + + intent = mInternal.createShortcutIntent(getCallingPackage(), + s1_2.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO), getCallingUserId()); + assertEquals(ShortcutActivity3.class.getName(), intent.getComponent().getClassName()); + + intent = mInternal.createShortcutIntent(getCallingPackage(), + s2_1.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO), getCallingUserId()); + assertEquals(ShortcutActivity.class.getName(), intent.getComponent().getClassName()); + + // TODO Check extra, etc + } + + // === Test for persisting === + + public void testSaveAndLoadUser_empty() { + assertTrue(mManager.setDynamicShortcuts(Arrays.asList())); + + Log.i(TAG, "Saved state"); + dumpsysOnLogcat(); + dumpUserFile(0); + + // Restore. + initService(); + + assertEquals(0, mManager.getDynamicShortcuts().size()); + } + + /** + * Try save and load, also stop/start the user. + */ + public void testSaveAndLoadUser() { + // First, create some shortcuts and save. + final Icon icon1 = Icon.createWithResource(mContext, R.drawable.icon1); + final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource( + mContext.getResources(), R.drawable.icon2)); + + final ShortcutInfo si1 = makeShortcut( + "shortcut1", + "Title 1", + makeComponent(ShortcutActivity.class), + icon1, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class, + "key1", "val1", "nest", makeBundle("key", 123)), + /* weight */ 10); + + final ShortcutInfo si2 = makeShortcut( + "shortcut2", + "Title 2", + /* activity */ null, + icon2, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class), + /* weight */ 12); + + assertTrue(mManager.setDynamicShortcuts(Arrays.asList(si1, si2))); + + assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime()); + assertEquals(2, mManager.getRemainingCallCount()); + + Log.i(TAG, "Saved state"); + dumpsysOnLogcat(); + dumpUserFile(0); + + // Restore. + initService(); + + // Before the load, the map should be empty. + assertEquals(0, mService.getShortcutsForTest().size()); + + // this will pre-load the per-user info. + mService.onStartUserLocked(UserHandle.USER_SYSTEM); + + // Now it's loaded. + assertEquals(1, mService.getShortcutsForTest().size()); + + // Start another user + mService.onStartUserLocked(10); + + // Now the size is 2. + assertEquals(2, mService.getShortcutsForTest().size()); + + Log.i(TAG, "Dumping the new instance"); + + List<ShortcutInfo> loaded = mManager.getDynamicShortcuts(); + + Log.i(TAG, "Loaded state"); + dumpsysOnLogcat(); + + assertEquals(2, loaded.size()); + + assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime()); + assertEquals(2, mManager.getRemainingCallCount()); + + // Try stopping the user + mService.onCleanupUserInner(UserHandle.USER_SYSTEM); + + // Now it's unloaded. + assertEquals(1, mService.getShortcutsForTest().size()); + + // TODO Check all other fields + } +} diff --git a/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java b/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java new file mode 100644 index 000000000000..52e8f37f0b6c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.testutis; + +import android.test.MoreAsserts; + +import junit.framework.Assert; + +public class TestUtils { + private TestUtils() { + } + + public static void assertExpectException(Class<? extends Throwable> expectedExceptionType, + Runnable r) { + assertExpectException(expectedExceptionType, null, r); + } + + public static void assertExpectException(Class<? extends Throwable> expectedExceptionType, + String expectedExceptionMessageRegex, Runnable r) { + try { + r.run(); + Assert.fail("Expected exception type " + expectedExceptionType.getClass().getName() + + " was not thrown"); + } catch (Throwable e) { + Assert.assertTrue( + "Expected exception type was " + expectedExceptionType.getClass().getName() + + " but caught " + e.getClass().getName(), + expectedExceptionType.isAssignableFrom(e.getClass())); + if (expectedExceptionMessageRegex != null) { + MoreAsserts.assertContainsRegex(expectedExceptionMessageRegex, e.getMessage()); + } + } + } +} diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_linear_clamp.xml b/tests/VectorDrawableTest/res/color/fill_gradient_linear_clamp.xml new file mode 100644 index 000000000000..6a24453c0198 --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_linear_clamp.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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. + */ +--> +<gradient xmlns:android="http://schemas.android.com/apk/res/android" + android:angle="90" + android:startColor="?android:attr/colorPrimary" + android:endColor="?android:attr/colorControlActivated" + android:centerColor="#00ff0000" + android:startX="0" + android:startY="0" + android:endX="50" + android:endY="50" + android:type="linear" + android:tileMode="clamp"> +</gradient>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_overlap_mirror.xml b/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_overlap_mirror.xml new file mode 100644 index 000000000000..d342bca32208 --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_overlap_mirror.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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. + */ +--> +<gradient xmlns:android="http://schemas.android.com/apk/res/android" + android:angle="90" + android:startColor="?android:attr/colorPrimary" + android:endColor="?android:attr/colorControlActivated" + android:centerColor="#f00" + android:startX="0" + android:startY="0" + android:endX="50" + android:endY="50" + android:type="linear" + android:tileMode="mirror"> + <item android:offset="0.1" android:color="?android:attr/colorPrimary"/> + <item android:offset="0.4" android:color="#f00"/> + <item android:offset="0.4" android:color="#fff"/> + <item android:offset="0.9" android:color="?android:attr/colorControlActivated"/> +</gradient>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_repeat.xml b/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_repeat.xml new file mode 100644 index 000000000000..afb45aa2eebe --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_linear_item_repeat.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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. + */ +--> +<gradient xmlns:android="http://schemas.android.com/apk/res/android" + android:angle="90" + android:startColor="?android:attr/colorPrimary" + android:endColor="?android:attr/colorControlActivated" + android:centerColor="#f00" + android:startX="0" + android:startY="0" + android:endX="50" + android:endY="50" + android:type="linear" + android:tileMode="repeat"> + <item android:offset="0.1" android:color="?android:attr/colorPrimary"/> + <item android:offset="0.4" android:color="#fff"/> + <item android:offset="0.9" android:color="?android:attr/colorControlActivated"/> +</gradient>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_radial_clamp.xml b/tests/VectorDrawableTest/res/color/fill_gradient_radial_clamp.xml new file mode 100644 index 000000000000..64b32f6fba3f --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_radial_clamp.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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. + */ +--> +<gradient xmlns:android="http://schemas.android.com/apk/res/android" + android:centerColor="#ff0000" + android:endColor="?android:attr/colorControlActivated" + android:centerX="300" + android:centerY="300" + android:gradientRadius="50" + android:startColor="?android:attr/colorPrimary" + android:type="radial" + android:tileMode="clamp"> +</gradient>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_radial_item.xml b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item.xml index 51b0e173f746..c6cea7c5c698 100644 --- a/tests/VectorDrawableTest/res/color/fill_gradient_radial_item.xml +++ b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> - <!-- +<!-- /* * Copyright (C) 2016 The Android Open Source Project * diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_repeat.xml b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_repeat.xml new file mode 100644 index 000000000000..fb4346ad4abd --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_repeat.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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. + */ +--> +<gradient xmlns:android="http://schemas.android.com/apk/res/android" + android:centerColor="#ff0000" + android:endColor="#ff0000ff" + android:centerX="300" + android:centerY="300" + android:gradientRadius="50" + android:startColor="#ffffffff" + android:type="radial" + android:tileMode="repeat"> + <item android:offset="0.1" android:color="?android:attr/colorPrimary"/> + <item android:offset="0.4" android:color="#fff"/> + <item android:offset="0.9" android:color="?android:attr/colorControlActivated"/> +</gradient>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short.xml b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short.xml index 8caa1b4348a2..fefbe9f05eff 100644 --- a/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short.xml +++ b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> - <!-- +<!-- /* * Copyright (C) 2016 The Android Open Source Project * diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short_mirror.xml b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short_mirror.xml new file mode 100644 index 000000000000..8b5ad7c826ac --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_radial_item_short_mirror.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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. + */ +--> +<gradient xmlns:android="http://schemas.android.com/apk/res/android" + android:centerX="300" + android:centerY="300" + android:gradientRadius="50" + android:type="radial" + android:tileMode="mirror"> + <item android:offset="0.1" android:color="?android:attr/colorPrimary"/> + <item android:offset="0.9" android:color="?android:attr/colorControlActivated"/> +</gradient>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_sweep_clamp.xml b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_clamp.xml new file mode 100644 index 000000000000..80f39f3ee980 --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_clamp.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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. + */ +--> +<gradient xmlns:android="http://schemas.android.com/apk/res/android" + android:centerColor="#ff0000" + android:endColor="#ff0000ff" + android:centerX="500" + android:centerY="500" + android:gradientRadius="10" + android:startColor="#ffffffff" + android:type="sweep" + android:tileMode="clamp"> +</gradient> diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_long_mirror.xml b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_long_mirror.xml new file mode 100644 index 000000000000..0890bd6fc733 --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_long_mirror.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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. + */ +--> +<gradient xmlns:android="http://schemas.android.com/apk/res/android" + android:centerX="500" + android:centerY="500" + android:gradientRadius="10" + android:type="sweep" + android:tileMode="mirror"> + <item android:offset="-0.3" android:color="#f00"/> + <item android:offset="0.1" android:color="?android:attr/colorPrimary"/> + <item android:offset="0.4" android:color="#0f0"/> + <item android:offset="0.6" android:color="#00f"/> + <item android:offset="0.7" android:color="?android:attr/colorControlActivated"/> + <item android:offset="1.5" android:color="#00f"/> +</gradient> diff --git a/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_repeat.xml b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_repeat.xml new file mode 100644 index 000000000000..2ec50148b44d --- /dev/null +++ b/tests/VectorDrawableTest/res/color/fill_gradient_sweep_item_repeat.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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. + */ +--> +<gradient xmlns:android="http://schemas.android.com/apk/res/android" + android:centerColor="#ff0000" + android:endColor="#ff0000ff" + android:centerX="500" + android:centerY="500" + android:gradientRadius="10" + android:startColor="#ffffffff" + android:type="sweep" + android:tileMode="repeat"> + <item android:offset="0.1" android:color="?android:attr/colorPrimary"/> + <item android:offset="0.4" android:color="#fff"/> + <item android:offset="0.9" android:color="?android:attr/colorControlActivated"/> +</gradient> diff --git a/tests/VectorDrawableTest/res/color/stroke_gradient_clamp.xml b/tests/VectorDrawableTest/res/color/stroke_gradient_clamp.xml new file mode 100644 index 000000000000..3d746e720cf8 --- /dev/null +++ b/tests/VectorDrawableTest/res/color/stroke_gradient_clamp.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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. + */ +--> +<gradient xmlns:android="http://schemas.android.com/apk/res/android" + android:angle="90" + android:centerColor="#7f7f7f" + android:endColor="#ffffff" + android:startColor="#000000" + android:startX="0" + android:endX="50" + android:startY="0" + android:endY="0" + android:type="linear" + android:tileMode="clamp"> +</gradient>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/stroke_gradient_item_alpha_mirror.xml b/tests/VectorDrawableTest/res/color/stroke_gradient_item_alpha_mirror.xml new file mode 100644 index 000000000000..352a2fd463a8 --- /dev/null +++ b/tests/VectorDrawableTest/res/color/stroke_gradient_item_alpha_mirror.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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. + */ +--> +<gradient xmlns:android="http://schemas.android.com/apk/res/android" + android:startX="0" + android:endX="50" + android:startY="0" + android:endY="0" + android:type="linear" + android:tileMode="mirror"> + <item android:offset="0.1" android:color="#f00"/> + <item android:offset="0.2" android:color="#2f0f"/> + <item android:offset="0.9" android:color="#f00f"/> +</gradient>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/color/stroke_gradient_item_repeat.xml b/tests/VectorDrawableTest/res/color/stroke_gradient_item_repeat.xml new file mode 100644 index 000000000000..42281d15dc0b --- /dev/null +++ b/tests/VectorDrawableTest/res/color/stroke_gradient_item_repeat.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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. + */ +--> +<gradient xmlns:android="http://schemas.android.com/apk/res/android" + android:angle="90" + android:centerColor="#7f7f7f" + android:endColor="#ffffff" + android:startColor="#000000" + android:startX="0" + android:endX="50" + android:startY="0" + android:endY="0" + android:type="linear" + android:tileMode="repeat"> + <item android:offset="0.1" android:color="#f00"/> + <item android:offset="0.2" android:color="#f0f"/> + <item android:offset="0.9" android:color="#f00f"/> +</gradient>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_1_clamp.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_1_clamp.xml new file mode 100644 index 000000000000..2fa440a84cff --- /dev/null +++ b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_1_clamp.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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="64dp" + android:width="64dp" + android:viewportHeight="400" + android:viewportWidth="400" > + +<group android:name="backgroundGroup" + android:scaleX="0.5" + android:scaleY="0.5"> + <path + android:name="background1" + android:fillColor="@color/fill_gradient_linear_clamp" + android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" /> + <path + android:name="background2" + android:fillColor="@color/fill_gradient_radial_clamp" + android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" /> + <path + android:name="background3" + android:fillColor="@color/fill_gradient_sweep_clamp" + android:pathData="M 400,400 l 200,0 l 0, 200 l -200, 0 z" /> +</group> +<group + android:name="translateToCenterGroup" + android:translateX="50.0" + android:translateY="90.0" > + <path + android:name="twoLines" + android:pathData="@string/twoLinePathData" + android:strokeColor="@color/stroke_gradient_clamp" + android:strokeWidth="20" /> + + <group + android:name="rotationGroup" + android:pivotX="0.0" + android:pivotY="0.0" + android:rotation="-45.0"> + <path + android:name="twoLines1" + android:pathData="@string/twoLinePathData" + android:strokeColor="@color/stroke_gradient_clamp" + android:strokeWidth="20" /> + + <group + android:name="translateGroup" + android:translateX="130.0" + android:translateY="160.0"> + <group android:name="scaleGroup" > + <path + android:name="twoLines3" + android:pathData="@string/twoLinePathData" + android:strokeColor="@color/stroke_gradient_clamp" + android:strokeWidth="20" /> + </group> + </group> + + <group + android:name="translateGroupHalf" + android:translateX="65.0" + android:translateY="80.0"> + <group android:name="scaleGroup" > + <path + android:name="twoLines2" + android:pathData="@string/twoLinePathData" + android:fillColor="@color/fill_gradient_linear_clamp" + android:strokeColor="@color/stroke_gradient_clamp" + android:strokeWidth="20" /> + </group> + </group> + </group> +</group> + +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_2_repeat.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_2_repeat.xml new file mode 100644 index 000000000000..5a43f804a6e0 --- /dev/null +++ b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_2_repeat.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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="64dp" + android:width="64dp" + android:viewportHeight="400" + android:viewportWidth="400" > + +<group android:name="backgroundGroup" + android:scaleX="0.5" + android:scaleY="0.5"> + <path + android:name="background1" + android:fillColor="@color/fill_gradient_linear_item_repeat" + android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" /> + <path + android:name="background2" + android:fillColor="@color/fill_gradient_radial_item_repeat" + android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" /> + <path + android:name="background3" + android:fillColor="@color/fill_gradient_sweep_item_repeat" + android:pathData="M 400,400 l 200,0 l 0, 200 l -200, 0 z" /> +</group> +<group + android:name="translateToCenterGroup" + android:translateX="50.0" + android:translateY="90.0" > + <path + android:name="twoLines" + android:pathData="@string/twoLinePathData" + android:strokeColor="@color/stroke_gradient_item_repeat" + android:strokeWidth="20" /> + + <group + android:name="rotationGroup" + android:pivotX="0.0" + android:pivotY="0.0" + android:rotation="-45.0"> + <path + android:name="twoLines1" + android:pathData="@string/twoLinePathData" + android:strokeColor="@color/stroke_gradient_item_repeat" + android:strokeWidth="20" /> + + <group + android:name="translateGroup" + android:translateX="130.0" + android:translateY="160.0"> + <group android:name="scaleGroup" > + <path + android:name="twoLines3" + android:pathData="@string/twoLinePathData" + android:strokeColor="@color/stroke_gradient_item_repeat" + android:strokeWidth="20" /> + </group> + </group> + + <group + android:name="translateGroupHalf" + android:translateX="65.0" + android:translateY="80.0"> + <group android:name="scaleGroup" > + <path + android:name="twoLines2" + android:pathData="@string/twoLinePathData" + android:strokeColor="@color/stroke_gradient_item_repeat" + android:strokeWidth="20" /> + </group> + </group> + </group> +</group> + +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_3_mirror.xml b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_3_mirror.xml new file mode 100644 index 000000000000..e8de7c2b1f5d --- /dev/null +++ b/tests/VectorDrawableTest/res/drawable/vector_icon_gradient_3_mirror.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (C) 2016 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="64dp" + android:width="64dp" + android:viewportHeight="400" + android:viewportWidth="400" > + +<group android:name="backgroundGroup" + android:scaleX="0.5" + android:scaleY="0.5"> + <path + android:name="background1" + android:fillColor="@color/fill_gradient_linear_item_overlap_mirror" + android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" /> + <path + android:name="background2" + android:fillColor="@color/fill_gradient_radial_item_short_mirror" + android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" /> + <path + android:name="background3" + android:fillColor="@color/fill_gradient_sweep_item_long_mirror" + android:pathData="M 400,400 l 200,0 l 0, 200 l -200, 0 z" /> +</group> +<group + android:name="translateToCenterGroup" + android:translateX="50.0" + android:translateY="90.0" > + <path + android:name="twoLines" + android:pathData="@string/twoLinePathData" + android:strokeColor="@color/stroke_gradient_item_alpha_mirror" + android:strokeWidth="20" /> + + <group + android:name="rotationGroup" + android:pivotX="0.0" + android:pivotY="0.0" + android:rotation="-45.0"> + <path + android:name="twoLines1" + android:pathData="@string/twoLinePathData" + android:strokeColor="@color/stroke_gradient_item_alpha_mirror" + android:strokeWidth="20" /> + + <group + android:name="translateGroup" + android:translateX="130.0" + android:translateY="160.0"> + <group android:name="scaleGroup" > + <path + android:name="twoLines3" + android:pathData="@string/twoLinePathData" + android:strokeColor="@color/stroke_gradient_item_alpha_mirror" + android:strokeWidth="20" /> + </group> + </group> + + <group + android:name="translateGroupHalf" + android:translateX="65.0" + android:translateY="80.0"> + <group android:name="scaleGroup" > + <path + android:name="twoLines2" + android:pathData="@string/twoLinePathData" + android:strokeColor="@color/stroke_gradient_item_alpha" + android:strokeWidth="20" /> + </group> + </group> + </group> +</group> + +</vector>
\ No newline at end of file diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java index 717214740698..495d62010fb8 100644 --- a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java +++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java @@ -38,6 +38,9 @@ public class VectorDrawablePerformance extends Activity { R.drawable.vector_icon_gradient_1, R.drawable.vector_icon_gradient_2, R.drawable.vector_icon_gradient_3, + R.drawable.vector_icon_gradient_1_clamp, + R.drawable.vector_icon_gradient_2_repeat, + R.drawable.vector_icon_gradient_3_mirror, R.drawable.vector_icon_state_list_simple, R.drawable.vector_icon_state_list_theme, R.drawable.vector_drawable01, diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index d1fd56a50f1a..c7ae6fc473fd 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -168,8 +168,8 @@ public final class BridgeContext extends Context { RTL_ATTRS.put("?android:attr/paddingRight", "paddingEnd"); RTL_ATTRS.put("?android:attr/layout_marginLeft", "layout_marginStart"); RTL_ATTRS.put("?android:attr/layout_marginRight", "layout_marginEnd"); - RTL_ATTRS.put("?android:attr/layout_toLeft", "layout_toStartOf"); - RTL_ATTRS.put("?android:attr/layout_toRight", "layout_toEndOf"); + RTL_ATTRS.put("?android:attr/layout_toLeftOf", "layout_toStartOf"); + RTL_ATTRS.put("?android:attr/layout_toRightOf", "layout_toEndOf"); RTL_ATTRS.put("?android:attr/layout_alignParentLeft", "layout_alignParentStart"); RTL_ATTRS.put("?android:attr/layout_alignParentRight", "layout_alignParentEnd"); RTL_ATTRS.put("?android:attr/drawableLeft", "drawableStart"); diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java index a46aaec984ac..a9259fa2e30c 100644 --- a/wifi/java/android/net/wifi/ScanResult.java +++ b/wifi/java/android/net/wifi/ScanResult.java @@ -305,9 +305,12 @@ public class ScanResult implements Parcelable { */ public static class InformationElement { public static final int EID_SSID = 0; + public static final int EID_SUPPORTED_RATES = 1; public static final int EID_TIM = 5; public static final int EID_BSS_LOAD = 11; + public static final int EID_ERP = 42; public static final int EID_RSN = 48; + public static final int EID_EXTENDED_SUPPORTED_RATES = 50; public static final int EID_HT_OPERATION = 61; public static final int EID_INTERWORKING = 107; public static final int EID_ROAMING_CONSORTIUM = 111; |