Merge "TIF: Drop the requirement for ID tuple for non-broadcast channels" into nyc-dev
diff --git a/Android.mk b/Android.mk
index ffc0421..08f3468 100644
--- a/Android.mk
+++ b/Android.mk
@@ -149,6 +149,7 @@
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 \
@@ -652,6 +653,7 @@
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 3fdcf0b..87d9c14 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8140,6 +8140,7 @@
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";
@@ -9445,13 +9446,19 @@
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);
}
@@ -9464,6 +9471,18 @@
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 {
@@ -9963,6 +9982,56 @@
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);
@@ -19722,7 +19791,7 @@
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);
@@ -19868,7 +19937,7 @@
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 {
@@ -19942,7 +20011,7 @@
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();
@@ -19950,7 +20019,7 @@
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 {
@@ -23023,6 +23092,7 @@
method public boolean onTouchEvent(android.view.MotionEvent);
method public boolean onTrackballEvent(android.view.MotionEvent);
method public abstract boolean onTune(android.net.Uri);
+ method public boolean onTune(android.net.Uri, android.os.Bundle);
method public void onUnblockContent(android.media.tv.TvContentRating);
method public void setOverlayViewEnabled(boolean);
}
@@ -23096,12 +23166,15 @@
method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener);
method public void setStreamVolume(float);
method public void setTimeShiftPositionCallback(android.media.tv.TvView.TimeShiftPositionCallback);
+ method public void setZOrderMediaOverlay(boolean);
+ method public void setZOrderOnTop(boolean);
method public void timeShiftPause();
method public void timeShiftPlay(java.lang.String, android.net.Uri);
method public void timeShiftResume();
method public void timeShiftSeekTo(long);
method public void timeShiftSetPlaybackParams(android.media.PlaybackParams);
method public void tune(java.lang.String, android.net.Uri);
+ method public void tune(java.lang.String, android.net.Uri, android.os.Bundle);
}
public static abstract interface TvView.OnUnhandledInputEventListener {
@@ -29007,6 +29080,7 @@
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);
@@ -34426,8 +34500,9 @@
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);
@@ -34458,6 +34533,7 @@
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";
@@ -57404,7 +57480,23 @@
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 115224c..2f55373 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -43,6 +43,14 @@
}
+package android.media.tv {
+
+ public class TvView extends android.view.ViewGroup {
+ method public void requestUnblockContent(android.media.tv.TvContentRating);
+ }
+
+}
+
package android.net {
public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
@@ -201,14 +209,6 @@
}
-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 59d53e0..545a3e0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8446,6 +8446,7 @@
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";
@@ -9779,13 +9780,19 @@
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);
}
@@ -9798,6 +9805,18 @@
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 {
@@ -10357,6 +10376,56 @@
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);
@@ -21203,7 +21272,7 @@
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);
@@ -21357,7 +21426,7 @@
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 {
@@ -21434,7 +21503,7 @@
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();
@@ -21442,7 +21511,7 @@
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 {
@@ -24872,7 +24941,6 @@
method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int);
method protected void onLayout(boolean, int, int, int, int);
method public boolean onUnhandledInputEvent(android.view.InputEvent);
- method public deprecated void requestUnblockContent(android.media.tv.TvContentRating);
method public void reset();
method public void selectTrack(int, java.lang.String);
method public void sendAppPrivateCommand(java.lang.String, android.os.Bundle);
@@ -31292,6 +31360,7 @@
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);
@@ -36916,8 +36985,9 @@
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);
@@ -36958,37 +37028,6 @@
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();
@@ -37058,6 +37097,37 @@
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);
@@ -60508,7 +60578,23 @@
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/system-removed.txt b/api/system-removed.txt
index 9ebc484..79f7297 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -41,6 +41,14 @@
}
+package android.media.tv {
+
+ public class TvView extends android.view.ViewGroup {
+ method public void requestUnblockContent(android.media.tv.TvContentRating);
+ }
+
+}
+
package android.net {
public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
diff --git a/api/test-current.txt b/api/test-current.txt
index f298dc4..35b63a2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8146,6 +8146,7 @@
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";
@@ -9454,13 +9455,19 @@
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);
}
@@ -9473,6 +9480,18 @@
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 {
@@ -9973,6 +9992,56 @@
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);
@@ -19733,7 +19802,7 @@
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);
@@ -19879,7 +19948,7 @@
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 {
@@ -19953,7 +20022,7 @@
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();
@@ -19961,7 +20030,7 @@
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 {
@@ -23034,6 +23103,7 @@
method public boolean onTouchEvent(android.view.MotionEvent);
method public boolean onTrackballEvent(android.view.MotionEvent);
method public abstract boolean onTune(android.net.Uri);
+ method public boolean onTune(android.net.Uri, android.os.Bundle);
method public void onUnblockContent(android.media.tv.TvContentRating);
method public void setOverlayViewEnabled(boolean);
}
@@ -23107,12 +23177,15 @@
method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener);
method public void setStreamVolume(float);
method public void setTimeShiftPositionCallback(android.media.tv.TvView.TimeShiftPositionCallback);
+ method public void setZOrderMediaOverlay(boolean);
+ method public void setZOrderOnTop(boolean);
method public void timeShiftPause();
method public void timeShiftPlay(java.lang.String, android.net.Uri);
method public void timeShiftResume();
method public void timeShiftSeekTo(long);
method public void timeShiftSetPlaybackParams(android.media.PlaybackParams);
method public void tune(java.lang.String, android.net.Uri);
+ method public void tune(java.lang.String, android.net.Uri, android.os.Bundle);
}
public static abstract interface TvView.OnUnhandledInputEventListener {
@@ -29018,6 +29091,7 @@
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);
@@ -34443,8 +34517,9 @@
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);
@@ -34475,6 +34550,7 @@
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";
@@ -57423,7 +57499,23 @@
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 115224c..2f55373 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -43,6 +43,14 @@
}
+package android.media.tv {
+
+ public class TvView extends android.view.ViewGroup {
+ method public void requestUnblockContent(android.media.tv.TvContentRating);
+ }
+
+}
+
package android.net {
public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory {
@@ -201,14 +209,6 @@
}
-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/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f64bf1d..2f6907e 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1575,6 +1575,7 @@
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 @@
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>() {
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 1f1f318..cd4ace6 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -42,7 +42,7 @@
* @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/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 5697924..2c270fc 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -61,7 +61,7 @@
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 @@
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 d8f0ac5..a2cc6b1 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -47,6 +47,7 @@
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
+import android.text.style.CharacterStyle;
import android.text.style.RelativeSizeSpan;
import android.text.style.TextAppearanceSpan;
import android.util.Log;
@@ -1664,17 +1665,22 @@
SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
for (Object span : spans) {
Object resultSpan = span;
- if (span instanceof TextAppearanceSpan) {
- TextAppearanceSpan originalSpan = (TextAppearanceSpan) span;
+ if (resultSpan instanceof CharacterStyle) {
+ resultSpan = ((CharacterStyle) span).getUnderlying();
+ }
+ if (resultSpan instanceof TextAppearanceSpan) {
+ TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
resultSpan = new TextAppearanceSpan(
originalSpan.getFamily(),
originalSpan.getTextStyle(),
-1,
originalSpan.getTextColor(),
originalSpan.getLinkTextColor());
- } else if (span instanceof RelativeSizeSpan
- || span instanceof AbsoluteSizeSpan) {
+ } else if (resultSpan instanceof RelativeSizeSpan
+ || resultSpan instanceof AbsoluteSizeSpan) {
continue;
+ } else {
+ resultSpan = span;
}
builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
ss.getSpanFlags(span));
@@ -3591,37 +3597,53 @@
}
/**
+ * Removes RemoteViews that were created for compatibility from {@param n}, if they did not
+ * change.
+ *
+ * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}.
+ *
* @hide
*/
- public static void stripForDelivery(Notification n) {
+ public static Notification maybeCloneStrippedForDelivery(Notification n) {
String templateClass = n.extras.getString(EXTRA_TEMPLATE);
- if (TextUtils.isEmpty(templateClass)) {
- return;
- }
+
// Only strip views for known Styles because we won't know how to
// re-create them otherwise.
- if (getNotificationStyleClass(templateClass) == null) {
- return;
+ if (!TextUtils.isEmpty(templateClass)
+ && getNotificationStyleClass(templateClass) == null) {
+ return n;
}
- // Get rid of unmodified BuilderRemoteViews.
- if (n.contentView instanceof BuilderRemoteViews &&
+
+ // Only strip unmodified BuilderRemoteViews.
+ boolean stripContentView = n.contentView instanceof BuilderRemoteViews &&
n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
- n.contentView.getSequenceNumber()) {
- n.contentView = null;
- n.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
- }
- if (n.bigContentView instanceof BuilderRemoteViews &&
+ n.contentView.getSequenceNumber();
+ boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews &&
n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
- n.bigContentView.getSequenceNumber()) {
- n.bigContentView = null;
- n.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
- }
- if (n.headsUpContentView instanceof BuilderRemoteViews &&
+ n.bigContentView.getSequenceNumber();
+ boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews &&
n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
- n.headsUpContentView.getSequenceNumber()) {
- n.headsUpContentView = null;
- n.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
+ n.headsUpContentView.getSequenceNumber();
+
+ // Nothing to do here, no need to clone.
+ if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) {
+ return n;
}
+
+ Notification clone = n.clone();
+ if (stripContentView) {
+ clone.contentView = null;
+ clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
+ }
+ if (stripBigContentView) {
+ clone.bigContentView = null;
+ clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
+ }
+ if (stripHeadsUpContentView) {
+ clone.headsUpContentView = null;
+ clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
+ }
+ return clone;
}
private int getBaseLayoutResource() {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 16aee78..7cfa0cd 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -259,8 +259,7 @@
}
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
- final Notification copy = notification.clone();
- Builder.stripForDelivery(copy);
+ final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
copy, idOut, user.getIdentifier());
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index b1c5fd8..3a5dd30 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -37,7 +37,9 @@
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 @@
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 b935b25..f96ddf0 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2680,6 +2680,7 @@
RADIO_SERVICE,
HARDWARE_PROPERTIES_SERVICE,
//@hide: SOUND_TRIGGER_SERVICE,
+ SHORTCUT_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServiceName {}
@@ -3576,6 +3577,14 @@
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 cc266c5..da3c873 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.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 @@
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 1303696..e6525af 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 @@
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 b4e9f60..c684447 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -120,6 +120,8 @@
int checkUidSignatures(int uid1, int uid2);
+ List<String> getAllPackages();
+
String[] getPackagesForUid(int uid);
String getNameForUid(int uid);
@@ -380,6 +382,13 @@
*/
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 0000000..23e671d
--- /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 e443d50..f05ecd3 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 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 @@
}
}
+ /**
+ * 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 @@
}
}
}
+
+ @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 @@
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 @@
String packageName;
boolean replacing;
UserHandle user;
+ List<ShortcutInfo> shortcuts;
}
public CallbackMessageHandler(Looper looper, LauncherApps.Callback callback) {
@@ -527,6 +753,9 @@
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 @@
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/core/java/android/content/pm/ShortcutInfo.aidl b/core/java/android/content/pm/ShortcutInfo.aidl
new file mode 100644
index 0000000..08e1873
--- /dev/null
+++ b/core/java/android/content/pm/ShortcutInfo.aidl
@@ -0,0 +1,18 @@
+/*
+ * 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;
+
+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 0000000..6520563
--- /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 0000000..4c51d49
--- /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 0000000..8055dd9
--- /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 98ef2ea..cc46cbd 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.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 @@
// 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 @@
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 @@
mEndColor = copy.mEndColor;
mHasCenterColor = copy.mHasCenterColor;
mGradientRadius = copy.mGradientRadius;
+ mTileMode = copy.mTileMode;
if (copy.mItemColors != null) {
mItemColors = copy.mItemColors.clone();
@@ -117,6 +157,20 @@
}
}
+ // 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 @@
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 @@
}
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 @@
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 ee7bd9a..5c71373 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -235,6 +235,12 @@
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 ea180b2..f36bb29 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -24,9 +24,6 @@
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 @@
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 @@
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/preference/EditTextPreference.java b/core/java/android/preference/EditTextPreference.java
index ff37042..9467c22 100644
--- a/core/java/android/preference/EditTextPreference.java
+++ b/core/java/android/preference/EditTextPreference.java
@@ -49,6 +49,7 @@
private EditText mEditText;
private String mText;
+ private boolean mTextSet;
public EditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
@@ -85,15 +86,16 @@
* @param text The text to save
*/
public void setText(String text) {
- final boolean wasBlocking = shouldDisableDependents();
-
- mText = text;
-
- persistString(text);
-
- final boolean isBlocking = shouldDisableDependents();
- if (isBlocking != wasBlocking) {
- notifyDependencyChange(isBlocking);
+ // Always persist/notify the first time.
+ final boolean changed = !TextUtils.equals(mText, text);
+ if (changed || !mTextSet) {
+ mText = text;
+ mTextSet = true;
+ persistString(text);
+ if(changed) {
+ notifyDependencyChange(shouldDisableDependents());
+ notifyChanged();
+ }
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4f185e6..d43ff4e 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5867,15 +5867,6 @@
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 ':'.
*
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 11737c6..0163b47 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 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 @@
@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 @@
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 @@
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 @@
.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 @@
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 @@
return 0;
}
+ @SystemApi
public Condition copy() {
final Parcel parcel = Parcel.obtain();
try {
@@ -186,10 +210,14 @@
}
}
+ @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 adcc9d6..44c3887 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -102,9 +102,6 @@
*/
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 a0de17f..8c35901 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -23,7 +23,7 @@
/** @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 @@
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 b8f9812..a8b8da1 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -170,13 +170,18 @@
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.
@@ -675,12 +680,18 @@
@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
similarity index 90%
rename from core/java/android/service/notification/NotificationAssistantService.java
rename to core/java/android/service/notification/NotificationRankerService.java
index 41af837..520b4c2 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationRankerService.java
@@ -31,31 +31,20 @@
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 @@
@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 @@
@Override
public final IBinder onBind(Intent intent) {
if (mWrapper == null) {
- mWrapper = new NotificationAssistantWrapper();
+ mWrapper = new NotificationRankingServiceWrapper();
}
return mWrapper;
}
@@ -216,14 +205,14 @@
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/transition/Slide.java b/core/java/android/transition/Slide.java
index 9af65e4..2645f86 100644
--- a/core/java/android/transition/Slide.java
+++ b/core/java/android/transition/Slide.java
@@ -47,6 +47,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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/transition/Transition.java b/core/java/android/transition/Transition.java
index 4afa9fe..316c7e3 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -41,12 +41,12 @@
import android.widget.ListView;
import android.widget.Spinner;
+import com.android.internal.R;
+
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
-import com.android.internal.R;
-
/**
* A Transition holds information about animations that will be run on its
* targets during a scene change. Subclasses of this abstract class may
@@ -192,7 +192,7 @@
private TransitionValuesMaps mStartValues = new TransitionValuesMaps();
private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
TransitionSet mParent = null;
- private int[] mMatchOrder = DEFAULT_MATCH_ORDER;
+ int[] mMatchOrder = DEFAULT_MATCH_ORDER;
ArrayList<TransitionValues> mStartValuesList; // only valid after playTransition starts
ArrayList<TransitionValues> mEndValuesList; // only valid after playTransitions starts
@@ -246,7 +246,7 @@
// The function used to interpolate along two-dimensional points. Typically used
// for adding curves to x/y View motion.
- private PathMotion mPathMotion = STRAIGHT_PATH_MOTION;
+ PathMotion mPathMotion = STRAIGHT_PATH_MOTION;
/**
* Constructs a Transition object with no target objects. A transition with
@@ -780,7 +780,7 @@
}
}
}
- if (minStartDelay != 0) {
+ if (startDelays.size() != 0) {
for (int i = 0; i < startDelays.size(); i++) {
int index = startDelays.keyAt(i);
Animator animator = mAnimators.get(index);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index afe2f10..f7405e2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1078,8 +1078,7 @@
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 0e34067..406dc2b 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -215,7 +215,7 @@
boolean mInBatchEditControllers;
boolean mShowSoftInputOnFocus = true;
- private boolean mPreserveDetachedSelection;
+ private boolean mPreserveSelection;
boolean mTemporaryDetach;
boolean mIsBeingLongClicked;
@@ -351,9 +351,14 @@
}
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 @@
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 @@
mShowSuggestionRunnable = new Runnable() {
public void run() {
- showSuggestions();
+ replace();
}
};
// removeCallbacks is performed on every touch
@@ -2113,9 +2112,9 @@
}
private void stopTextActionModeWithPreservingSelection() {
- mPreserveDetachedSelection = true;
+ mPreserveSelection = true;
stopTextActionMode();
- mPreserveDetachedSelection = false;
+ mPreserveSelection = false;
}
/**
@@ -2233,15 +2232,6 @@
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 @@
.setEnabled(mTextView.canSelectAllText())
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
- mPreserveDetachedSelection = true;
+ mPreserveSelection = true;
}
private void replaceWithSuggestion(final SuggestionInfo suggestionInfo) {
@@ -3426,7 +3416,7 @@
@Override
protected int getTextOffset() {
- return mTextView.getSelectionStart();
+ return (mTextView.getSelectionStart() + mTextView.getSelectionStart()) / 2;
}
@Override
@@ -3590,7 +3580,6 @@
}
if (menu.hasVisibleItems() || mode.getCustomView() != null) {
- mTextView.setHasTransientState(true);
return true;
} else {
return false;
@@ -3690,15 +3679,14 @@
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 @@
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 73f8fdc..ac3eaf7 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8286,6 +8286,13 @@
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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 451669b..fbc96c2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2774,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
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 90a573b..9ccd7f0 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8170,6 +8170,8 @@
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 a99ee92..17afd92 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3138,7 +3138,7 @@
<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>
@@ -4228,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 0934c3b..cad0e7b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1844,7 +1844,7 @@
<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" />
@@ -1895,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" />
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 0000000..8b9ec74
--- /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/core/tests/coretests/src/android/transition/TransitionTest.java b/core/tests/coretests/src/android/transition/TransitionTest.java
new file mode 100644
index 0000000..7e72e25
--- /dev/null
+++ b/core/tests/coretests/src/android/transition/TransitionTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.graphics.Rect;
+import android.test.ActivityInstrumentationTestCase2;
+import android.transition.Transition.EpicenterCallback;
+import android.util.ArrayMap;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.TextView;
+
+import com.android.frameworks.coretests.R;
+
+public class TransitionTest extends ActivityInstrumentationTestCase2<AnimatorSetActivity> {
+ Activity mActivity;
+ public TransitionTest() {
+ super(AnimatorSetActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ mActivity = getActivity();
+ }
+
+ public void testClone() throws Throwable {
+ View square1 = mActivity.findViewById(R.id.square1);
+ View square2 = mActivity.findViewById(R.id.square2);
+ View square3 = mActivity.findViewById(R.id.square3);
+ Fade fade = new Fade();
+ fade.setStartDelay(1000);
+ fade.setDuration(1001);
+
+ fade.addTarget(square1);
+ fade.excludeTarget(square2, true);
+ fade.excludeChildren(square3, true);
+
+ fade.addTarget(R.id.square4);
+ fade.excludeTarget(R.id.square3, true);
+ fade.excludeChildren(R.id.square2, true);
+
+ fade.addTarget("hello");
+ fade.excludeTarget("world", true);
+
+ fade.addTarget(View.class);
+ fade.excludeTarget(TextView.class, true);
+
+ fade.setMatchOrder(Transition.MATCH_ID);
+ fade.setPropagation(new CircularPropagation());
+ fade.setPathMotion(new ArcMotion());
+ fade.setInterpolator(new AccelerateInterpolator());
+ fade.setNameOverrides(new ArrayMap<>());
+
+ EpicenterCallback epicenterCallback = new EpicenterCallback() {
+ @Override
+ public Rect onGetEpicenter(Transition transition) {
+ return null;
+ }
+ };
+
+ fade.setEpicenterCallback(epicenterCallback);
+
+ Fade clone = (Fade) fade.clone();
+ assertEquals(fade.mStartDelay, clone.mStartDelay);
+ assertEquals(fade.mDuration, clone.mDuration);
+ assertEquals(fade.mInterpolator, clone.mInterpolator);
+ assertEquals(fade.mPropagation, clone.mPropagation);
+ assertEquals(fade.getPathMotion(), clone.getPathMotion());
+ assertEquals(fade.getEpicenterCallback(), clone.getEpicenterCallback());
+ assertEquals(fade.mNameOverrides, clone.mNameOverrides);
+ assertEquals(fade.mMatchOrder, clone.mMatchOrder);
+
+ assertEquals(fade.mTargets, clone.mTargets);
+ assertEquals(fade.mTargetExcludes, clone.mTargetExcludes);
+ assertEquals(fade.mTargetChildExcludes, clone.mTargetChildExcludes);
+
+ assertEquals(fade.mTargetIds, clone.mTargetIds);
+ assertEquals(fade.mTargetIdExcludes, clone.mTargetIdExcludes);
+ assertEquals(fade.mTargetIdChildExcludes, clone.mTargetIdChildExcludes);
+
+ assertEquals(fade.mTargetNames, clone.mTargetNames);
+ assertEquals(fade.mTargetNameExcludes, clone.mTargetNameExcludes);
+
+ assertEquals(fade.mTargetTypes, clone.mTargetTypes);
+ assertEquals(fade.mTargetTypeExcludes, clone.mTargetTypeExcludes);
+ }
+}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 70995ac..9a9a89b 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -227,15 +227,16 @@
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 9595a85..c809ff4 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 @@
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 @@
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 6f23705..28ac116 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -180,8 +180,6 @@
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/CrashHandlerInjector.cpp b/libs/hwui/tests/unit/CrashHandlerInjector.cpp
deleted file mode 100644
index b1c678d..0000000
--- a/libs/hwui/tests/unit/CrashHandlerInjector.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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 "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;
diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp
new file mode 100644
index 0000000..409a12d
--- /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 8206d23..69d4487 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2730,15 +2730,16 @@
* 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 @@
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 @@
* @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 @@
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
similarity index 93%
rename from media/java/android/media/AudioRecordConfiguration.aidl
rename to media/java/android/media/AudioRecordingConfiguration.aidl
index afe912b..c63d30b 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
similarity index 82%
rename from media/java/android/media/AudioRecordConfiguration.java
rename to media/java/android/media/AudioRecordingConfiguration.java
index de78a5a..cd6f95a 100644
--- a/media/java/android/media/AudioRecordConfiguration.java
+++ b/media/java/android/media/AudioRecordingConfiguration.java
@@ -27,13 +27,13 @@
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 @@
/**
* @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 @@
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 @@
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 @@
@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 987a8b6..97f670b 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -20,7 +20,7 @@
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 @@
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 eaa92ca..e803283 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 @@
*/
oneway interface IRecordingConfigDispatcher {
- void dispatchRecordingConfigChange(in AudioRecordConfiguration[] configs);
+ void dispatchRecordingConfigChange(in AudioRecordingConfiguration[] configs);
}
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 63e3edc..7c9591d 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -349,12 +349,12 @@
/**
* 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/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 8fb58b5..bc20c17 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -491,7 +491,7 @@
* until this method is called.
*
* <p>The TV input service must call this method as soon as the content rendered onto its
- * surface is ready for viewing. This method must be called each time {@link #onTune(Uri)}
+ * surface is ready for viewing. This method must be called each time {@link #onTune}
* is called.
*
* @see #notifyVideoUnavailable
@@ -837,14 +837,15 @@
public abstract boolean onTune(Uri channelUri);
/**
- * Calls {@link #onTune(Uri)}. Override this method in order to handle {@code params}.
+ * Calls {@link #onTune(Uri)}. Override this method in order to handle domain-specific
+ * features that are only known between certain TV inputs and their clients.
*
* @param channelUri The URI of the channel.
- * @param params The extra parameters from other applications.
+ * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
+ * name, i.e. prefixed with a package name you own, so that different developers
+ * will not create conflicting keys.
* @return {@code true} if the tuning was successful, {@code false} otherwise.
- * @hide
*/
- @SystemApi
public boolean onTune(Uri channelUri, Bundle params) {
return onTune(channelUri);
}
@@ -1209,7 +1210,7 @@
}
/**
- * Calls {@link #onTune}.
+ * Calls {@link #onTune(Uri, Bundle)}.
*/
void tune(Uri channelUri, Bundle params) {
mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
@@ -1836,7 +1837,7 @@
* a hardware TV Input (e.g. HDMI 1) and forward the application's surface to the session so
* that the user can see the screen of the hardware TV Input when she tunes to a channel from
* this TV input. The implementation of this class is expected to change the channel of the
- * external set-top box via a proprietary protocol when {@link HardwareSession#onTune(Uri)} is
+ * external set-top box via a proprietary protocol when {@link HardwareSession#onTune} is
* requested by the application.
*
* <p>Note that this class is not for inputs for internal hardware like built-in tuner and HDMI
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index 6a44b1e..e623353 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -121,6 +121,8 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
*
* <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 @@
* @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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* {@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 @@
* 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 @@
* 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/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 5c4b528..9623076 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
import android.graphics.Canvas;
@@ -56,7 +57,7 @@
* TV inputs available on the system can be obtained by calling
* {@link TvInputManager#getTvInputList() TvInputManager.getTvInputList()}.)
*
- * <p>Once the application supplies the URI for a specific TV channel to {@link #tune(String, Uri)}
+ * <p>Once the application supplies the URI for a specific TV channel to {@link #tune}
* method, it takes care of underlying service binding (and unbinding if the current TvView is
* already bound to a service) and automatically allocates/deallocates resources needed. In addition
* to a few essential methods to control how the contents are presented, it also provides a way to
@@ -206,13 +207,18 @@
}
/**
- * Sets the Z order of a window owning the surface of this TvView above the normal TvView
- * but below an application.
+ * Controls whether the TvView's surface is placed on top of another regular surface view in the
+ * window (but still behind the window itself).
+ * This is typically used to place overlays on top of an underlying TvView.
*
- * @see SurfaceView#setZOrderMediaOverlay
- * @hide
+ * <p>Note that this must be set before the TvView's containing window is attached to the
+ * window manager.
+ *
+ * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
+ *
+ * @param isMediaOverlay {@code true} to be on top of another regular surface, {@code false}
+ * otherwise.
*/
- @SystemApi
public void setZOrderMediaOverlay(boolean isMediaOverlay) {
if (isMediaOverlay) {
mWindowZOrder = ZORDER_MEDIA_OVERLAY;
@@ -230,12 +236,18 @@
}
/**
- * Sets the Z order of a window owning the surface of this TvView on top of an application.
+ * Controls whether the TvView's surface is placed on top of its window. Normally it is placed
+ * behind the window, to allow it to (for the most part) appear to composite with the views in
+ * the hierarchy. By setting this, you cause it to be placed above the window. This means that
+ * none of the contents of the window this TvView is in will be visible on top of its surface.
*
- * @see SurfaceView#setZOrderOnTop
- * @hide
+ * <p>Note that this must be set before the TvView's containing window is attached to the window
+ * manager.
+ *
+ * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
+ *
+ * @param onTop {@code true} to be on top of its window, {@code false} otherwise.
*/
- @SystemApi
public void setZOrderOnTop(boolean onTop) {
if (onTop) {
mWindowZOrder = ZORDER_ON_TOP;
@@ -280,14 +292,15 @@
}
/**
- * Tunes to a given channel.
+ * Tunes to a given channel. This can be used to provide domain-specific features that are only
+ * known between certain TvView applications and their TV inputs.
*
* @param inputId The ID of TV input for the given channel.
* @param channelUri The URI of a channel.
- * @param params Extra parameters.
- * @hide
+ * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
+ * name, i.e. prefixed with a package name you own, so that different developers will
+ * not create conflicting keys.
*/
- @SystemApi
public void tune(String inputId, Uri channelUri, Bundle params) {
if (DEBUG) Log.d(TAG, "tune(" + channelUri + ")");
if (TextUtils.isEmpty(inputId)) {
@@ -360,11 +373,8 @@
*
* @param unblockedRating A TvContentRating to unblock.
* @see TvInputService.Session#notifyContentBlocked(TvContentRating)
- * @hide
- * @deprecated Use {@link #unblockContent} instead.
+ * @removed
*/
- @Deprecated
- @SystemApi
public void requestUnblockContent(TvContentRating unblockedRating) {
unblockContent(unblockedRating);
}
@@ -379,6 +389,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
public void unblockContent(TvContentRating unblockedRating) {
if (mSession != null) {
mSession.unblockContent(unblockedRating);
@@ -539,7 +550,7 @@
}
/**
- * Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle)} for the current
+ * Calls {@link TvInputService.Session#onAppPrivateCommand(String, Bundle)} for the current
* session.
*
* @param action The name of the private command to send. This <em>must</em> be a scoped name,
@@ -893,7 +904,7 @@
/**
* This is invoked when the channel of this TvView is changed by the underlying TV input
- * without any {@link TvView#tune(String, Uri)} request.
+ * without any {@link TvView#tune} request.
*
* @param inputId The ID of the TV input bound to this view.
* @param channelUri The URI of a channel.
diff --git a/packages/DocumentsUI/res/layout/dialog_delete_confirmation.xml b/packages/DocumentsUI/res/layout/dialog_delete_confirmation.xml
index 990ce0b..a1c2910 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/res/values-sw720dp-land/dimens.xml b/packages/DocumentsUI/res/values-sw720dp-land/dimens.xml
index fa11244..1b67ee5 100644
--- a/packages/DocumentsUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/DocumentsUI/res/values-sw720dp-land/dimens.xml
@@ -20,4 +20,5 @@
<dimen name="list_divider_inset">80dp</dimen>
+ <dimen name="max_drawer_width">320dp</dimen>
</resources>
diff --git a/packages/DocumentsUI/res/values-sw720dp/dimens.xml b/packages/DocumentsUI/res/values-sw720dp/dimens.xml
index b5d1150..982b204 100644
--- a/packages/DocumentsUI/res/values-sw720dp/dimens.xml
+++ b/packages/DocumentsUI/res/values-sw720dp/dimens.xml
@@ -20,4 +20,5 @@
<dimen name="list_item_padding">24dp</dimen>
+ <dimen name="max_drawer_width">320dp</dimen>
</resources>
diff --git a/packages/DocumentsUI/res/values/dimens.xml b/packages/DocumentsUI/res/values/dimens.xml
index 9fc8a73..5af7da3 100644
--- a/packages/DocumentsUI/res/values/dimens.xml
+++ b/packages/DocumentsUI/res/values/dimens.xml
@@ -37,4 +37,5 @@
<dimen name="dir_elevation">8dp</dimen>
<dimen name="drag_shadow_size">120dp</dimen>
<dimen name="grid_item_elevation">2dp</dimen>
+ <dimen name="max_drawer_width">280dp</dimen>
</resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Display.java b/packages/DocumentsUI/src/com/android/documentsui/Display.java
new file mode 100644
index 0000000..bae2d58
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/Display.java
@@ -0,0 +1,56 @@
+/*
+ * 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.documentsui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Point;
+import android.util.TypedValue;
+
+/*
+ * Convenience class for getting display related attributes
+ */
+public final class Display {
+ /*
+ * Returns the screen width in pixels.
+ */
+ public static float screenWidth(Activity activity) {
+ Point size = new Point();
+ activity.getWindowManager().getDefaultDisplay().getSize(size);
+ return size.x;
+ }
+
+ /*
+ * Returns logical density of the display.
+ */
+ public static float density(Context context) {
+ return context.getResources().getDisplayMetrics().density;
+ }
+
+ /*
+ * Returns action bar height in pixels.
+ */
+ public static float actionBarHeight(Context context) {
+ int actionBarHeight = 0;
+ TypedValue tv = new TypedValue();
+ if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
+ actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,
+ context.getResources().getDisplayMetrics());
+ }
+ return actionBarHeight;
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
index 9fceff5..020f2c0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DrawerController.java
@@ -16,10 +16,14 @@
package com.android.documentsui;
+import static com.android.documentsui.Shared.DEBUG;
+
import android.app.Activity;
+import android.content.Context;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.DrawerLayout.DrawerListener;
+import android.util.Log;
import android.view.View;
import android.widget.Toolbar;
@@ -30,6 +34,8 @@
*/
abstract class DrawerController implements DrawerListener {
+ public static final String TAG = "DrawerController";
+
abstract void setOpen(boolean open);
abstract boolean isPresent();
abstract boolean isOpen();
@@ -50,6 +56,8 @@
View drawer = activity.findViewById(R.id.drawer_roots);
Toolbar toolbar = (Toolbar) activity.findViewById(R.id.roots_toolbar);
+ drawer.getLayoutParams().width = calculateDrawerWidth(activity);
+
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
activity,
layout,
@@ -67,6 +75,19 @@
return new DummyDrawerController();
}
+ private static int calculateDrawerWidth(Activity activity) {
+ // Material design specification for navigation drawer:
+ // https://www.google.com/design/spec/patterns/navigation-drawer.html
+ float width = Display.screenWidth(activity) - Display.actionBarHeight(activity);
+ float maxWidth = activity.getResources().getDimension(R.dimen.max_drawer_width);
+ int finalWidth = (int) ((width > maxWidth ? maxWidth : width));
+
+ if (DEBUG)
+ Log.d(TAG, "Calculated drawer width:" + (finalWidth / Display.density(activity)));
+
+ return finalWidth;
+ }
+
/**
* Runtime controller that manages a real drawer.
*/
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 3920c62..ad4823e 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 @@
}
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 ae2c6e7..ef397f6 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/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 51d8ca0..978ca94 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -155,6 +155,9 @@
<!-- Default for Settings.Secure.LONG_PRESS_TIMEOUT_MILLIS -->
<integer name="def_long_press_timeout_millis">500</integer>
+ <!-- Default for Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD -->
+ <bool name="def_show_ime_with_hard_keyboard">false</bool>
+
<!-- Default for Settings.System.POINTER_SPEED -->
<integer name="def_pointer_speed">0</integer>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index a424d55..987b5ea 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 @@
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 @@
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) {
@@ -1936,7 +1940,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 124;
+ private static final int SETTINGS_VERSION = 125;
private final int mUserId;
@@ -2116,6 +2120,22 @@
currentVersion = 124;
}
+ if (currentVersion == 124) {
+ // Version 124: allow OEMs to set a default value for whether IME should be
+ // shown when a physical keyboard is connected.
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ Setting currentSetting = secureSettings.getSettingLocked(
+ Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
+ if (currentSetting == null) {
+ secureSettings.insertSettingLocked(
+ Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+ getContext().getResources().getBoolean(
+ R.bool.def_show_ime_with_hard_keyboard) ? "1" : "0",
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ currentVersion = 125;
+ }
+
// vXXX: Add new settings above this point.
// Return the current version.
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 0fad113..1c6a071 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -56,7 +56,6 @@
import android.app.Service;
import android.content.ClipData;
import android.content.Context;
-import android.content.ContextWrapper;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
@@ -206,7 +205,7 @@
mMainHandler = new ServiceHandler("BugreportProgressServiceMainThread");
mScreenshotHandler = new ScreenshotHandler("BugreportProgressServiceScreenshotThread");
- mScreenshotsDir = new File(new ContextWrapper(mContext).getFilesDir(), SCREENSHOT_DIR);
+ mScreenshotsDir = new File(getFilesDir(), SCREENSHOT_DIR);
if (!mScreenshotsDir.exists()) {
Log.i(TAG, "Creating directory " + mScreenshotsDir + " to store temporary screenshots");
if (!mScreenshotsDir.mkdir()) {
@@ -505,9 +504,9 @@
Log.d(TAG, "Removing ID " + id);
mProcesses.remove(id);
}
- stopSelfWhenDone();
Log.v(TAG, "stopProgress(" + id + "): cancel notification");
NotificationManager.from(mContext).cancel(TAG, id);
+ stopSelfWhenDone();
}
/**
@@ -877,6 +876,8 @@
info = sharedInfo;
Log.d(TAG, "shareBugreport(): no info for ID " + id + " on managed processes ("
+ mProcesses + "), using info from intent instead (" + info + ")");
+ } else {
+ Log.v(TAG, "shareBugReport(): id " + id + " info = " + info);
}
addDetailsToZipFile(mContext, info);
@@ -1532,6 +1533,7 @@
final File newFile;
if (!newName.equals(oldName)) {
final File renamedFile = new File(screenshotDir, newName);
+ Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
} else {
Log.w(TAG, "Name didn't change: " + oldName); // Shouldn't happen.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 6137349..c8c71cb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -14,11 +14,11 @@
package com.android.systemui.qs;
+import android.graphics.Path;
import android.util.Log;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnLayoutChangeListener;
-import android.view.animation.PathInterpolator;
import android.widget.TextView;
import com.android.systemui.qs.PagedTileLayout.PageListener;
import com.android.systemui.qs.QSPanel.QSTileLayout;
@@ -40,9 +40,6 @@
private static final String ALLOW_FANCY_ANIMATION = "sysui_qs_fancy_anim";
private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows";
- public static final PathInterpolator TRANSLATION_Y_INTERPOLATOR =
- new PathInterpolator(.1f, .3f, 1, 1);
-
public static final float EXPANDED_TILE_DELAY = .7f;
private final ArrayList<View> mAllViews = new ArrayList<>();
@@ -56,7 +53,7 @@
private boolean mOnFirstPage = true;
private TouchAnimator mFirstPageAnimator;
private TouchAnimator mFirstPageDelayedAnimator;
- private TouchAnimator mTranslationYAnimator;
+ private TouchAnimator mTranslationAnimator;
private TouchAnimator mNonfirstPageAnimator;
private boolean mOnKeyguard;
@@ -110,7 +107,7 @@
clearAnimationState();
}
} else if (MOVE_FULL_ROWS.equals(key)) {
- mFullRows = newValue != null && Integer.parseInt(newValue) != 0;
+ mFullRows = newValue == null || Integer.parseInt(newValue) != 0;
} else if (QuickQSPanel.NUM_QUICK_TILES.equals(key)) {
mNumQuickTiles = QuickQSPanel.getNumQuickTiles(mQsContainer.getContext());
clearAnimationState();
@@ -129,6 +126,7 @@
private void updateAnimators() {
TouchAnimator.Builder firstPageBuilder = new Builder();
+ TouchAnimator.Builder translationXBuilder = new Builder();
TouchAnimator.Builder translationYBuilder = new Builder();
TouchAnimator.Builder firstPageDelayedBuilder = new Builder();
Collection<QSTile<?>> tiles = mQsPanel.getHost().getTiles();
@@ -138,7 +136,6 @@
int lastYDiff = 0;
firstPageDelayedBuilder.setStartDelay(EXPANDED_TILE_DELAY);
firstPageBuilder.setListener(this);
- translationYBuilder.setInterpolator(TRANSLATION_Y_INTERPOLATOR);
// Fade in the tiles/labels as we reach the final position.
firstPageDelayedBuilder.addFloat(mQsPanel.getTileLayout(), "alpha", 0, 1);
mAllViews.clear();
@@ -158,7 +155,7 @@
final int yDiff = loc2[1] - loc1[1];
lastYDiff = yDiff;
// Move the quick tile right from its location to the new one.
- firstPageBuilder.addFloat(quickTileView, "translationX", 0, xDiff);
+ translationXBuilder.addFloat(quickTileView, "translationX", 0, xDiff);
translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff);
// Counteract the parent translation on the tile. So we have a static base to
@@ -167,7 +164,7 @@
// Move the real tile's label from the quick tile position to its final
// location.
- firstPageBuilder.addFloat(label, "translationX", -xDiff, 0);
+ translationXBuilder.addFloat(label, "translationX", -xDiff, 0);
translationYBuilder.addFloat(label, "translationY", -yDiff, 0);
mTopFiveQs.add(tileIcon);
@@ -188,7 +185,13 @@
if (mAllowFancy) {
mFirstPageAnimator = firstPageBuilder.build();
mFirstPageDelayedAnimator = firstPageDelayedBuilder.build();
- mTranslationYAnimator = translationYBuilder.build();
+ Path path = new Path();
+ path.moveTo(0, 0);
+ path.cubicTo(0, 0, 0, 1, 1, 1);
+ mTranslationAnimator = new TouchAnimator.Builder()
+ .addPath(translationXBuilder.build(), translationYBuilder.build(),
+ "position", "position", path)
+ .build();
}
mNonfirstPageAnimator = new TouchAnimator.Builder()
.addFloat(mQuickQsPanel, "alpha", 1, 0)
@@ -226,7 +229,7 @@
mQuickQsPanel.setAlpha(1);
mFirstPageAnimator.setPosition(position);
mFirstPageDelayedAnimator.setPosition(position);
- mTranslationYAnimator.setPosition(position);
+ mTranslationAnimator.setPosition(position);
} else {
mNonfirstPageAnimator.setPosition(position);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
index 35ade58..db17245 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
@@ -14,7 +14,11 @@
package com.android.systemui.qs;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.util.FloatProperty;
import android.util.MathUtils;
+import android.util.Pair;
import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
@@ -74,6 +78,19 @@
}
}
+ private static final FloatProperty<TouchAnimator> POSITION =
+ new FloatProperty<TouchAnimator>("position") {
+ @Override
+ public void setValue(TouchAnimator touchAnimator, float value) {
+ touchAnimator.setPosition(value);
+ }
+
+ @Override
+ public Float get(TouchAnimator touchAnimator) {
+ return touchAnimator.mLastT;
+ }
+ };
+
public static class ListenerAdapter implements Listener {
@Override
public void onAnimationAtStart() { }
@@ -124,6 +141,19 @@
return this;
}
+ public Builder addPath(Object target, String xProp, String yProp,
+ Path path) {
+ return addPath(target, target, xProp, yProp, path);
+ }
+
+ public Builder addPath(Object xTarget, Object yTarget, String xProp, String yProp,
+ Path path) {
+ add(new Pair<>(xTarget, yTarget),
+ KeyframeSet.ofPath(getProperty(xTarget, xProp, float.class),
+ getProperty(yTarget, yProp, float.class), path));
+ return this;
+ }
+
private void add(Object target, KeyframeSet keyframeSet) {
mTargets.add(target);
mValues.add(keyframeSet);
@@ -152,6 +182,9 @@
return View.SCALE_Y;
}
}
+ if (target instanceof TouchAnimator && "position".equals(property)) {
+ return POSITION;
+ }
return Property.of(target.getClass(), cls, property);
}
@@ -208,6 +241,10 @@
public static KeyframeSet ofFloat(Property property, float... values) {
return new FloatKeyframeSet((Property<?, Float>) property, values);
}
+
+ public static KeyframeSet ofPath(Property xProp, Property yProp, Path path) {
+ return new PathKeyframeSet<>(xProp, yProp, path);
+ }
}
private static class FloatKeyframeSet<T> extends KeyframeSet {
@@ -245,4 +282,31 @@
mProperty.set((T) target, (int) (firstFloat + (secondFloat - firstFloat) * amount));
}
}
+
+ private static class PathKeyframeSet<T> extends KeyframeSet {
+ private final Property<T, Float> mXProp;
+ private final Property<T, Float> mYProp;
+ private final Path mPath;
+ private final PathMeasure mPathMeasure;
+ private final float mLength;
+ private final float[] mPos;
+
+ public PathKeyframeSet(Property<T, Float> xProp, Property<T, Float> yProp, Path path) {
+ super(2);
+ mXProp = xProp;
+ mYProp = yProp;
+ mPath = path;
+ mPathMeasure = new PathMeasure(mPath, false);
+ mLength = mPathMeasure.getLength();
+ mPos = new float[2];
+ }
+
+ @Override
+ protected void interpolate(int index, float amount, Object target) {
+ Pair<Object, Object> targets = (Pair<Object, Object>) target;
+ mPathMeasure.getPosTan(amount * mLength, mPos, null);
+ mXProp.set((T) targets.first, mPos[0]);
+ mYProp.set((T) targets.second, mPos[1]);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 0709992..db686a8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -58,6 +58,7 @@
private final IBinder mToken = new Binder();
private final IQSTileService mService;
private final TileServiceManager mServiceManager;
+ private final int mUser;
private boolean mListening;
private boolean mBound;
@@ -71,6 +72,7 @@
mServiceManager = host.getTileServices().getTileWrapper(this);
mService = mServiceManager.getTileService();
mTile = new Tile(mComponent);
+ mUser = ActivityManager.getCurrentUser();
try {
PackageManager pm = mContext.getPackageManager();
ServiceInfo info = pm.getServiceInfo(mComponent, 0);
@@ -86,6 +88,10 @@
}
}
+ public int getUser() {
+ return mUser;
+ }
+
public ComponentName getComponent() {
return mComponent;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index c4436f4..2aad161 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -85,6 +85,7 @@
mHandler = handler;
mIntent = intent;
mUser = user;
+ if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser);
}
public ComponentName getComponent() {
@@ -116,13 +117,13 @@
if (!checkComponentState()) {
return;
}
- if (DEBUG) Log.d(TAG, "Binding service " + mIntent);
+ if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser);
mBindTryCount++;
mContext.bindServiceAsUser(mIntent, this,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
mUser);
} else {
- if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent);
+ if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser);
// Give it another chance next time it needs to be bound, out of kindness.
mBindTryCount = 0;
mWrapper = null;
@@ -350,7 +351,7 @@
@Override
public void onClick(IBinder iBinder) {
- if (DEBUG) Log.d(TAG, "onClick " + iBinder);
+ if (DEBUG) Log.d(TAG, "onClick " + iBinder + " " + mUser);
if (mWrapper == null || !mWrapper.onClick(iBinder)) {
mClickBinder = iBinder;
queueMessage(MSG_ON_CLICK);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 54bf68b..efac0fb3 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.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 @@
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/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index ac23f44..3ad2cc3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -645,7 +645,8 @@
if (stack != null) {
stackLayout.initialize(taskStackBounds,
TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
- mDummyStackView.setTasks(stack, false /* notifyStackChanges */);
+ mDummyStackView.setTasks(stack, false /* notifyStackChanges */,
+ false /* relayoutTaskStack */);
}
Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
if (!taskViewBounds.equals(mLastTaskViewBounds)) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 1af5a55..8342de5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -184,7 +184,8 @@
// Update the stack
mTaskStackView.onResume(isResumingFromVisible);
- mTaskStackView.setTasks(stack, isResumingFromVisible /* notifyStackChanges */);
+ mTaskStackView.setTasks(stack, isResumingFromVisible /* notifyStackChanges */,
+ true /* relayoutTaskStack */);
if (isResumingFromVisible) {
// If we are already visible, then restore the background scrim
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 9da8fee..2601cd3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -298,7 +298,7 @@
/**
* Sets the stack tasks of this TaskStackView from the given TaskStack.
*/
- public void setTasks(TaskStack stack, boolean notifyStackChanges) {
+ public void setTasks(TaskStack stack, boolean notifyStackChanges, boolean relayoutTaskStack) {
boolean isInitialized = mLayoutAlgorithm.isInitialized();
mStack.setTasks(getContext(), stack.computeAllTasksList(),
notifyStackChanges && isInitialized);
@@ -307,15 +307,18 @@
// measure/layout pass
updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET);
updateToInitialState();
- 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());
+ if (relayoutTaskStack) {
+ 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());
+ }
}
}
}
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 9c6f2c5..d50e67a 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 @@
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 @@
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 @@
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/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index f894a22..5dcd393 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -114,6 +114,7 @@
private final ManagedProfileController mProfileController;
private final NextAlarmController mNextAlarmController;
private View mHeader;
+ private int mCurrentUser;
public QSTileHost(Context context, PhoneStatusBar statusBar,
BluetoothController bluetooth, LocationController location,
@@ -320,7 +321,8 @@
}
if (DEBUG) Log.d(TAG, "Recreating tiles");
final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
- if (tileSpecs.equals(mTileSpecs)) return;
+ int currentUser = ActivityManager.getCurrentUser();
+ if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
if (!tileSpecs.contains(tile.getKey())) {
if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
@@ -329,15 +331,16 @@
}
final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
for (String tileSpec : tileSpecs) {
- if (mTiles.containsKey(tileSpec)) {
- QSTile<?> tile = mTiles.get(tileSpec);
+ QSTile<?> tile = mTiles.get(tileSpec);
+ if (tile != null && (!(tile instanceof CustomTile)
+ || ((CustomTile) tile).getUser() == currentUser)) {
if (DEBUG) Log.d(TAG, "Adding " + tile);
tile.removeCallbacks();
newTiles.put(tileSpec, tile);
} else {
if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
try {
- QSTile<?> tile = createTile(tileSpec);
+ tile = createTile(tileSpec);
if (tile != null && tile.isAvailable()) {
tile.setTileSpec(tileSpec);
newTiles.put(tileSpec, tile);
@@ -347,6 +350,7 @@
}
}
}
+ mCurrentUser = currentUser;
mTileSpecs.clear();
mTileSpecs.addAll(tileSpecs);
mTiles.clear();
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 1a557e4..64d8ab2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -45,7 +45,6 @@
public static final long ANIMATION_DURATION = 220;
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
= new PathInterpolator(0f, 0, 0.7f, 1f);
-
private static final float SCRIM_BEHIND_ALPHA = 0.62f;
private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.45f;
private static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
@@ -60,6 +59,10 @@
private final UnlockMethodCache mUnlockMethodCache;
private final View mHeadsUpScrim;
+ private float mScrimBehindAlpha = SCRIM_BEHIND_ALPHA;
+ private float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
+ private float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
+
protected boolean mKeyguardShowing;
private float mFraction;
@@ -101,6 +104,19 @@
scheduleUpdate();
}
+ public void setShowScrimBehind(boolean show) {
+ if (show) {
+ mScrimBehindAlpha = SCRIM_BEHIND_ALPHA;
+ mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
+ mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
+ } else {
+ mScrimBehindAlpha = 0;
+ mScrimBehindAlphaKeyguard = 0;
+ mScrimBehindAlphaUnlocking = 0;
+ }
+ scheduleUpdate();
+ }
+
public void onTrackingStarted() {
mExpanding = true;
mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
@@ -229,7 +245,7 @@
fraction = (float) Math.pow(fraction, 0.8f);
behindFraction = (float) Math.pow(behindFraction, 0.8f);
setScrimInFrontColor(fraction * SCRIM_IN_FRONT_ALPHA);
- setScrimBehindColor(behindFraction * SCRIM_BEHIND_ALPHA_KEYGUARD);
+ setScrimBehindColor(behindFraction * mScrimBehindAlphaKeyguard);
} else if (mBouncerShowing) {
setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA);
setScrimBehindColor(0f);
@@ -237,8 +253,8 @@
float fraction = Math.max(0, Math.min(mFraction, 1));
setScrimInFrontColor(0f);
setScrimBehindColor(fraction
- * (SCRIM_BEHIND_ALPHA_KEYGUARD - SCRIM_BEHIND_ALPHA_UNLOCKING)
- + SCRIM_BEHIND_ALPHA_UNLOCKING);
+ * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking)
+ + mScrimBehindAlphaUnlocking);
}
}
@@ -251,7 +267,7 @@
} else {
// woo, special effects
final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
- setScrimBehindColor(k * SCRIM_BEHIND_ALPHA);
+ setScrimBehindColor(k * mScrimBehindAlpha);
}
}
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 4877a378..4e667c6 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -1062,7 +1062,7 @@
type, kind, norm, size);
}
return (jlong)(uintptr_t)rsElementCreate((RsContext)con, (RsDataType)type, (RsDataKind)kind,
- norm, size, true);
+ norm, size);
}
static jlong
@@ -1101,7 +1101,7 @@
jlong id = (jlong)(uintptr_t)rsElementCreate2((RsContext)con,
(const RsElement *)ids, fieldCount,
nameArray, fieldCount * sizeof(size_t), sizeArray,
- (const uint32_t *)arraySizes, fieldCount, true);
+ (const uint32_t *)arraySizes, fieldCount);
free(ids);
free(arraySizes);
@@ -1175,7 +1175,7 @@
}
return (jlong)(uintptr_t)rsTypeCreate((RsContext)con, (RsElement)eid, dimx, dimy, dimz, mips,
- faces, yuv, true);
+ faces, yuv);
}
static void
@@ -1211,7 +1211,7 @@
}
return (jlong)(uintptr_t) rsAllocationCreateTyped((RsContext)con, (RsType)type,
(RsAllocationMipmapControl)mips,
- (uint32_t)usage, (uintptr_t)pointer, true);
+ (uint32_t)usage, (uintptr_t)pointer);
}
static void
@@ -1316,7 +1316,7 @@
const void* ptr = bitmap.getPixels();
jlong id = (jlong)(uintptr_t)rsAllocationCreateFromBitmap((RsContext)con,
(RsType)type, (RsAllocationMipmapControl)mip,
- ptr, bitmap.getSize(), usage, true);
+ ptr, bitmap.getSize(), usage);
bitmap.unlockPixels();
return id;
}
@@ -1332,7 +1332,7 @@
const void* ptr = bitmap.getPixels();
jlong id = (jlong)(uintptr_t)rsAllocationCreateTyped((RsContext)con,
(RsType)type, (RsAllocationMipmapControl)mip,
- (uint32_t)usage, (uintptr_t)ptr, true);
+ (uint32_t)usage, (uintptr_t)ptr);
bitmap.unlockPixels();
return id;
}
@@ -1348,7 +1348,7 @@
const void* ptr = bitmap.getPixels();
jlong id = (jlong)(uintptr_t)rsAllocationCubeCreateFromBitmap((RsContext)con,
(RsType)type, (RsAllocationMipmapControl)mip,
- ptr, bitmap.getSize(), usage, true);
+ ptr, bitmap.getSize(), usage);
bitmap.unlockPixels();
return id;
}
diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
index e3af7e3..3f453dc 100644
--- a/services/core/java/com/android/server/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -1051,6 +1051,30 @@
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 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 0317641..f07c1d0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1899,7 +1899,9 @@
case SYSTEM_USER_UNLOCK_MSG: {
final int userId = msg.arg1;
mSystemServiceManager.unlockUser(userId);
- mRecentTasks.loadUserRecentsLocked(userId);
+ synchronized (ActivityManagerService.this) {
+ mRecentTasks.loadUserRecentsLocked(userId);
+ }
if (userId == UserHandle.USER_SYSTEM) {
startPersistentApps(PackageManager.MATCH_ENCRYPTION_UNAWARE);
}
@@ -6655,7 +6657,7 @@
synchronized(this) {
ActivityStack stack = ActivityRecord.getStackLocked(token);
if (stack != null) {
- ActivityRecord.activityResumedLocked(token);
+ stack.activityResumedLocked(token);
}
}
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 2ea5d15..5219827 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1277,13 +1277,6 @@
}
}
- static void activityResumedLocked(IBinder token) {
- final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- if (DEBUG_SAVED_STATE) Slog.i(TAG_STATES, "Resumed activity; dropping state of: " + r);
- r.icicle = null;
- r.haveState = false;
- }
-
static int getTaskForActivityLocked(IBinder token, boolean onlyRoot) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index e50722a..39895ac5 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1088,6 +1088,13 @@
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}
+ final void activityResumedLocked(IBinder token) {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ if (DEBUG_SAVED_STATE) Slog.i(TAG_STATES, "Resumed activity; dropping state of: " + r);
+ r.icicle = null;
+ r.haveState = false;
+ }
+
final void activityStoppedLocked(ActivityRecord r, Bundle icicle,
PersistableBundle persistentState, CharSequence description) {
if (r.state != ActivityState.STOPPING) {
@@ -4436,10 +4443,10 @@
"Moving to " + (andResume ? "RESUMED" : "PAUSED") + " Relaunching " + r
+ " callers=" + Debug.getCallers(6));
r.forceNewConfig = false;
+ mStackSupervisor.activityRelaunchingLocked(r);
r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, changes,
!andResume, new Configuration(mService.mConfiguration),
new Configuration(r.task.mOverrideConfig), preserveWindow);
- mStackSupervisor.activityRelaunchingLocked(r);
// Note: don't need to call pauseIfSleepingLocked() here, because
// the caller will only pass in 'andResume' if this activity is
// currently resumed, which implies we aren't sleeping.
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 62275a9..ea85fa1 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -40,6 +40,7 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
import android.util.DisplayMetrics;
import android.util.Slog;
@@ -74,6 +75,7 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
@@ -444,9 +446,9 @@
// task as having a true root activity.
rootWasReset = true;
}
-
userId = UserHandle.getUserId(info.applicationInfo.uid);
- mUserSetupComplete = mService.mUserController.isUserSetupCompleteLocked(userId);
+ mUserSetupComplete = Settings.Secure.getIntForUser(mService.mContext.getContentResolver(),
+ USER_SETUP_COMPLETE, 0, userId) != 0;
if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) {
// If the activity itself has requested auto-remove, then just always do it.
autoRemoveRecents = true;
@@ -1570,6 +1572,7 @@
pw.print(prefix); pw.print("userId="); pw.print(userId);
pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
pw.print(" mCallingUid="); UserHandle.formatUid(pw, mCallingUid);
+ pw.print(" mUserSetupComplete="); pw.print(mUserSetupComplete);
pw.print(" mCallingPackage="); pw.println(mCallingPackage);
if (affinity != null || rootAffinity != null) {
pw.print(prefix); pw.print("affinity="); pw.print(affinity);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index addffd3..4a5df7a 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -24,7 +24,6 @@
import static android.app.ActivityManager.USER_OP_SUCCESS;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.os.Process.SYSTEM_UID;
-import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -46,14 +45,11 @@
import android.app.IStopUserCallback;
import android.app.IUserSwitchObserver;
import android.app.KeyguardManager;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Bundle;
@@ -71,12 +67,10 @@
import android.os.UserManagerInternal;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
-import android.provider.Settings;
import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.android.internal.R;
@@ -153,34 +147,6 @@
private final LockPatternUtils mLockPatternUtils;
- // Set of users who have completed the set-up process.
- private final SparseBooleanArray mSetupCompletedUsers = new SparseBooleanArray();
- private final UserSetupCompleteContentObserver mUserSetupCompleteContentObserver;
-
- private class UserSetupCompleteContentObserver extends ContentObserver {
- private final Uri mUserSetupComplete = Settings.Secure.getUriFor(USER_SETUP_COMPLETE);
-
- public UserSetupCompleteContentObserver(Handler handler) {
- super(handler);
- }
-
- void register(ContentResolver resolver) {
- resolver.registerContentObserver(mUserSetupComplete, false, this, UserHandle.USER_ALL);
- synchronized (mService) {
- updateUserSetupCompleteLocked(UserHandle.USER_ALL);
- }
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
- if (mUserSetupComplete.equals(uri)) {
- synchronized (mService) {
- updateUserSetupCompleteLocked(userId);
- }
- }
- }
- }
-
UserController(ActivityManagerService service) {
mService = service;
mHandler = mService.mHandler;
@@ -190,7 +156,6 @@
mUserLru.add(UserHandle.USER_SYSTEM);
mLockPatternUtils = new LockPatternUtils(mService.mContext);
updateStartedUserArrayLocked();
- mUserSetupCompleteContentObserver = new UserSetupCompleteContentObserver(mHandler);
}
void finishUserSwitch(UserState uss) {
@@ -477,7 +442,6 @@
mStartedUsers.remove(userId);
mUserLru.remove(Integer.valueOf(userId));
updateStartedUserArrayLocked();
- mSetupCompletedUsers.delete(userId);
mService.onUserStoppedLocked(userId);
// Clean up all state and processes associated with the user.
@@ -677,7 +641,6 @@
final Integer userIdInt = userId;
mUserLru.remove(userIdInt);
mUserLru.add(userIdInt);
- updateUserSetupCompleteLocked(userId);
if (foreground) {
mCurrentUserId = userId;
@@ -892,22 +855,6 @@
mUserSwitchObservers.finishBroadcast();
}
- void updateUserSetupCompleteLocked(int userId) {
- final ContentResolver cr = mService.mContext.getContentResolver();
- for (int i = mStartedUsers.size() - 1; i >= 0; i--) {
- int startedUser = mStartedUsers.keyAt(i);
- if (startedUser == userId || userId == UserHandle.USER_ALL) {
- final boolean setupComplete =
- Settings.Secure.getIntForUser(cr, USER_SETUP_COMPLETE, 0, startedUser) != 0;
- mSetupCompletedUsers.put(startedUser, setupComplete);
- }
- }
- }
-
- boolean isUserSetupCompleteLocked(int userId) {
- return mSetupCompletedUsers.get(userId);
- }
-
private void stopBackgroundUsersIfEnforced(int oldUserId) {
// Never stop system user
if (oldUserId == UserHandle.USER_SYSTEM) {
@@ -1218,7 +1165,6 @@
void onSystemReady() {
updateCurrentProfileIdsLocked();
- mUserSetupCompleteContentObserver.register(mService.mContext.getContentResolver());
}
/**
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f2a9c2c..d919737 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.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 @@
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 86dcd0f..7a085e1 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 @@
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 @@
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 @@
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 @@
}
}
- AudioRecordConfiguration[] getActiveRecordConfigurations() {
+ AudioRecordingConfiguration[] getActiveRecordingConfigurations() {
synchronized(mRecordConfigs) {
- return mRecordConfigs.values().toArray(new AudioRecordConfiguration[0]);
+ return mRecordConfigs.values().toArray(new AudioRecordingConfiguration[0]);
}
}
@@ -121,10 +121,10 @@
* @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 @@
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 @@
}
} else {
mRecordConfigs.put(sessionKey,
- new AudioRecordConfiguration(session, source,
+ new AudioRecordingConfiguration(session, source,
clientFormat, deviceFormat, patchHandle));
configChanged = true;
}
@@ -171,7 +171,7 @@
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 9820a12..c19b51f 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 @@
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 @@
}
}
}
- 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 @@
}
}
- 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 @@
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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 881e8f0..24ffe1f 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.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;
@@ -224,8 +224,6 @@
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;
@@ -291,14 +289,13 @@
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;
@@ -756,7 +753,7 @@
}
}
mListeners.onPackagesChanged(queryReplace, pkgList);
- mAssistant.onPackagesChanged(queryReplace, pkgList);
+ mRankerServices.onPackagesChanged(queryReplace, pkgList);
mConditionProviders.onPackagesChanged(queryReplace, pkgList);
mRankingHelper.onPackagesChanged(queryReplace, pkgList);
}
@@ -805,7 +802,7 @@
// 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);
@@ -816,7 +813,7 @@
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);
}
}
@@ -892,7 +889,6 @@
mHandler = new WorkerHandler();
mRankingThread.start();
- mAssistantThread.start();
String[] extractorNames;
try {
extractorNames = resources.getStringArray(R.array.config_notificationSignalExtractors);
@@ -901,7 +897,6 @@
}
mUsageStats = new NotificationUsageStats(getContext());
mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
- mAssistantHandler = new Handler(mAssistantThread.getLooper());
mRankingHelper = new RankingHelper(getContext(),
mRankingHandler,
mUsageStats,
@@ -937,7 +932,7 @@
importOldBlockDb();
mListeners = new NotificationListeners();
- mAssistant = new NotificationAssistant();
+ mRankerServices = new NotificationRanker();
mStatusBar = getLocalService(StatusBarManagerInternal.class);
mStatusBar.setNotificationDelegate(mNotificationDelegate);
@@ -1060,7 +1055,7 @@
// bind to listener services.
mSettingsObserver.observe();
mListeners.onBootPhaseAppsCanStart();
- mAssistant.onBootPhaseAppsCanStart();
+ mRankerServices.onBootPhaseAppsCanStart();
mConditionProviders.onBootPhaseAppsCanStart();
}
}
@@ -1415,17 +1410,30 @@
*/
@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);
+ }
}
/**
@@ -1478,8 +1486,8 @@
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 {
@@ -1972,7 +1980,7 @@
}
@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);
@@ -1980,7 +1988,7 @@
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();
@@ -2130,8 +2138,8 @@
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);
@@ -2358,9 +2366,9 @@
}
}
- // 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
}
@@ -3477,21 +3485,21 @@
}
}
- 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;
}
@@ -3519,10 +3527,10 @@
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;
@@ -3531,7 +3539,7 @@
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);
@@ -3542,12 +3550,12 @@
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 93dcc72..206a143 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 @@
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 8d75f60..902a3d9 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.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.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 @@
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 @@
}
}
+ 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 @@
}
}
+ 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 @@
}
- 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 @@
}
}
+ // TODO Simplify with lambdas.
+
@Override
public void onPackageAdded(String packageName, int uid) {
UserHandle user = new UserHandle(getChangingUserId());
@@ -523,6 +601,25 @@
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 c9613b4..561682c 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 @@
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 8d4c9e5..e180e050 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 @@
}
@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 @@
}
@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 ccbd823..28d34a8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -233,17 +233,30 @@
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 "-m":
- compilationMode = getNextArgRequired();
+ case "-a":
+ allPackages = true;
+ break;
+ case "-c":
+ clearProfileData = true;
break;
case "-f":
forceCompilation = true;
break;
+ case "-m":
+ compilationMode = getNextArgRequired();
+ break;
+ case "--reset":
+ forceCompilation = true;
+ clearProfileData = true;
+ compilationMode = "extract";
+ break;
default:
pw.println("Error: Unknown option: " + opt);
return 1;
@@ -255,7 +268,7 @@
useJitProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
extractOnly = false;
break;
- case "all":
+ case "full":
useJitProfiles = false;
extractOnly = false;
break;
@@ -272,19 +285,49 @@
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);
}
- boolean success = mInterface.performDexOpt(packageName, null /* instructionSet */,
- useJitProfiles, extractOnly, forceCompilation);
- if (success) {
+ 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);
+ }
+ }
+
+ 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 @@
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 0000000..5382ff1
--- /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 e51a2e1..83a5ef5 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 @@
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/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 49aaa4a..30442bc 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -1292,6 +1292,7 @@
@Override
public void unblockContent(
IBinder sessionToken, String unblockedRating, int userId) {
+ ensureParentalControlsPermission();
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "unblockContent");
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index a81fba0..55b3c7b 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 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/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 2731f429..9795c93 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -129,6 +129,7 @@
boolean mAlwaysFocusable;
boolean mAppStopped;
+ int mPendingRelaunchCount;
ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
@@ -531,6 +532,26 @@
}
}
+ boolean isRelaunching() {
+ return mPendingRelaunchCount > 0;
+ }
+
+ void startRelaunching() {
+ if (canFreezeBounds()) {
+ freezeBounds();
+ }
+ mPendingRelaunchCount++;
+ }
+
+ void finishRelaunching() {
+ if (canFreezeBounds()) {
+ unfreezeBounds();
+ }
+ if (mPendingRelaunchCount > 0) {
+ mPendingRelaunchCount--;
+ }
+ }
+
void addWindow(WindowState w) {
for (int i = allAppWindows.size() - 1; i >= 0; i--) {
WindowState candidate = allAppWindows.get(i);
@@ -579,20 +600,26 @@
}
}
+ private boolean canFreezeBounds() {
+ // For freeform windows, we can't freeze the bounds at the moment because this would make
+ // the resizing unresponsive.
+ return mTask != null && !mTask.inFreeformWorkspace();
+ }
+
/**
* Freezes the task bounds. The size of this task reported the app will be fixed to the bounds
* freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even
* if they change in the meantime. If the bounds are already frozen, the bounds will be frozen
* with a queue.
*/
- void freezeBounds() {
+ private void freezeBounds() {
mFrozenBounds.offer(new Rect(mTask.mPreparedFrozenBounds));
}
/**
* Unfreezes the previously frozen bounds. See {@link #freezeBounds}.
*/
- void unfreezeBounds() {
+ private void unfreezeBounds() {
mFrozenBounds.remove();
for (int i = windows.size() - 1; i >= 0; i--) {
final WindowState win = windows.get(i);
@@ -658,7 +685,10 @@
pw.print(" startingMoved="); pw.println(startingMoved);
}
if (!mFrozenBounds.isEmpty()) {
- pw.print(prefix); pw.print("mFrozenBounds="); pw.print(mFrozenBounds);
+ pw.print(prefix); pw.print("mFrozenBounds="); pw.println(mFrozenBounds);
+ }
+ if (mPendingRelaunchCount != 0) {
+ pw.print(prefix); pw.print("mPendingRelaunchCount="); pw.println(mPendingRelaunchCount);
}
}
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index fc5d8ce..95be233 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 @@
/** 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 @@
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 6d1cec4..97d0ae0 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.Slog;
import android.util.TypedValue;
+import com.android.server.wm.DimLayer.DimLayerUser;
+
import java.io.PrintWriter;
/**
@@ -18,7 +20,8 @@
* 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 @@
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 @@
}
} 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 @@
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 b6aa3f2..9bceee7 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 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 @@
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 @@
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 f7035c5..92701de 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.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 @@
}
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 86327f7..676b90f 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 @@
}
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 85bddee..4698e4e 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 @@
}
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 ccba88d..0979cd3 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 @@
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 b64aaa8..a998bc3 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.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.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;
@@ -2340,6 +2344,7 @@
mTokenMap.remove(token.token);
} else if (atoken != null) {
atoken.firstWindowDrawn = false;
+ atoken.allDrawn = false;
}
}
@@ -4251,6 +4256,28 @@
TAG_WM, "No longer Stopped: " + wtoken);
wtoken.mAppStopped = false;
wtoken.setWindowsExiting(false);
+ mOpeningApps.add(wtoken);
+ wtoken.startingMoved = false;
+
+ // If the token is currently hidden (should be the
+ // common case), then we need to set up to wait for
+ // its windows to be ready.
+ if (wtoken.hidden) {
+ wtoken.allDrawn = false;
+ wtoken.deferClearAllDrawn = false;
+ wtoken.waitingToShow = true;
+
+ if (wtoken.clientHidden) {
+ // In the case where we are making an app visible
+ // but holding off for a transition, we still need
+ // to tell the client to make its windows visible so
+ // they get drawn. Otherwise, we will wait on
+ // performing the transition until all windows have
+ // been drawn, they never will be, and we are sad.
+ wtoken.clientHidden = false;
+ wtoken.sendAppVisibilityToClients();
+ }
+ }
}
// If we are preparing an app transition, then delay changing
@@ -4268,29 +4295,7 @@
}
wtoken.inPendingTransaction = true;
if (visible) {
- mOpeningApps.add(wtoken);
- wtoken.startingMoved = false;
wtoken.mEnteringAnimation = true;
-
- // If the token is currently hidden (should be the
- // common case), then we need to set up to wait for
- // its windows to be ready.
- if (wtoken.hidden) {
- wtoken.allDrawn = false;
- wtoken.deferClearAllDrawn = false;
- wtoken.waitingToShow = true;
-
- if (wtoken.clientHidden) {
- // In the case where we are making an app visible
- // but holding off for a transition, we still need
- // to tell the client to make its windows visible so
- // they get drawn. Otherwise, we will wait on
- // performing the transition until all windows have
- // been drawn, they never will be, and we are sad.
- wtoken.clientHidden = false;
- wtoken.sendAppVisibilityToClients();
- }
- }
} else {
wtoken.setWindowsExiting(true);
mClosingApps.add(wtoken);
@@ -7468,6 +7473,35 @@
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)) {
@@ -7500,6 +7534,7 @@
if (auditSafeMode >= buildDate) {
mSafeMode = true;
+ showAuditSafeModeNotification();
} else {
SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, "");
SystemProperties.set(ShutdownThread.AUDIT_SAFEMODE_PROPERTY, "");
@@ -9636,8 +9671,8 @@
public void notifyAppRelaunching(IBinder token) {
synchronized (mWindowMap) {
AppWindowToken appWindow = findAppWindowToken(token);
- if (canFreezeBounds(appWindow)) {
- appWindow.freezeBounds();
+ if (appWindow != null) {
+ appWindow.startRelaunching();
}
}
}
@@ -9645,20 +9680,12 @@
public void notifyAppRelaunchingFinished(IBinder token) {
synchronized (mWindowMap) {
AppWindowToken appWindow = findAppWindowToken(token);
- if (canFreezeBounds(appWindow)) {
- appWindow.unfreezeBounds();
+ if (appWindow != null) {
+ appWindow.finishRelaunching();
}
}
}
- private boolean canFreezeBounds(AppWindowToken appWindow) {
-
- // For freeform windows, we can't freeze the bounds at the moment because this would make
- // the resizing unresponsive.
- return appWindow != null && appWindow.mTask != null
- && !appWindow.mTask.inFreeformWorkspace();
- }
-
@Override
public int getDockedDividerInsetsLw() {
return getDefaultDisplayContentLocked().getDockedDividerController().getContentInsets();
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index e3955fe..8ada2f1 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.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 @@
}
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 @@
mService.enableScreenIfNeededLocked();
mService.scheduleAnimationLocked();
+ mService.mWindowPlacerLocked.destroyPendingSurfaces();
if (DEBUG_WINDOW_TRACE) Slog.e(TAG,
"performSurfacePlacementInner exit: animating=" + mService.mAnimator.isAnimating());
@@ -1278,7 +1280,12 @@
"Check opening app=" + wtoken + ": allDrawn="
+ wtoken.allDrawn + " startingDisplayed="
+ wtoken.startingDisplayed + " startingMoved="
- + wtoken.startingMoved);
+ + wtoken.startingMoved + " isRelaunching()="
+ + wtoken.isRelaunching());
+
+ if (wtoken.isRelaunching()) {
+ return false;
+ }
final boolean drawnBeforeRestoring = wtoken.allDrawn;
wtoken.restoreSavedSurfaces();
@@ -1620,6 +1627,25 @@
}
}
+ /**
+ * 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 d979675..6ab4804 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.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 @@
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 @@
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 @@
* - 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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 68fd0f6..b316cbd 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 @@
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 a2d5259..59f8284a 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.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 @@
// 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/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 3ae1072..23f186c 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
new file mode 100644
index 0000000..64eb294
--- /dev/null
+++ b/services/tests/servicestests/res/drawable/icon1.png
Binary files differ
diff --git a/services/tests/servicestests/res/drawable/icon2.png b/services/tests/servicestests/res/drawable/icon2.png
new file mode 100644
index 0000000..75024841
--- /dev/null
+++ b/services/tests/servicestests/res/drawable/icon2.png
Binary files differ
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 0000000..eb16a1d
--- /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 0000000..a9b6684
--- /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 0000000..52e8f37
--- /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 0000000..6a24453
--- /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 0000000..d342bca
--- /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 0000000..afb45aa
--- /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 0000000..64b32f6
--- /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 51b0e17..c6cea7c 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 0000000..fb4346a
--- /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 8caa1b4..fefbe9f 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 0000000..8b5ad7c
--- /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 0000000..80f39f3e
--- /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 0000000..0890bd6
--- /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 0000000..2ec5014
--- /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 0000000..3d746e7
--- /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 0000000..352a2fd
--- /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 0000000..42281d1
--- /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 0000000..2fa440a
--- /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 0000000..5a43f80
--- /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 0000000..e8de7c2
--- /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 7172147..495d620 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 @@
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 d1fd56a..c7ae6fc 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 @@
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 a46aaec..a9259fa 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -305,9 +305,12 @@
*/
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;