Merge "Show package icon/label for resolved package-targeted implicit intents" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index adbc76a..e91d017 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5300,6 +5300,13 @@
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+ field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
+ field public static final int IMPORTANCE_HIGH = 4; // 0x4
+ field public static final int IMPORTANCE_LOW = 2; // 0x2
+ field public static final int IMPORTANCE_MAX = 5; // 0x5
+ field public static final int IMPORTANCE_MIN = 1; // 0x1
+ field public static final int IMPORTANCE_NONE = 0; // 0x0
+ field public static final int IMPORTANCE_UNSPECIFIED = -1000; // 0xfffffc18
field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4
field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1
field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
@@ -6066,6 +6073,7 @@
field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20
+ field public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 64; // 0x40
field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
field public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 4; // 0x4
field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
@@ -8185,7 +8193,6 @@
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 SYSTEM_HEALTH_SERVICE = "systemhealth";
field public static final java.lang.String TELECOM_SERVICE = "telecom";
@@ -9495,20 +9502,13 @@
public class LauncherApps {
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
- method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo);
- method public android.os.ParcelFileDescriptor getShortcutIconFd(java.lang.String, 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 hasShortcutHostPermission();
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 boolean startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
- method public boolean startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
}
@@ -9521,19 +9521,6 @@
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);
- method public void setShortcutIds(java.util.List<java.lang.String>);
- 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 {
@@ -10034,66 +10021,6 @@
field public java.lang.String permission;
}
- public final class ShortcutInfo implements android.os.Parcelable {
- method public int describeContents();
- method public android.content.ComponentName getActivityComponent();
- method public java.util.Set<java.lang.String> getCategories();
- method public android.os.PersistableBundle getExtras();
- method public int getIconResourceId();
- 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 getText();
- method public java.lang.String getTitle();
- method public android.os.UserHandle getUserHandle();
- method public int getWeight();
- method public boolean hasIconFile();
- method public boolean hasIconResource();
- method public boolean hasKeyFieldsOnly();
- 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_KEY_FIELDS_ONLY = 16; // 0x10
- field public static final int FLAG_PINNED = 2; // 0x2
- field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
- }
-
- 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 setCategories(java.util.Set<java.lang.String>);
- 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 setText(java.lang.String);
- 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 addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
- method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
- method public int getIconMaxDimensions();
- method public int getMaxDynamicShortcutCount();
- method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
- method public long getRateLimitResetTime();
- method public int getRemainingCallCount();
- method public void removeAllDynamicShortcuts();
- method public void removeDynamicShortcuts(java.util.List<java.lang.String>);
- 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);
@@ -32416,7 +32343,7 @@
field public static final java.lang.String AUTO_TIME_ZONE = "auto_time_zone";
field public static final java.lang.String BLUETOOTH_ON = "bluetooth_on";
field public static final java.lang.String BOOT_COUNT = "boot_count";
- field public static final java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync";
+ field public static final java.lang.String CONTACT_METADATA_SYNC_ENABLED = "contact_metadata_sync_enabled";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATA_ROAMING = "data_roaming";
field public static final java.lang.String DEBUG_APP = "debug_app";
@@ -34717,13 +34644,6 @@
method public int getSuppressedVisualEffects();
method public boolean isAmbient();
method public boolean matchesInterruptionFilter();
- field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
- field public static final int IMPORTANCE_HIGH = 4; // 0x4
- field public static final int IMPORTANCE_LOW = 2; // 0x2
- field public static final int IMPORTANCE_MAX = 5; // 0x5
- field public static final int IMPORTANCE_MIN = 1; // 0x1
- field public static final int IMPORTANCE_NONE = 0; // 0x0
- field public static final int IMPORTANCE_UNSPECIFIED = -1000; // 0xfffffc18
}
public static class NotificationListenerService.RankingMap implements android.os.Parcelable {
diff --git a/api/removed.txt b/api/removed.txt
index 2673a82..0db555f 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -387,6 +387,10 @@
field public static final deprecated java.lang.String TIMESTAMP = "timestamp";
}
+ public static final class Settings.Global extends android.provider.Settings.NameValueTable {
+ field public static final deprecated java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync";
+ }
+
public static final class Settings.System extends android.provider.Settings.NameValueTable {
field public static final java.lang.String APPEND_FOR_LAST_AUDIBLE = "_last_audible";
field public static final java.lang.String VOLUME_ALARM = "volume_alarm";
diff --git a/api/system-current.txt b/api/system-current.txt
index a57e14b..8781663 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5447,6 +5447,13 @@
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+ field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
+ field public static final int IMPORTANCE_HIGH = 4; // 0x4
+ field public static final int IMPORTANCE_LOW = 2; // 0x2
+ field public static final int IMPORTANCE_MAX = 5; // 0x5
+ field public static final int IMPORTANCE_MIN = 1; // 0x1
+ field public static final int IMPORTANCE_NONE = 0; // 0x0
+ field public static final int IMPORTANCE_UNSPECIFIED = -1000; // 0xfffffc18
field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4
field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1
field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
@@ -6233,6 +6240,7 @@
field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20
+ field public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 64; // 0x40
field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
field public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 4; // 0x4
field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
@@ -8507,7 +8515,6 @@
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 SYSTEM_HEALTH_SERVICE = "systemhealth";
field public static final java.lang.String TELECOM_SERVICE = "telecom";
@@ -9852,20 +9859,13 @@
public class LauncherApps {
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
- method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo);
- method public android.os.ParcelFileDescriptor getShortcutIconFd(java.lang.String, 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 hasShortcutHostPermission();
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 boolean startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
- method public boolean startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
}
@@ -9878,19 +9878,6 @@
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);
- method public void setShortcutIds(java.util.List<java.lang.String>);
- 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 {
@@ -10461,66 +10448,6 @@
field public java.lang.String permission;
}
- public final class ShortcutInfo implements android.os.Parcelable {
- method public int describeContents();
- method public android.content.ComponentName getActivityComponent();
- method public java.util.Set<java.lang.String> getCategories();
- method public android.os.PersistableBundle getExtras();
- method public int getIconResourceId();
- 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 getText();
- method public java.lang.String getTitle();
- method public android.os.UserHandle getUserHandle();
- method public int getWeight();
- method public boolean hasIconFile();
- method public boolean hasIconResource();
- method public boolean hasKeyFieldsOnly();
- 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_KEY_FIELDS_ONLY = 16; // 0x10
- field public static final int FLAG_PINNED = 2; // 0x2
- field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
- }
-
- 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 setCategories(java.util.Set<java.lang.String>);
- 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 setText(java.lang.String);
- 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 addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
- method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
- method public int getIconMaxDimensions();
- method public int getMaxDynamicShortcutCount();
- method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
- method public long getRateLimitResetTime();
- method public int getRemainingCallCount();
- method public void removeAllDynamicShortcuts();
- method public void removeDynamicShortcuts(java.util.List<java.lang.String>);
- 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);
@@ -35115,7 +35042,7 @@
field public static final java.lang.String AUTO_TIME_ZONE = "auto_time_zone";
field public static final java.lang.String BLUETOOTH_ON = "bluetooth_on";
field public static final java.lang.String BOOT_COUNT = "boot_count";
- field public static final java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync";
+ field public static final java.lang.String CONTACT_METADATA_SYNC_ENABLED = "contact_metadata_sync_enabled";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATA_ROAMING = "data_roaming";
field public static final java.lang.String DEBUG_APP = "debug_app";
@@ -37443,13 +37370,6 @@
method public int getSuppressedVisualEffects();
method public boolean isAmbient();
method public boolean matchesInterruptionFilter();
- field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
- field public static final int IMPORTANCE_HIGH = 4; // 0x4
- field public static final int IMPORTANCE_LOW = 2; // 0x2
- field public static final int IMPORTANCE_MAX = 5; // 0x5
- field public static final int IMPORTANCE_MIN = 1; // 0x1
- field public static final int IMPORTANCE_NONE = 0; // 0x0
- field public static final int IMPORTANCE_UNSPECIFIED = -1000; // 0xfffffc18
}
public static class NotificationListenerService.RankingMap implements android.os.Parcelable {
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 7f2c2d6..5f9d350 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -385,6 +385,10 @@
field public static final deprecated java.lang.String TIMESTAMP = "timestamp";
}
+ public static final class Settings.Global extends android.provider.Settings.NameValueTable {
+ field public static final deprecated java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync";
+ }
+
public static final class Settings.System extends android.provider.Settings.NameValueTable {
field public static final java.lang.String APPEND_FOR_LAST_AUDIBLE = "_last_audible";
field public static final java.lang.String VOLUME_ALARM = "volume_alarm";
diff --git a/api/test-current.txt b/api/test-current.txt
index 2f28265..410eb88 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5301,6 +5301,13 @@
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+ field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
+ field public static final int IMPORTANCE_HIGH = 4; // 0x4
+ field public static final int IMPORTANCE_LOW = 2; // 0x2
+ field public static final int IMPORTANCE_MAX = 5; // 0x5
+ field public static final int IMPORTANCE_MIN = 1; // 0x1
+ field public static final int IMPORTANCE_NONE = 0; // 0x0
+ field public static final int IMPORTANCE_UNSPECIFIED = -1000; // 0xfffffc18
field public static final int INTERRUPTION_FILTER_ALARMS = 4; // 0x4
field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1
field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
@@ -6071,6 +6078,7 @@
field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20
+ field public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 64; // 0x40
field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
field public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 4; // 0x4
field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
@@ -8192,7 +8200,6 @@
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 SYSTEM_HEALTH_SERVICE = "systemhealth";
field public static final java.lang.String TELECOM_SERVICE = "telecom";
@@ -9506,20 +9513,13 @@
public class LauncherApps {
ctor public LauncherApps(android.content.Context);
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
- method public android.os.ParcelFileDescriptor getShortcutIconFd(android.content.pm.ShortcutInfo);
- method public android.os.ParcelFileDescriptor getShortcutIconFd(java.lang.String, 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 hasShortcutHostPermission();
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 boolean startShortcut(java.lang.String, java.lang.String, android.graphics.Rect, android.os.Bundle, android.os.UserHandle);
- method public boolean startShortcut(android.content.pm.ShortcutInfo, android.graphics.Rect, android.os.Bundle);
method public void unregisterCallback(android.content.pm.LauncherApps.Callback);
}
@@ -9532,19 +9532,6 @@
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);
- method public void setShortcutIds(java.util.List<java.lang.String>);
- 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 {
@@ -10046,67 +10033,6 @@
field public java.lang.String permission;
}
- public final class ShortcutInfo implements android.os.Parcelable {
- method public int describeContents();
- method public android.content.ComponentName getActivityComponent();
- method public java.util.Set<java.lang.String> getCategories();
- method public android.os.PersistableBundle getExtras();
- method public int getIconResourceId();
- 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 getText();
- method public java.lang.String getTitle();
- method public android.os.UserHandle getUserHandle();
- method public int getWeight();
- method public boolean hasIconFile();
- method public boolean hasIconResource();
- method public boolean hasKeyFieldsOnly();
- 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_KEY_FIELDS_ONLY = 16; // 0x10
- field public static final int FLAG_PINNED = 2; // 0x2
- field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
- }
-
- 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 setCategories(java.util.Set<java.lang.String>);
- 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 setText(java.lang.String);
- method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
- method public android.content.pm.ShortcutInfo.Builder setWeight(int);
- }
-
- public class ShortcutManager {
- ctor public ShortcutManager(android.content.Context);
- method public boolean addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
- method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
- method public int getIconMaxDimensions();
- method public int getMaxDynamicShortcutCount();
- method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
- method public long getRateLimitResetTime();
- method public int getRemainingCallCount();
- method public void removeAllDynamicShortcuts();
- method public void removeDynamicShortcuts(java.util.List<java.lang.String>);
- 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);
@@ -32489,7 +32415,7 @@
field public static final java.lang.String AUTO_TIME_ZONE = "auto_time_zone";
field public static final java.lang.String BLUETOOTH_ON = "bluetooth_on";
field public static final java.lang.String BOOT_COUNT = "boot_count";
- field public static final java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync";
+ field public static final java.lang.String CONTACT_METADATA_SYNC_ENABLED = "contact_metadata_sync_enabled";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATA_ROAMING = "data_roaming";
field public static final java.lang.String DEBUG_APP = "debug_app";
@@ -34794,13 +34720,6 @@
method public int getSuppressedVisualEffects();
method public boolean isAmbient();
method public boolean matchesInterruptionFilter();
- field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
- field public static final int IMPORTANCE_HIGH = 4; // 0x4
- field public static final int IMPORTANCE_LOW = 2; // 0x2
- field public static final int IMPORTANCE_MAX = 5; // 0x5
- field public static final int IMPORTANCE_MIN = 1; // 0x1
- field public static final int IMPORTANCE_NONE = 0; // 0x0
- field public static final int IMPORTANCE_UNSPECIFIED = -1000; // 0xfffffc18
}
public static class NotificationListenerService.RankingMap implements android.os.Parcelable {
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 2673a82..0db555f 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -387,6 +387,10 @@
field public static final deprecated java.lang.String TIMESTAMP = "timestamp";
}
+ public static final class Settings.Global extends android.provider.Settings.NameValueTable {
+ field public static final deprecated java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync";
+ }
+
public static final class Settings.System extends android.provider.Settings.NameValueTable {
field public static final java.lang.String APPEND_FOR_LAST_AUDIBLE = "_last_audible";
field public static final java.lang.String VOLUME_ALARM = "volume_alarm";
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e54edf2..cfffe34 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4183,12 +4183,10 @@
// window is being added.
r.mPendingRemoveWindow = r.window;
r.mPendingRemoveWindowManager = wm;
- if (r.mPreserveWindow) {
- // We can only keep the part of the view hierarchy that we control,
- // everything else must be removed, because it might not be able to
- // behave properly when activity is relaunching.
- r.window.clearContentView();
- }
+ // We can only keep the part of the view hierarchy that we control,
+ // everything else must be removed, because it might not be able to
+ // behave properly when activity is relaunching.
+ r.window.clearContentView();
} else {
wm.removeViewImmediate(v);
}
@@ -4196,6 +4194,13 @@
if (wtoken != null && r.mPendingRemoveWindow == null) {
WindowManagerGlobal.getInstance().closeAll(wtoken,
r.activity.getClass().getName(), "Activity");
+ } else if (r.mPendingRemoveWindow != null) {
+ // We're preserving only one window, others should be closed so app views
+ // will be detached before the final tear down. It should be done now because
+ // some components (e.g. WebView) rely on detach callbacks to perform receiver
+ // unregister and other cleanup.
+ WindowManagerGlobal.getInstance().closeAllExceptView(token, v,
+ r.activity.getClass().getName(), "Activity");
}
r.activity.mDecor = null;
}
@@ -5205,10 +5210,6 @@
} else {
Log.e(TAG, "Unable to setupGraphicsSupport due to missing code-cache directory");
}
-
- // Add the lib dir path to hardware renderer so that vulkan layers
- // can be searched for within that directory.
- ThreadedRenderer.setLibDir(data.info.getLibDir());
}
// Install the Network Security Config Provider. This must happen before the application
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index d89e186..c869944 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -65,6 +65,8 @@
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ setupVulkanLayerPath(pathClassloader, librarySearchPath);
+
mLoaders.put(zip, pathClassloader);
return pathClassloader;
}
@@ -76,6 +78,8 @@
}
}
+ private static native void setupVulkanLayerPath(ClassLoader classLoader, String librarySearchPath);
+
/**
* Adds a new path the classpath of the given loader.
* @throws IllegalStateException if the provided class loader is not a {@link PathClassLoader}.
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 47bff64..18b72e2 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -16,11 +16,8 @@
package android.app;
-import com.android.internal.util.Preconditions;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.app.Notification.Builder;
import android.content.ComponentName;
@@ -39,8 +36,7 @@
import android.os.StrictMode;
import android.os.UserHandle;
import android.provider.Settings.Global;
-import android.service.notification.IConditionListener;
-import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
import android.util.ArraySet;
@@ -48,12 +44,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Collection;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.List;
-import java.util.Set;
/**
* Class to notify the user of events that happen. This is how you tell
@@ -186,6 +180,56 @@
*/
public static final int INTERRUPTION_FILTER_UNKNOWN = 0;
+ /** @hide */
+ @IntDef({VISIBILITY_NO_OVERRIDE, IMPORTANCE_UNSPECIFIED, IMPORTANCE_NONE,
+ IMPORTANCE_MIN, IMPORTANCE_LOW, IMPORTANCE_DEFAULT, IMPORTANCE_HIGH,
+ IMPORTANCE_MAX})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Importance {}
+
+ /** Value signifying that the user has not expressed a per-app visibility override value.
+ * @hide */
+ public static final int VISIBILITY_NO_OVERRIDE = -1000;
+ /**
+ * Value signifying that the user has not expressed an importance.
+ *
+ * This value is for persisting preferences, and should never be associated with
+ * an actual notification.
+ */
+ public static final int IMPORTANCE_UNSPECIFIED = -1000;
+
+ /**
+ * A notification with no importance: shows nowhere, is blocked.
+ */
+ public static final int IMPORTANCE_NONE = 0;
+
+ /**
+ * Min notification importance: only shows in the shade, below the fold.
+ */
+ public static final int IMPORTANCE_MIN = 1;
+
+ /**
+ * Low notification importance: shows everywhere, but is not intrusive.
+ */
+ public static final int IMPORTANCE_LOW = 2;
+
+ /**
+ * Default notification importance: shows everywhere, allowed to makes noise,
+ * but does not visually intrude.
+ */
+ public static final int IMPORTANCE_DEFAULT = 3;
+
+ /**
+ * Higher notification importance: shows everywhere, allowed to makes noise and peek.
+ */
+ public static final int IMPORTANCE_HIGH = 4;
+
+ /**
+ * Highest notification importance: shows everywhere, allowed to makes noise, peek, and
+ * use full screen intents.
+ */
+ public static final int IMPORTANCE_MAX = 5;
+
private static INotificationManager sService;
/** @hide */
@@ -538,8 +582,10 @@
/**
* Returns the user specified importance for notifications from the calling package.
+ *
+ * @return An importance level, such as {@link #IMPORTANCE_DEFAULT}.
*/
- public @NotificationListenerService.Ranking.Importance int getImportance() {
+ public @Importance int getImportance() {
INotificationManager service = getService();
try {
return service.getPackageImportance(mContext.getPackageName());
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 451acf3..f78ed1e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2571,6 +2571,11 @@
public static final int KEYGUARD_DISABLE_FINGERPRINT = 1 << 5;
/**
+ * Disable text entry into notifications on secure keyguard screens (e.g. PIN/Pattern/Password).
+ */
+ public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 1 << 6;
+
+ /**
* Disable all current and future keyguard customizations.
*/
public static final int KEYGUARD_DISABLE_FEATURES_ALL = 0x7fffffff;
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index e748477..d036d96 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -472,12 +472,6 @@
private static final int ADDRESS_LENGTH = 17;
- private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
- /** @hide */
- public static final int ACTIVITY_ENERGY_INFO_CACHED = 0;
- /** @hide */
- public static final int ACTIVITY_ENERGY_INFO_REFRESHED = 1;
-
/**
* Lazily initialized singleton. Guaranteed final after first object
* constructed.
@@ -1374,13 +1368,13 @@
* @return a record with {@link BluetoothActivityEnergyInfo} or null if
* report is unavailable or unsupported
* @deprecated use the asynchronous
- * {@link #requestControllerActivityEnergyInfo(int, ResultReceiver)} instead.
+ * {@link #requestControllerActivityEnergyInfo(ResultReceiver)} instead.
* @hide
*/
@Deprecated
public BluetoothActivityEnergyInfo getControllerActivityEnergyInfo(int updateType) {
SynchronousResultReceiver receiver = new SynchronousResultReceiver();
- requestControllerActivityEnergyInfo(updateType, receiver);
+ requestControllerActivityEnergyInfo(receiver);
try {
SynchronousResultReceiver.Result result = receiver.awaitResult(1000);
if (result.bundle != null) {
@@ -1400,34 +1394,24 @@
* A null value for the activity info object may be sent if the bluetooth service is
* unreachable or the device does not support reporting such information.
*
- * @param updateType Type of info, cached vs refreshed.
* @param result The callback to which to send the activity info.
* @hide
*/
- public void requestControllerActivityEnergyInfo(int updateType, ResultReceiver result) {
- if (getState() != STATE_ON) {
- result.send(0, null);
- return;
- }
-
+ public void requestControllerActivityEnergyInfo(ResultReceiver result) {
try {
- if (!mService.isActivityAndEnergyReportingSupported()) {
- result.send(0, null);
- return;
- }
- synchronized(this) {
- if (updateType == ACTIVITY_ENERGY_INFO_REFRESHED) {
- mService.getActivityEnergyInfoFromController();
- wait(CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS);
+ synchronized(mManagerCallback) {
+ if (mService != null) {
+ mService.requestActivityInfo(result);
+ result = null;
}
- mService.requestActivityInfo(result);
}
- } catch (InterruptedException e) {
- Log.e(TAG, "getControllerActivityEnergyInfoCallback wait interrupted: " + e);
- result.send(0, null);
} catch (RemoteException e) {
Log.e(TAG, "getControllerActivityEnergyInfoCallback: " + e);
- result.send(0, null);
+ } finally {
+ if (result != null) {
+ // Only send an immediate result if we failed.
+ result.send(0, null);
+ }
}
}
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 45d86415..a420539 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -102,7 +102,6 @@
boolean isOffloadedFilteringSupported();
boolean isOffloadedScanBatchingSupported();
boolean isActivityAndEnergyReportingSupported();
- void getActivityEnergyInfoFromController();
BluetoothActivityEnergyInfo reportActivityInfo();
/**
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index 0d25f80..7ed827c 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -230,11 +230,12 @@
}
/**
- * Gets a value. Valid value types are {@link String}, {@link Boolean}, and
- * {@link Number} implementations.
+ * Gets a value. Valid value types are {@link String}, {@link Boolean},
+ * {@link Number}, and {@code byte[]} implementations.
*
* @param key the value to get
- * @return the data for the value
+ * @return the data for the value, or {@code null} if the value is missing or if {@code null}
+ * was previously added with the given {@code key}
*/
public Object get(String key) {
return mValues.get(key);
@@ -255,7 +256,7 @@
* Gets a value and converts it to a Long.
*
* @param key the value to get
- * @return the Long value, or null if the value is missing or cannot be converted
+ * @return the Long value, or {@code null} if the value is missing or cannot be converted
*/
public Long getAsLong(String key) {
Object value = mValues.get(key);
@@ -280,7 +281,7 @@
* Gets a value and converts it to an Integer.
*
* @param key the value to get
- * @return the Integer value, or null if the value is missing or cannot be converted
+ * @return the Integer value, or {@code null} if the value is missing or cannot be converted
*/
public Integer getAsInteger(String key) {
Object value = mValues.get(key);
@@ -305,7 +306,7 @@
* Gets a value and converts it to a Short.
*
* @param key the value to get
- * @return the Short value, or null if the value is missing or cannot be converted
+ * @return the Short value, or {@code null} if the value is missing or cannot be converted
*/
public Short getAsShort(String key) {
Object value = mValues.get(key);
@@ -330,7 +331,7 @@
* Gets a value and converts it to a Byte.
*
* @param key the value to get
- * @return the Byte value, or null if the value is missing or cannot be converted
+ * @return the Byte value, or {@code null} if the value is missing or cannot be converted
*/
public Byte getAsByte(String key) {
Object value = mValues.get(key);
@@ -355,7 +356,7 @@
* Gets a value and converts it to a Double.
*
* @param key the value to get
- * @return the Double value, or null if the value is missing or cannot be converted
+ * @return the Double value, or {@code null} if the value is missing or cannot be converted
*/
public Double getAsDouble(String key) {
Object value = mValues.get(key);
@@ -380,7 +381,7 @@
* Gets a value and converts it to a Float.
*
* @param key the value to get
- * @return the Float value, or null if the value is missing or cannot be converted
+ * @return the Float value, or {@code null} if the value is missing or cannot be converted
*/
public Float getAsFloat(String key) {
Object value = mValues.get(key);
@@ -405,7 +406,7 @@
* Gets a value and converts it to a Boolean.
*
* @param key the value to get
- * @return the Boolean value, or null if the value is missing or cannot be converted
+ * @return the Boolean value, or {@code null} if the value is missing or cannot be converted
*/
public Boolean getAsBoolean(String key) {
Object value = mValues.get(key);
@@ -428,7 +429,8 @@
* any other types to byte arrays.
*
* @param key the value to get
- * @return the byte[] value, or null is the value is missing or not a byte[]
+ * @return the {@code byte[]} value, or {@code null} is the value is missing or not a
+ * {@code byte[]}
*/
public byte[] getAsByteArray(String key) {
Object value = mValues.get(key);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 8349d3d..8f5ddf8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3603,6 +3603,8 @@
*
* @see #getSystemService
* @see android.content.pm.ShortcutManager
+ *
+ * @hide
*/
public static final String SHORTCUT_SERVICE = "shortcut";
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 824722d..8ca27c5 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -166,6 +166,8 @@
* @param shortcuts all shortcuts from the package (dynamic and/or pinned). Only "key"
* information will be provided, as defined in {@link ShortcutInfo#hasKeyFieldsOnly()}.
* @param user The UserHandle of the profile that generated the change.
+ *
+ * @hide
*/
public void onShortcutsChanged(@NonNull String packageName,
@NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
@@ -174,6 +176,8 @@
/**
* Represents a query passed to {@link #getShortcuts(ShortcutQuery, UserHandle)}.
+ *
+ * @hide
*/
public static class ShortcutQuery {
/**
@@ -422,6 +426,8 @@
* the user is trying a new launcher application. The user may decide to change the default
* launcher to the calling application again, so even if a launcher application loses
* this permission, it does <b>not</b> have to purge pinned shortcut information.
+ *
+ * @hide
*/
public boolean hasShortcutHostPermission() {
try {
@@ -441,6 +447,8 @@
* @param user The UserHandle of the profile.
*
* @return the IDs of {@link ShortcutInfo}s that match the query.
+ *
+ * @hide
*/
@Nullable
public List<ShortcutInfo> getShortcuts(@NonNull ShortcutQuery query,
@@ -480,6 +488,8 @@
* @param packageName The target package name.
* @param shortcutIds The IDs of the shortcut to be pinned.
* @param user The UserHandle of the profile.
+ *
+ * @hide
*/
public void pinShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
@NonNull UserHandle user) {
@@ -519,6 +529,8 @@
* #hasShortcutHostPermission()}.
*
* @param shortcut The target shortcut.
+ *
+ * @hide
*/
public ParcelFileDescriptor getShortcutIconFd(
@NonNull ShortcutInfo shortcut) {
@@ -536,6 +548,8 @@
* @param packageName The target package name.
* @param shortcutId The ID of the shortcut to lad rom.
* @param user The UserHandle of the profile.
+ *
+ * @hide
*/
public ParcelFileDescriptor getShortcutIconFd(
@NonNull String packageName, @NonNull String shortcutId, @NonNull UserHandle user) {
@@ -565,6 +579,8 @@
* @param user The UserHandle of the profile.
* @return {@code false} when the shortcut is no longer valid (e.g. the creator application
* has been uninstalled). {@code true} when the shortcut is still valid.
+ *
+ * @hide
*/
public boolean startShortcut(@NonNull String packageName, @NonNull String shortcutId,
@Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions,
@@ -584,6 +600,8 @@
* @param startActivityOptions Options to pass to startActivity.
* @return {@code false} when the shortcut is no longer valid (e.g. the creator application
* has been uninstalled). {@code true} when the shortcut is still valid.
+ *
+ * @hide
*/
public boolean startShortcut(@NonNull ShortcutInfo shortcut,
@Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index bd8cae2..4340d04 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -50,6 +50,8 @@
* </ul>
*
* @see {@link ShortcutManager}.
+ *
+ * @hide
*/
public final class ShortcutInfo implements Parcelable {
/* @hide */
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index ab0367d..16486e1 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -88,6 +88,8 @@
* 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)}.
+ *
+ * @hide
*/
public class ShortcutManager {
private static final String TAG = "ShortcutManager";
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index a364010..110df5d 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -309,90 +309,97 @@
public void updateConfiguration(Configuration config, DisplayMetrics metrics,
CompatibilityInfo compat) {
- synchronized (mAccessLock) {
- if (false) {
- Slog.i(TAG, "**** Updating config of " + this + ": old config is "
- + mConfiguration + " old compat is " + mCompatibilityInfo);
- Slog.i(TAG, "**** Updating config of " + this + ": new config is "
- + config + " new compat is " + compat);
- }
- if (compat != null) {
- mCompatibilityInfo = compat;
- }
- if (metrics != null) {
- mMetrics.setTo(metrics);
- }
- // NOTE: We should re-arrange this code to create a Display
- // with the CompatibilityInfo that is used everywhere we deal
- // with the display in relation to this app, rather than
- // doing the conversion here. This impl should be okay because
- // we make sure to return a compatible display in the places
- // where there are public APIs to retrieve the display... but
- // it would be cleaner and more maintainble to just be
- // consistently dealing with a compatible display everywhere in
- // the framework.
- mCompatibilityInfo.applyToDisplayMetrics(mMetrics);
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
+ try {
+ synchronized (mAccessLock) {
+ if (false) {
+ Slog.i(TAG, "**** Updating config of " + this + ": old config is "
+ + mConfiguration + " old compat is " + mCompatibilityInfo);
+ Slog.i(TAG, "**** Updating config of " + this + ": new config is "
+ + config + " new compat is " + compat);
+ }
+ if (compat != null) {
+ mCompatibilityInfo = compat;
+ }
+ if (metrics != null) {
+ mMetrics.setTo(metrics);
+ }
+ // NOTE: We should re-arrange this code to create a Display
+ // with the CompatibilityInfo that is used everywhere we deal
+ // with the display in relation to this app, rather than
+ // doing the conversion here. This impl should be okay because
+ // we make sure to return a compatible display in the places
+ // where there are public APIs to retrieve the display... but
+ // it would be cleaner and more maintainble to just be
+ // consistently dealing with a compatible display everywhere in
+ // the framework.
+ mCompatibilityInfo.applyToDisplayMetrics(mMetrics);
- final @Config int configChanges = calcConfigChanges(config);
+ final @Config int configChanges = calcConfigChanges(config);
- LocaleList locales = mConfiguration.getLocales();
- if (locales.isEmpty()) {
- locales = LocaleList.getAdjustedDefault();
- mConfiguration.setLocales(locales);
+ LocaleList locales = mConfiguration.getLocales();
+ if (locales.isEmpty()) {
+ locales = LocaleList.getAdjustedDefault();
+ mConfiguration.setLocales(locales);
+ }
+ if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
+ mMetrics.densityDpi = mConfiguration.densityDpi;
+ mMetrics.density =
+ mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ }
+ mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
+
+ final int width, height;
+ if (mMetrics.widthPixels >= mMetrics.heightPixels) {
+ width = mMetrics.widthPixels;
+ height = mMetrics.heightPixels;
+ } else {
+ //noinspection SuspiciousNameCombination
+ width = mMetrics.heightPixels;
+ //noinspection SuspiciousNameCombination
+ height = mMetrics.widthPixels;
+ }
+
+ final int keyboardHidden;
+ if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
+ && mConfiguration.hardKeyboardHidden
+ == Configuration.HARDKEYBOARDHIDDEN_YES) {
+ keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
+ } else {
+ keyboardHidden = mConfiguration.keyboardHidden;
+ }
+
+ mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
+ adjustLanguageTag(locales.get(0).toLanguageTag()),
+ mConfiguration.orientation,
+ mConfiguration.touchscreen,
+ mConfiguration.densityDpi, mConfiguration.keyboard,
+ keyboardHidden, mConfiguration.navigation, width, height,
+ mConfiguration.smallestScreenWidthDp,
+ mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
+ mConfiguration.screenLayout, mConfiguration.uiMode,
+ Build.VERSION.RESOURCES_SDK_INT);
+
+ if (DEBUG_CONFIG) {
+ Slog.i(TAG, "**** Updating config of " + this + ": final config is "
+ + mConfiguration + " final compat is " + mCompatibilityInfo);
+ }
+
+ mDrawableCache.onConfigurationChange(configChanges);
+ mColorDrawableCache.onConfigurationChange(configChanges);
+ mComplexColorCache.onConfigurationChange(configChanges);
+ mAnimatorCache.onConfigurationChange(configChanges);
+ mStateListAnimatorCache.onConfigurationChange(configChanges);
+
+ flushLayoutCache();
}
- if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
- mMetrics.densityDpi = mConfiguration.densityDpi;
- mMetrics.density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ synchronized (sSync) {
+ if (mPluralRule != null) {
+ mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
+ }
}
- mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
-
- final int width, height;
- if (mMetrics.widthPixels >= mMetrics.heightPixels) {
- width = mMetrics.widthPixels;
- height = mMetrics.heightPixels;
- } else {
- //noinspection SuspiciousNameCombination
- width = mMetrics.heightPixels;
- //noinspection SuspiciousNameCombination
- height = mMetrics.widthPixels;
- }
-
- final int keyboardHidden;
- if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
- && mConfiguration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
- keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
- } else {
- keyboardHidden = mConfiguration.keyboardHidden;
- }
-
- mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
- adjustLanguageTag(locales.get(0).toLanguageTag()),
- mConfiguration.orientation,
- mConfiguration.touchscreen,
- mConfiguration.densityDpi, mConfiguration.keyboard,
- keyboardHidden, mConfiguration.navigation, width, height,
- mConfiguration.smallestScreenWidthDp,
- mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
- mConfiguration.screenLayout, mConfiguration.uiMode,
- Build.VERSION.RESOURCES_SDK_INT);
-
- if (DEBUG_CONFIG) {
- Slog.i(TAG, "**** Updating config of " + this + ": final config is "
- + mConfiguration + " final compat is " + mCompatibilityInfo);
- }
-
- mDrawableCache.onConfigurationChange(configChanges);
- mColorDrawableCache.onConfigurationChange(configChanges);
- mComplexColorCache.onConfigurationChange(configChanges);
- mAnimatorCache.onConfigurationChange(configChanges);
- mStateListAnimatorCache.onConfigurationChange(configChanges);
-
- flushLayoutCache();
- }
- synchronized (sSync) {
- if (mPluralRule != null) {
- mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
- }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 50e7356..0f64b92 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1431,10 +1431,9 @@
* row. The keys should be the column names and the values the
* column values
* @param conflictAlgorithm for insert conflict resolver
- * @return the row ID of the newly inserted row
- * OR the primary key of the existing row if the input param 'conflictAlgorithm' =
- * {@link #CONFLICT_IGNORE}
- * OR -1 if any error
+ * @return the row ID of the newly inserted row OR <code>-1</code> if either the
+ * input parameter <code>conflictAlgorithm</code> = {@link #CONFLICT_IGNORE}
+ * or an error occurred.
*/
public long insertWithOnConflict(String table, String nullColumnHack,
ContentValues initialValues, int conflictAlgorithm) {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 55b107a..175d883 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -676,6 +676,15 @@
return getTotalSwappedOutPss();
}
+ /**
+ * Return true if the kernel is reporting pss swapped out... that is, if
+ * {@link #getSummaryTotalSwapPss()} will return non-0 values.
+ * @hide
+ */
+ public boolean hasSwappedOutPss() {
+ return hasSwappedOutPss;
+ }
+
public int describeContents() {
return 0;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 370cd57..7b4be71 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8875,10 +8875,19 @@
/**
* Whether to enable contacts metadata syncing or not
* The value 1 - enable, 0 - disable
+ *
+ * @removed
*/
+ @Deprecated
public static final String CONTACT_METADATA_SYNC = "contact_metadata_sync";
/**
+ * Whether to enable contacts metadata syncing or not
+ * The value 1 - enable, 0 - disable
+ */
+ public static final String CONTACT_METADATA_SYNC_ENABLED = "contact_metadata_sync_enabled";
+
+ /**
* Whether to enable cellular on boot.
* The value 1 - enable, 0 - disable
* @hide
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index cf783d2..25fe4ff 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1003,56 +1003,63 @@
*/
public static class Ranking {
- /** @hide */
- @IntDef({VISIBILITY_NO_OVERRIDE, IMPORTANCE_UNSPECIFIED, IMPORTANCE_NONE,
- IMPORTANCE_MIN, IMPORTANCE_LOW, IMPORTANCE_DEFAULT, IMPORTANCE_HIGH,
- IMPORTANCE_MAX})
- @Retention(RetentionPolicy.SOURCE)
- public @interface Importance {}
-
/** Value signifying that the user has not expressed a per-app visibility override value.
* @hide */
- public static final int VISIBILITY_NO_OVERRIDE = -1000;
+ public static final int VISIBILITY_NO_OVERRIDE = NotificationManager.VISIBILITY_NO_OVERRIDE;
/**
* Value signifying that the user has not expressed an importance.
*
* This value is for persisting preferences, and should never be associated with
* an actual notification.
+ *
+ * @hide
*/
- public static final int IMPORTANCE_UNSPECIFIED = -1000;
+ public static final int IMPORTANCE_UNSPECIFIED = NotificationManager.IMPORTANCE_UNSPECIFIED;
/**
* A notification with no importance: shows nowhere, is blocked.
+ *
+ * @hide
*/
- public static final int IMPORTANCE_NONE = 0;
+ public static final int IMPORTANCE_NONE = NotificationManager.IMPORTANCE_NONE;
/**
* Min notification importance: only shows in the shade, below the fold.
+ *
+ * @hide
*/
- public static final int IMPORTANCE_MIN = 1;
+ public static final int IMPORTANCE_MIN = NotificationManager.IMPORTANCE_MIN;
/**
* Low notification importance: shows everywhere, but is not intrusive.
+ *
+ * @hide
*/
- public static final int IMPORTANCE_LOW = 2;
+ public static final int IMPORTANCE_LOW = NotificationManager.IMPORTANCE_LOW;
/**
* Default notification importance: shows everywhere, allowed to makes noise,
* but does not visually intrude.
+ *
+ * @hide
*/
- public static final int IMPORTANCE_DEFAULT = 3;
+ public static final int IMPORTANCE_DEFAULT = NotificationManager.IMPORTANCE_DEFAULT;
/**
* Higher notification importance: shows everywhere, allowed to makes noise and peek.
+ *
+ * @hide
*/
- public static final int IMPORTANCE_HIGH = 4;
+ public static final int IMPORTANCE_HIGH = NotificationManager.IMPORTANCE_HIGH;
/**
* Highest notification importance: shows everywhere, allowed to makes noise, peek, and
* use full screen intents.
+ *
+ * @hide
*/
- public static final int IMPORTANCE_MAX = 5;
+ public static final int IMPORTANCE_MAX = NotificationManager.IMPORTANCE_MAX;
private String mKey;
private int mRank = -1;
@@ -1060,7 +1067,7 @@
private boolean mMatchesInterruptionFilter;
private int mVisibilityOverride;
private int mSuppressedVisualEffects;
- private @Importance int mImportance;
+ private @NotificationManager.Importance int mImportance;
private CharSequence mImportanceExplanation;
// System specified group key.
private String mOverrideGroupKey;
@@ -1124,11 +1131,11 @@
/**
* Returns the importance of the notification, which dictates its
- * modes of presentation, see: {@link #IMPORTANCE_DEFAULT}, etc.
+ * modes of presentation, see: {@link NotificationManager#IMPORTANCE_DEFAULT}, etc.
*
* @return the rank of the notification
*/
- public @Importance int getImportance() {
+ public @NotificationManager.Importance int getImportance() {
return mImportance;
}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 1cd2fb0..e650d95 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -252,17 +252,6 @@
}
/**
- * Sets the library directory to use as a search path for vulkan layers.
- *
- * @param libDir A directory that contains vulkan layers
- *
- * @hide
- */
- public static void setLibDir(String libDir) {
- ThreadedRenderer.setupVulkanLayerPath(libDir);
- }
-
- /**
* Creates a hardware renderer using OpenGL.
*
* @param translucent True if the surface is translucent, false otherwise
@@ -980,7 +969,6 @@
}
static native void setupShadersDiskCache(String cacheFile);
- static native void setupVulkanLayerPath(String layerPath);
private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map);
private static native void nSetProcessStatsBuffer(long nativeProxy, int fd);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4e7d1914..77884f6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8740,7 +8740,7 @@
* @param direction The direction of the focus
*/
public void addFocusables(ArrayList<View> views, @FocusDirection int direction) {
- addFocusables(views, direction, FOCUSABLES_TOUCH_MODE);
+ addFocusables(views, direction, isInTouchMode() ? FOCUSABLES_TOUCH_MODE : FOCUSABLES_ALL);
}
/**
@@ -8768,7 +8768,7 @@
return;
}
if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
- && isInTouchMode() && !isFocusableInTouchMode()) {
+ && !isFocusableInTouchMode()) {
return;
}
views.add(this);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 9b74fc0..fe24230 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1803,7 +1803,21 @@
*/
public final void setSurfaceInsets(View view, boolean manual, boolean preservePrevious) {
final int surfaceInset = (int) Math.ceil(view.getZ() * 2);
- surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
+ // Partial workaround for b/28318973. Every inset change causes a freeform window
+ // to jump a little for a few frames. If we never allow surface insets to decrease,
+ // they will stabilize quickly (often from the very beginning, as most windows start
+ // as focused).
+ // TODO(b/22668382) to fix this properly.
+ if (surfaceInset == 0) {
+ // OK to have 0 (this is the case for non-freeform windows).
+ surfaceInsets.set(0, 0, 0, 0);
+ } else {
+ surfaceInsets.set(
+ Math.max(surfaceInset, surfaceInsets.left),
+ Math.max(surfaceInset, surfaceInsets.top),
+ Math.max(surfaceInset, surfaceInsets.right),
+ Math.max(surfaceInset, surfaceInsets.bottom));
+ }
hasManualSurfaceInsets = manual;
preservePreviousSurfaceInsets = preservePrevious;
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 887cc3a..11734d3 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -391,17 +391,34 @@
}
}
+ /**
+ * Remove all roots with specified token.
+ *
+ * @param token app or window token.
+ * @param who name of caller, used in logs.
+ * @param what type of caller, used in logs.
+ */
public void closeAll(IBinder token, String who, String what) {
+ closeAllExceptView(token, null /* view */, who, what);
+ }
+
+ /**
+ * Remove all roots with specified token, except maybe one view.
+ *
+ * @param token app or window token.
+ * @param view view that should be should be preserved along with it's root.
+ * Pass null if everything should be removed.
+ * @param who name of caller, used in logs.
+ * @param what type of caller, used in logs.
+ */
+ public void closeAllExceptView(IBinder token, View view, String who, String what) {
synchronized (mLock) {
int count = mViews.size();
- //Log.i("foo", "Closing all windows of " + token);
for (int i = 0; i < count; i++) {
- //Log.i("foo", "@ " + i + " token " + mParams[i].token
- // + " view " + mRoots[i].getView());
- if (token == null || mParams.get(i).token == token) {
+ if ((view == null || mViews.get(i) != view)
+ && (token == null || mParams.get(i).token == token)) {
ViewRootImpl root = mRoots.get(i);
- //Log.i("foo", "Force closing " + root);
if (who != null) {
WindowLeaked leak = new WindowLeaked(
what + " " + who + " has leaked window "
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index b0d204b..908a99d 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1121,10 +1121,11 @@
* isKeyguardSecure
*
* Return whether the keyguard requires a password to unlock.
+ * @param userId
*
* @return true if in keyguard is secure.
*/
- public boolean isKeyguardSecure();
+ public boolean isKeyguardSecure(int userId);
/**
* Return whether the keyguard is on.
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6e1dff9..28ade80 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2322,7 +2322,8 @@
*
* @param position The position to display
* @param isScrap Array of at least 1 boolean, the first entry will become true if
- * the returned view was taken from the scrap heap, false if otherwise.
+ * the returned view was taken from the "temporary detached" scrap heap, false if
+ * otherwise.
*
* @return A view displaying the data associated with the specified position
*/
@@ -2362,10 +2363,18 @@
// Failed to re-bind the data, return scrap to the heap.
mRecycler.addScrapView(scrapView, position);
} else {
- isScrap[0] = true;
+ if (child.isTemporarilyDetached()) {
+ isScrap[0] = true;
- // Finish the temporary detach started in addScrapView().
- child.dispatchFinishTemporaryDetach();
+ // Finish the temporary detach started in addScrapView().
+ child.dispatchFinishTemporaryDetach();
+ } else {
+ // we set isScrap to "true" only if the view is temporarily detached.
+ // if the view is fully detached, it is as good as a view created by the
+ // adapter
+ isScrap[0] = false;
+ }
+
}
}
@@ -5152,6 +5161,7 @@
fillGap(down);
}
+ mRecycler.fullyDetachScrapViews();
if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
final int childIndex = mSelectedPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
@@ -6861,8 +6871,8 @@
scrapViews = mScrapViews[whichScrap];
}
- victim.dispatchStartTemporaryDetach();
lp.scrappedFromPosition = mFirstActivePosition + i;
+ removeDetachedView(victim, false);
scrapViews.add(victim);
if (hasListener) {
@@ -6871,11 +6881,29 @@
}
}
}
-
pruneScrapViews();
}
/**
+ * At the end of a layout pass, all temp detached views should either be re-attached or
+ * completely detached. This method ensures that any remaining view in the scrap list is
+ * fully detached.
+ */
+ void fullyDetachScrapViews() {
+ final int viewTypeCount = mViewTypeCount;
+ final ArrayList<View>[] scrapViews = mScrapViews;
+ for (int i = 0; i < viewTypeCount; ++i) {
+ final ArrayList<View> scrapPile = scrapViews[i];
+ for (int j = scrapPile.size() - 1; j >= 0; j--) {
+ final View view = scrapPile.get(j);
+ if (view.isTemporarilyDetached()) {
+ removeDetachedView(view, false);
+ }
+ }
+ }
+ }
+
+ /**
* Makes sure that the size of mScrapViews does not exceed the size of
* mActiveViews, which can happen if an adapter does not recycle its
* views. Removes cached transient state views that no longer have
@@ -6888,10 +6916,8 @@
for (int i = 0; i < viewTypeCount; ++i) {
final ArrayList<View> scrapPile = scrapViews[i];
int size = scrapPile.size();
- final int extras = size - maxViews;
- size--;
- for (int j = 0; j < extras; j++) {
- removeDetachedView(scrapPile.remove(size--), false);
+ while (size > maxViews) {
+ scrapPile.remove(--size);
}
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index bb1ffcb..0e04e30 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -54,6 +54,7 @@
import android.widget.RemoteViews.RemoteView;
import java.util.ArrayList;
+import java.util.List;
/*
* Implementation Notes:
@@ -1763,6 +1764,10 @@
// Flush any cached views that did not get reused above
recycleBin.scrapActiveViews();
+ // remove any header/footer that has been temp detached and not re-attached
+ removeUnusedFixedViews(mHeaderViewInfos);
+ removeUnusedFixedViews(mFooterViewInfos);
+
if (sel != null) {
// The current selected item should get focus if items are
// focusable.
@@ -1879,6 +1884,36 @@
}
}
+ @Override
+ boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
+ final boolean result = super.trackMotionScroll(deltaY, incrementalDeltaY);
+ removeUnusedFixedViews(mHeaderViewInfos);
+ removeUnusedFixedViews(mFooterViewInfos);
+ return result;
+ }
+
+ /**
+ * Header and Footer views are not scrapped / recycled like other views but they are still
+ * detached from the ViewGroup. After a layout operation, call this method to remove such views.
+ *
+ * @param infoList The info list to be traversed
+ */
+ private void removeUnusedFixedViews(@Nullable List<FixedViewInfo> infoList) {
+ if (infoList == null) {
+ return;
+ }
+ for (int i = infoList.size() - 1; i >= 0; i--) {
+ final FixedViewInfo fixedViewInfo = infoList.get(i);
+ final View view = fixedViewInfo.view;
+ final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ if (view.getParent() == null && lp != null && lp.recycledHeaderFooter) {
+ removeDetachedView(view, false);
+ lp.recycledHeaderFooter = false;
+ }
+
+ }
+ }
+
/**
* @param child a direct child of this list.
* @return Whether child is a header or footer view.
@@ -3179,6 +3214,9 @@
last = getChildAt(--lastIndex);
}
}
+ recycleBin.fullyDetachScrapViews();
+ removeUnusedFixedViews(mHeaderViewInfos);
+ removeUnusedFixedViews(mFooterViewInfos);
}
private View addViewAbove(View theView, int position) {
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index 1a81d20..d8c7c35 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -36,7 +36,6 @@
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.util.IntArray;
-import android.util.Log;
import android.util.MathUtils;
import android.util.StateSet;
import android.view.KeyEvent;
@@ -48,7 +47,6 @@
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import java.text.NumberFormat;
-import java.util.Arrays;
import java.util.Calendar;
import java.util.Locale;
@@ -59,8 +57,6 @@
* within the specified month.
*/
class SimpleMonthView extends View {
- private static final String LOG_TAG = "SimpleMonthView";
-
private static final int DAYS_IN_WEEK = 7;
private static final int MAX_WEEKS_IN_MONTH = 6;
@@ -71,9 +67,6 @@
private static final int SELECTED_HIGHLIGHT_ALPHA = 0xB0;
- /** Temporary until we figure out why the date gets messed up. */
- private static final boolean DEBUG_WRONG_DATE = true;
-
private final TextPaint mMonthPaint = new TextPaint();
private final TextPaint mDayOfWeekPaint = new TextPaint();
private final TextPaint mDayPaint = new TextPaint();
@@ -195,22 +188,12 @@
}
private void updateDayOfWeekLabels() {
- if (DEBUG_WRONG_DATE) {
- Log.d(LOG_TAG, "enter updateDayOfWeekLabels()", new Exception());
- Log.d(LOG_TAG, "mLocale => " + mLocale);
- Log.d(LOG_TAG, "mWeekStart => " + mWeekStart);
- }
-
// Use tiny (e.g. single-character) weekday names from ICU. The indices
// for this list correspond to Calendar days, e.g. SUNDAY is index 1.
final String[] tinyWeekdayNames = LocaleData.get(mLocale).tinyWeekdayNames;
for (int i = 0; i < DAYS_IN_WEEK; i++) {
mDayOfWeekLabels[i] = tinyWeekdayNames[(mWeekStart + i - 1) % DAYS_IN_WEEK + 1];
}
-
- if (DEBUG_WRONG_DATE) {
- Log.d(LOG_TAG, "mDayOfWeekLabels <= " + Arrays.toString(mDayOfWeekLabels));
- }
}
/**
@@ -773,20 +756,12 @@
* {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY}
*/
public void setFirstDayOfWeek(int weekStart) {
- if (DEBUG_WRONG_DATE) {
- Log.d(LOG_TAG, "enter setFirstDayOfWeek(" + weekStart + ")", new Exception());
- }
-
if (isValidDayOfWeek(weekStart)) {
mWeekStart = weekStart;
} else {
mWeekStart = mCalendar.getFirstDayOfWeek();
}
- if (DEBUG_WRONG_DATE) {
- Log.d(LOG_TAG, "mWeekStart <=" + mWeekStart);
- }
-
updateDayOfWeekLabels();
// Invalidate cached accessibility information.
@@ -812,11 +787,6 @@
*/
void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart,
int enabledDayEnd) {
- if (DEBUG_WRONG_DATE) {
- Log.d(LOG_TAG, "setMonthParams(" + selectedDay + ", " + month + ", " + year + ", "
- + weekStart + ", " + enabledDayStart + ", " + enabledDayEnd + ")");
- }
-
mActivatedDay = selectedDay;
if (isValidMonth(month)) {
@@ -855,14 +825,6 @@
// Invalidate cached accessibility information.
mTouchHelper.invalidateRoot();
invalidate();
-
- if (DEBUG_WRONG_DATE) {
- Log.d(LOG_TAG, "mMonth = " + mMonth);
- Log.d(LOG_TAG, "mDayOfWeekStart = " + mDayOfWeekStart);
- Log.d(LOG_TAG, "mWeekStart = " + mWeekStart);
- Log.d(LOG_TAG, "mDaysInMonth = " + mDaysInMonth);
- Log.d(LOG_TAG, "mToday = " + mToday);
- }
}
private static int getDaysInMonth(int month, int year) {
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index 2f80b86..fb2337e 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -16,6 +16,7 @@
package com.android.internal.app.procstats;
+import android.os.Debug;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -150,7 +151,7 @@
};
// Current version of the parcel format.
- private static final int PARCEL_VERSION = 19;
+ private static final int PARCEL_VERSION = 20;
// In-memory Parcel magic number, used to detect attempts to unmarshall bad data
private static final int MAGIC = 0x50535454;
@@ -174,10 +175,9 @@
String mRuntime;
boolean mRunning;
- public final SparseMappingTable mTableData = new SparseMappingTable();
+ boolean mHasSwappedOutPss;
- int[] mAddLongTable;
- int mAddLongTableSize;
+ public final SparseMappingTable mTableData = new SparseMappingTable();
public final long[] mSysMemUsageArgs = new long[SYS_MEM_USAGE_COUNT];
public final SysMemUsageTable mSysMemUsage = new SysMemUsageTable(mTableData);
@@ -191,6 +191,13 @@
public ProcessStats(boolean running) {
mRunning = running;
reset();
+ if (running) {
+ // If we are actively running, we need to determine whether the system is
+ // collecting swap pss data.
+ Debug.MemoryInfo info = new Debug.MemoryInfo();
+ Debug.getMemoryInfo(android.os.Process.myPid(), info);
+ mHasSwappedOutPss = info.hasSwappedOutPss();
+ }
}
public ProcessStats(Parcel in) {
@@ -281,6 +288,8 @@
}
mTimePeriodEndRealtime += other.mTimePeriodEndRealtime - other.mTimePeriodStartRealtime;
mTimePeriodEndUptime += other.mTimePeriodEndUptime - other.mTimePeriodStartUptime;
+
+ mHasSwappedOutPss |= other.mHasSwappedOutPss;
}
public void addSysMemUsage(long cachedMem, long freeMem, long zramMem, long kernelMem,
@@ -353,8 +362,8 @@
* (double)memTime;
data.sysMemFreeWeight += longs[idx+SYS_MEM_USAGE_FREE_AVERAGE]
* (double)memTime;
- data.sysMemZRamWeight += longs[idx+SYS_MEM_USAGE_ZRAM_AVERAGE]
- * (double)memTime;
+ data.sysMemZRamWeight += longs[idx + SYS_MEM_USAGE_ZRAM_AVERAGE]
+ * (double) memTime;
data.sysMemKernelWeight += longs[idx+SYS_MEM_USAGE_KERNEL_AVERAGE]
* (double)memTime;
data.sysMemNativeWeight += longs[idx+SYS_MEM_USAGE_NATIVE_AVERAGE]
@@ -362,6 +371,7 @@
data.sysMemSamples += longs[idx+SYS_MEM_USAGE_SAMPLE_COUNT];
}
}
+ data.hasSwappedOutPss = mHasSwappedOutPss;
ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
for (int iproc=0; iproc<procMap.size(); iproc++) {
SparseArray<ProcessState> uids = procMap.valueAt(iproc);
@@ -640,6 +650,7 @@
out.writeLong(mTimePeriodStartUptime);
out.writeLong(mTimePeriodEndUptime);
out.writeString(mRuntime);
+ out.writeInt(mHasSwappedOutPss ? 1 : 0);
out.writeInt(mFlags);
mTableData.writeToParcel(out);
@@ -798,6 +809,7 @@
mTimePeriodStartUptime = in.readLong();
mTimePeriodEndUptime = in.readLong();
mRuntime = in.readString();
+ mHasSwappedOutPss = in.readInt() != 0;
mFlags = in.readInt();
mTableData.readFromParcel(in);
readCompactedLongArray(in, version, mMemFactorDurations, mMemFactorDurations.length);
@@ -1344,6 +1356,9 @@
if (partial) {
pw.print(" (partial)");
}
+ if (mHasSwappedOutPss) {
+ pw.print(" (swapped-out-pss)");
+ }
pw.print(' ');
pw.print(mRuntime);
pw.println();
@@ -1429,6 +1444,9 @@
if (partial) {
pw.print(",partial");
}
+ if (mHasSwappedOutPss) {
+ pw.print(",swapped-out-pss");
+ }
pw.println();
pw.print("config,"); pw.println(mRuntime);
for (int ip=0; ip<pkgMap.size(); ip++) {
@@ -1616,6 +1634,7 @@
public double sysMemKernelWeight;
public double sysMemNativeWeight;
public int sysMemSamples;
+ public boolean hasSwappedOutPss;
}
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 2b7afea..5554182 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -549,6 +549,7 @@
private static boolean startSystemServer(String abiList, String socketName)
throws MethodAndArgsCaller, RuntimeException {
long capabilities = posixCapabilitiesAsBits(
+ OsConstants.CAP_IPC_LOCK,
OsConstants.CAP_KILL,
OsConstants.CAP_NET_ADMIN,
OsConstants.CAP_NET_BIND_SERVICE,
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
index d6172db..cf14471 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -246,7 +246,8 @@
int dividerMax = isHorizontalDivision
? mDisplayHeight
: mDisplayWidth;
- mTargets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START, 0.35f));
+ mTargets.add(new SnapTarget(-mDividerSize, -mDividerSize, SnapTarget.FLAG_DISMISS_START,
+ 0.35f));
switch (mSnapMode) {
case SNAP_MODE_16_9:
addRatio16_9Targets(isHorizontalDivision, dividerMax);
@@ -259,7 +260,8 @@
break;
}
int navBarSize = isHorizontalDivision ? mInsets.bottom : mInsets.right;
- mTargets.add(new SnapTarget(dividerMax - navBarSize, SnapTarget.FLAG_DISMISS_END, 0.35f));
+ mTargets.add(new SnapTarget(dividerMax - navBarSize, dividerMax,
+ SnapTarget.FLAG_DISMISS_END, 0.35f));
}
private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition,
@@ -269,13 +271,15 @@
maybeAddTarget(bottomPosition, dividerMax - mInsets.bottom
- (bottomPosition + mDividerSize));
}
+
private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) {
int start = isHorizontalDivision ? mInsets.top : mInsets.left;
int end = isHorizontalDivision
? mDisplayHeight - mInsets.bottom
: mDisplayWidth - mInsets.right;
- int topPosition = (int) (start + mFixedRatio * (end - start)) - mDividerSize / 2;
- int bottomPosition = (int) (start + (1 - mFixedRatio) * (end - start)) - mDividerSize / 2;
+ int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;
+ int topPosition = start + size;
+ int bottomPosition = end - size - mDividerSize;
addNonDismissingTargets(isHorizontalDivision, topPosition, bottomPosition, dividerMax);
}
@@ -301,13 +305,14 @@
*/
private void maybeAddTarget(int position, int smallerSize) {
if (smallerSize >= mMinimalSizeResizableTask) {
- mTargets.add(new SnapTarget(position, SnapTarget.FLAG_NONE));
+ mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
}
}
private void addMiddleTarget(boolean isHorizontalDivision) {
- mTargets.add(new SnapTarget(DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
- mInsets, mDisplayWidth, mDisplayHeight, mDividerSize), SnapTarget.FLAG_NONE));
+ int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
+ mInsets, mDisplayWidth, mDisplayHeight, mDividerSize);
+ mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
}
public SnapTarget getMiddleTarget() {
@@ -370,7 +375,15 @@
/** If the divider reaches this value, the right/bottom task should be dismissed */
public static final int FLAG_DISMISS_END = 2;
+ /** Position of this snap target. The right/bottom edge of the top/left task snaps here. */
public final int position;
+
+ /**
+ * Like {@link #position}, but used to calculate the task bounds which might be different
+ * from the stack bounds.
+ */
+ public final int taskPosition;
+
public final int flag;
/**
@@ -379,12 +392,13 @@
*/
private final float distanceMultiplier;
- public SnapTarget(int position, int flag) {
- this(position, flag, 1f);
+ public SnapTarget(int position, int taskPosition, int flag) {
+ this(position, taskPosition, flag, 1f);
}
- public SnapTarget(int position, int flag, float distanceMultiplier) {
+ public SnapTarget(int position, int taskPosition, int flag, float distanceMultiplier) {
this.position = position;
+ this.taskPosition = taskPosition;
this.flag = flag;
this.distanceMultiplier = distanceMultiplier;
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 4738f5e..6989654 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -22,6 +22,7 @@
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
@@ -30,7 +31,6 @@
import android.view.accessibility.AccessibilityEvent;
import android.widget.ActionMenuView;
import android.widget.ForwardingListener;
-import android.widget.ListPopupWindow;
import android.widget.TextView;
import android.widget.Toast;
@@ -86,6 +86,7 @@
setOnLongClickListener(this);
mSavedPaddingLeft = -1;
+ setSaveEnabled(false);
}
@Override
@@ -348,6 +349,13 @@
}
}
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ // This might get called with the state of ActionView since it shares the same ID with
+ // ActionMenuItemView. Do not restore this state as ActionMenuItemView never saved it.
+ super.onRestoreInstanceState(null);
+ }
+
public static abstract class PopupCallback {
public abstract ShowableListMenu getPopup();
}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 986a5fd..dd66b24 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -34,6 +34,7 @@
com_google_android_gles_jni_EGLImpl.cpp \
com_google_android_gles_jni_GLImpl.cpp.arm \
android_app_Activity.cpp \
+ android_app_ApplicationLoaders.cpp \
android_app_NativeActivity.cpp \
android_app_admin_SecurityLog.cpp \
android_opengl_EGL14.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index c2273d6..315887d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -181,6 +181,7 @@
extern int register_android_app_backup_FullBackup(JNIEnv *env);
extern int register_android_app_Activity(JNIEnv *env);
extern int register_android_app_ActivityThread(JNIEnv *env);
+extern int register_android_app_ApplicationLoaders(JNIEnv *env);
extern int register_android_app_NativeActivity(JNIEnv *env);
extern int register_android_media_RemoteDisplay(JNIEnv *env);
extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
@@ -1389,6 +1390,7 @@
REG_JNI(register_android_app_backup_FullBackup),
REG_JNI(register_android_app_Activity),
REG_JNI(register_android_app_ActivityThread),
+ REG_JNI(register_android_app_ApplicationLoaders),
REG_JNI(register_android_app_NativeActivity),
REG_JNI(register_android_util_jar_StrictJarFile),
REG_JNI(register_android_view_InputChannel),
diff --git a/core/jni/android_app_ApplicationLoaders.cpp b/core/jni/android_app_ApplicationLoaders.cpp
new file mode 100644
index 0000000..24ee959
--- /dev/null
+++ b/core/jni/android_app_ApplicationLoaders.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright 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 <nativehelper/ScopedUtfChars.h>
+#include <nativeloader/native_loader.h>
+#include <vulkan/vulkan_loader_data.h>
+
+#include "core_jni_helpers.h"
+
+static void setupVulkanLayerPath_native(JNIEnv* env, jobject clazz,
+ jobject classLoader, jstring librarySearchPath) {
+ ScopedUtfChars layerPathChars(env, librarySearchPath);
+ vulkan::LoaderData& loader_data = vulkan::LoaderData::GetInstance();
+ loader_data.layer_path = layerPathChars.c_str();
+ loader_data.app_namespace = android::FindNamespaceByClassLoader(env, classLoader);
+}
+
+static const JNINativeMethod g_methods[] = {
+ { "setupVulkanLayerPath", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
+ reinterpret_cast<void*>(setupVulkanLayerPath_native) },
+};
+
+static const char* const kApplicationLoadersName = "android/app/ApplicationLoaders";
+
+namespace android
+{
+
+int register_android_app_ApplicationLoaders(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, kApplicationLoadersName, g_methods, NELEM(g_methods));
+}
+
+} // namespace android
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 650a0fc..5b4bfe9 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -28,7 +28,6 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/egl_cache.h>
-#include <vulkan/vulkan_loader_data.h>
#include <utils/Looper.h>
#include <utils/RefBase.h>
@@ -50,7 +49,6 @@
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
#include <renderthread/RenderThread.h>
-#include <Vector.h>
namespace android {
@@ -718,18 +716,6 @@
}
// ----------------------------------------------------------------------------
-// Layers
-// ----------------------------------------------------------------------------
-
-static void android_view_ThreadedRenderer_setupVulkanLayerPath(JNIEnv* env, jobject clazz,
- jstring layerPath) {
-
- const char* layerArray = env->GetStringUTFChars(layerPath, NULL);
- vulkan::LoaderData::GetInstance().layer_path = layerArray;
- env->ReleaseStringUTFChars(layerPath, layerArray);
-}
-
-// ----------------------------------------------------------------------------
// JNI Glue
// ----------------------------------------------------------------------------
@@ -771,8 +757,6 @@
{ "nDumpProfileData", "([BLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileData },
{ "setupShadersDiskCache", "(Ljava/lang/String;)V",
(void*) android_view_ThreadedRenderer_setupShadersDiskCache },
- { "setupVulkanLayerPath", "(Ljava/lang/String;)V",
- (void*) android_view_ThreadedRenderer_setupVulkanLayerPath },
{ "nAddRenderNode", "(JJZ)V", (void*) android_view_ThreadedRenderer_addRenderNode},
{ "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
{ "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
diff --git a/core/res/res/drawable-hdpi/text_select_handle_left.png b/core/res/res/drawable-hdpi/text_select_handle_left.png
deleted file mode 100644
index d2ed06d..0000000
--- a/core/res/res/drawable-hdpi/text_select_handle_left.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/text_select_handle_middle.png b/core/res/res/drawable-hdpi/text_select_handle_middle.png
deleted file mode 100644
index be2dc68..0000000
--- a/core/res/res/drawable-hdpi/text_select_handle_middle.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/text_select_handle_right.png b/core/res/res/drawable-hdpi/text_select_handle_right.png
deleted file mode 100644
index e419249..0000000
--- a/core/res/res/drawable-hdpi/text_select_handle_right.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-ldpi/text_select_handle_left.png b/core/res/res/drawable-ldpi/text_select_handle_left.png
deleted file mode 100644
index bded42c..0000000
--- a/core/res/res/drawable-ldpi/text_select_handle_left.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-ldpi/text_select_handle_middle.png b/core/res/res/drawable-ldpi/text_select_handle_middle.png
deleted file mode 100644
index c1d8d17..0000000
--- a/core/res/res/drawable-ldpi/text_select_handle_middle.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-ldpi/text_select_handle_right.png b/core/res/res/drawable-ldpi/text_select_handle_right.png
deleted file mode 100644
index aae0b40..0000000
--- a/core/res/res/drawable-ldpi/text_select_handle_right.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/text_select_handle_left.png b/core/res/res/drawable-mdpi/text_select_handle_left.png
deleted file mode 100644
index 750cdea..0000000
--- a/core/res/res/drawable-mdpi/text_select_handle_left.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/text_select_handle_middle.png b/core/res/res/drawable-mdpi/text_select_handle_middle.png
deleted file mode 100644
index 3cdca90..0000000
--- a/core/res/res/drawable-mdpi/text_select_handle_middle.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/text_select_handle_right.png b/core/res/res/drawable-mdpi/text_select_handle_right.png
deleted file mode 100644
index fc3d144..0000000
--- a/core/res/res/drawable-mdpi/text_select_handle_right.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/text_select_handle_left.png b/core/res/res/drawable-xhdpi/text_select_handle_left.png
deleted file mode 100644
index 98d10c9..0000000
--- a/core/res/res/drawable-xhdpi/text_select_handle_left.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/text_select_handle_middle.png b/core/res/res/drawable-xhdpi/text_select_handle_middle.png
deleted file mode 100644
index 058b30b..0000000
--- a/core/res/res/drawable-xhdpi/text_select_handle_middle.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/text_select_handle_right.png b/core/res/res/drawable-xhdpi/text_select_handle_right.png
deleted file mode 100644
index b3a0c9f..0000000
--- a/core/res/res/drawable-xhdpi/text_select_handle_right.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/text_select_handle_left.png b/core/res/res/drawable-xxhdpi/text_select_handle_left.png
deleted file mode 100644
index 8497601..0000000
--- a/core/res/res/drawable-xxhdpi/text_select_handle_left.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/text_select_handle_middle.png b/core/res/res/drawable-xxhdpi/text_select_handle_middle.png
deleted file mode 100644
index 7b74f66..0000000
--- a/core/res/res/drawable-xxhdpi/text_select_handle_middle.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/text_select_handle_right.png b/core/res/res/drawable-xxhdpi/text_select_handle_right.png
deleted file mode 100644
index 25e0780..0000000
--- a/core/res/res/drawable-xxhdpi/text_select_handle_right.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index c083a5e..30e23f5 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -81,8 +81,6 @@
<item>@drawable/edit_text_holo_dark</item>
<item>@drawable/text_cursor_holo_light</item>
<item>@drawable/text_cursor_holo_dark</item>
- <item>@drawable/text_select_handle_left</item>
- <item>@drawable/text_select_handle_right</item>
<item>@drawable/text_edit_paste_window</item>
<item>@drawable/expander_close_holo_dark</item>
<item>@drawable/expander_close_holo_light</item>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2a83d88..e64d905 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2476,4 +2476,9 @@
<!-- True if the device supports persisting security logs across reboots. -->
<bool name="config_supportPreRebootSecurityLogs">false</bool>
+
+ <!-- Default files to pin via Pinner Service -->
+ <string-array translatable="false" name="config_defaultPinnerServiceFiles">
+ </string-array>
+
</resources>
diff --git a/core/res/res/values/styles_holo.xml b/core/res/res/values/styles_holo.xml
index fdf9e31..8ca12ae 100644
--- a/core/res/res/values/styles_holo.xml
+++ b/core/res/res/values/styles_holo.xml
@@ -1172,7 +1172,8 @@
<style name="Widget.Holo.Light.FastScroll" parent="Widget.Holo.FastScroll" />
- <style name="Widget.Holo.SuggestionItem" parent="TextAppearance.Holo.Medium">
+ <style name="Widget.Holo.SuggestionItem">
+ <item name="textAppearance">@android:style/TextAppearance.Holo.Medium</item>
<item name="background">@color/white</item>
<item name="drawablePadding">8dip</item>
<item name="gravity">start|center_vertical</item>
@@ -1188,9 +1189,9 @@
<item name="textColor">@color/black</item>
</style>
- <style name="TextAppearance.Holo.SuggestionHighlight" parent="TextAppearance.SuggestionHighlight" />
+ <style name="TextAppearance.Holo.SuggestionHighlight" parent="@android:style/TextAppearance.SuggestionHighlight" />
- <style name="Widget.Holo.SuggestionButton" parent="Widget.Holo.SuggestionItem">
+ <style name="Widget.Holo.SuggestionButton" parent="@android:style/Widget.Holo.SuggestionItem">
<item name="background">#E9E9E9</item>
<item name="textColor">@color/black</item>
</style>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 6752e3c..3ed8daa 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -1001,7 +1001,8 @@
<item name="contentDescription">@string/media_route_button_content_description</item>
</style>
- <style name="Widget.Material.SuggestionItem" parent="@android:style/TextAppearance.Material.Body1">
+ <style name="Widget.Material.SuggestionItem">
+ <item name="textAppearance">@android:style/TextAppearance.Material.Body1</item>
<item name="textColor">?attr/textColorSecondary</item>
<item name="drawablePadding">8dip</item>
<item name="gravity">start|center_vertical</item>
@@ -1016,11 +1017,12 @@
<item name="textSize">14sp</item>
</style>
- <style name="TextAppearance.Material.TextSuggestionHighlight" parent="Widget.Material.SuggestionItem">
+ <style name="TextAppearance.Material.TextSuggestionHighlight">
<item name="textColor">?attr/textColorPrimary</item>
</style>
- <style name="Widget.Material.SuggestionButton" parent="@android:style/TextAppearance.Material.Button">
+ <style name="Widget.Material.SuggestionButton">
+ <item name="textAppearance">@android:style/TextAppearance.Material.Button</item>
<item name="textColor">?attr/colorAccent</item>
<item name="drawablePadding">8dip</item>
<item name="gravity">start|center_vertical</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 29818df..ac36a2f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1232,9 +1232,6 @@
<java-symbol type="drawable" name="tab_bottom_right" />
<java-symbol type="drawable" name="tab_bottom_right_v4" />
<java-symbol type="drawable" name="tab_indicator_v4" />
- <java-symbol type="drawable" name="text_select_handle_left" />
- <java-symbol type="drawable" name="text_select_handle_middle" />
- <java-symbol type="drawable" name="text_select_handle_right" />
<java-symbol type="drawable" name="unknown_image" />
<java-symbol type="drawable" name="unlock_default" />
<java-symbol type="drawable" name="unlock_halo" />
@@ -2590,4 +2587,8 @@
<java-symbol type="string" name="config_tvRemoteServicePackage" />
<java-symbol type="bool" name="config_supportPreRebootSecurityLogs" />
+
+ <!-- Pinner Service -->
+ <java-symbol type="array" name="config_defaultPinnerServiceFiles" />
+
</resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index bf7718e..998eea5 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -248,9 +248,9 @@
<item name="scrollbarTrackVertical">@null</item>
<!-- Text selection handle attributes -->
- <item name="textSelectHandleLeft">@drawable/text_select_handle_left</item>
- <item name="textSelectHandleRight">@drawable/text_select_handle_right</item>
- <item name="textSelectHandle">@drawable/text_select_handle_middle</item>
+ <item name="textSelectHandleLeft">@drawable/text_select_handle_left_material</item>
+ <item name="textSelectHandleRight">@drawable/text_select_handle_right_material</item>
+ <item name="textSelectHandle">@drawable/text_select_handle_middle_material</item>
<item name="textSelectHandleWindowStyle">@style/Widget.TextSelectHandle</item>
<item name="textEditPasteWindowLayout">@layout/text_edit_paste_window</item>
<item name="textEditNoPasteWindowLayout">@layout/text_edit_no_paste_window</item>
diff --git a/core/res/res/values/themes_holo.xml b/core/res/res/values/themes_holo.xml
index 677051a..8f73479 100644
--- a/core/res/res/values/themes_holo.xml
+++ b/core/res/res/values/themes_holo.xml
@@ -247,9 +247,9 @@
<item name="scrollbarTrackVertical">@null</item>
<!-- Text selection handle attributes -->
- <item name="textSelectHandleLeft">@drawable/text_select_handle_left</item>
- <item name="textSelectHandleRight">@drawable/text_select_handle_right</item>
- <item name="textSelectHandle">@drawable/text_select_handle_middle</item>
+ <item name="textSelectHandleLeft">@drawable/text_select_handle_left_material</item>
+ <item name="textSelectHandleRight">@drawable/text_select_handle_right_material</item>
+ <item name="textSelectHandle">@drawable/text_select_handle_middle_material</item>
<item name="textSelectHandleWindowStyle">@style/Widget.Holo.TextSelectHandle</item>
<item name="textCursorDrawable">@drawable/text_cursor_holo_dark</item>
@@ -588,9 +588,9 @@
<item name="scrollbarTrackVertical">@null</item>
<!-- Text selection handle attributes -->
- <item name="textSelectHandleLeft">@drawable/text_select_handle_left</item>
- <item name="textSelectHandleRight">@drawable/text_select_handle_right</item>
- <item name="textSelectHandle">@drawable/text_select_handle_middle</item>
+ <item name="textSelectHandleLeft">@drawable/text_select_handle_left_material</item>
+ <item name="textSelectHandleRight">@drawable/text_select_handle_right_material</item>
+ <item name="textSelectHandle">@drawable/text_select_handle_middle_material</item>
<item name="textSelectHandleWindowStyle">@style/Widget.Holo.TextSelectHandle</item>
<item name="textCursorDrawable">@drawable/text_cursor_holo_light</item>
diff --git a/docs/html-intl/intl/es/preview/setup-sdk.jd b/docs/html-intl/intl/es/preview/setup-sdk.jd
index 4ccc4f8..01c715b 100644
--- a/docs/html-intl/intl/es/preview/setup-sdk.jd
+++ b/docs/html-intl/intl/es/preview/setup-sdk.jd
@@ -42,8 +42,6 @@
según lo descrito a continuación.
</p>
-<iframe width="400" height="225" src="//www.youtube.com/embed/SBbWGxXCMqQ?autohide=1&showinfo=0" frameborder="0" allowfullscreen="" style="float: right; margin: 0 0 20px 20px;"></iframe>
-
<p>Android Studio 2.1 actualmente se encuentra disponible como muestra en el canal de
versiones Canary. Si ya
cuentas con Android Studio y no deseas realizar la actualización al canal Canary, puedes
diff --git a/docs/html-intl/intl/in/preview/setup-sdk.jd b/docs/html-intl/intl/in/preview/setup-sdk.jd
index 5187e99..a842931 100644
--- a/docs/html-intl/intl/in/preview/setup-sdk.jd
+++ b/docs/html-intl/intl/in/preview/setup-sdk.jd
@@ -42,8 +42,6 @@
seperti dijelaskan di bawah ini.
</p>
-<iframe width="400" height="225" src="//www.youtube.com/embed/SBbWGxXCMqQ?autohide=1&showinfo=0" frameborder="0" allowfullscreen="" style="float: right; margin: 0 0 20px 20px;"></iframe>
-
<p>Android Studio 2.1 saat ini tersedia sebagai preview di
Canary Release Channel. Jika Anda sudah
memiliki Android Studio dan tidak ingin memperbarui ke Canary Channel, Anda bisa
diff --git a/docs/html-intl/intl/ja/preview/setup-sdk.jd b/docs/html-intl/intl/ja/preview/setup-sdk.jd
index 8f3cd06..fd5faba 100644
--- a/docs/html-intl/intl/ja/preview/setup-sdk.jd
+++ b/docs/html-intl/intl/ja/preview/setup-sdk.jd
@@ -42,8 +42,6 @@
</p>
-<iframe width="400" height="225" src="//www.youtube.com/embed/SBbWGxXCMqQ?autohide=1&showinfo=0" frameborder="0" allowfullscreen="" style="float: right; margin: 0 0 20px 20px;"></iframe>
-
<p>現在、Android Studio 2.1 は、先行リリース チャンネルでプレビューとして入手できます。Android Studio を既に入手していて、先行チャンネル版にアップデートしない場合は、Android Studio 2.1 を個別のインストールとしてダウンロードして、Android N での開発に使用することにより、Android Studio のプライマリ環境に影響を及ぼさないようにすることができます。
diff --git a/docs/html-intl/intl/ko/preview/setup-sdk.jd b/docs/html-intl/intl/ko/preview/setup-sdk.jd
index 91c68a6..adb9edf 100644
--- a/docs/html-intl/intl/ko/preview/setup-sdk.jd
+++ b/docs/html-intl/intl/ko/preview/setup-sdk.jd
@@ -42,8 +42,6 @@
업데이트해야 합니다.
</p>
-<iframe width="400" height="225" src="//www.youtube.com/embed/SBbWGxXCMqQ?autohide=1&showinfo=0" frameborder="0" allowfullscreen="" style="float: right; margin: 0 0 20px 20px;"></iframe>
-
<p>현재 Android Studio 2.1은 Canary 릴리스 채널에서 프리뷰로
제공됩니다.
Android Studio가 이미 있고 Canary Channel로 업데이트하지 않으려면,
diff --git a/docs/html-intl/intl/pt-br/preview/setup-sdk.jd b/docs/html-intl/intl/pt-br/preview/setup-sdk.jd
index 4766a88..4159c25 100644
--- a/docs/html-intl/intl/pt-br/preview/setup-sdk.jd
+++ b/docs/html-intl/intl/pt-br/preview/setup-sdk.jd
@@ -42,8 +42,6 @@
conforme é descrito abaixo.
</p>
-<iframe width="400" height="225" src="//www.youtube.com/embed/SBbWGxXCMqQ?autohide=1&showinfo=0" frameborder="0" allowfullscreen="" style="float: right; margin: 0 0 20px 20px;"></iframe>
-
<p>No momento, o Android Studio 2.1 está disponível como prévia no canal da versão canary
. Se já tiver
o Android Studio e não quiser atualizar para o canal canary, poderá
diff --git a/docs/html-intl/intl/ru/preview/setup-sdk.jd b/docs/html-intl/intl/ru/preview/setup-sdk.jd
index 1fcbc0c..3838700 100644
--- a/docs/html-intl/intl/ru/preview/setup-sdk.jd
+++ b/docs/html-intl/intl/ru/preview/setup-sdk.jd
@@ -42,8 +42,6 @@
как описано ниже.
</p>
-<iframe width="400" height="225" src="//www.youtube.com/embed/SBbWGxXCMqQ?autohide=1&showinfo=0" frameborder="0" allowfullscreen="" style="float: right; margin: 0 0 20px 20px;"></iframe>
-
<p>Предварительная версия Android Studio 2.1 в настоящее время доступна на канале обновлений Canary.
Если у вас уже есть Android Studio
и вы не хотите обновлять ее до версии канала Canary, вы можете загрузить
diff --git a/docs/html-intl/intl/vi/preview/setup-sdk.jd b/docs/html-intl/intl/vi/preview/setup-sdk.jd
index fca5331..2e3a18b 100644
--- a/docs/html-intl/intl/vi/preview/setup-sdk.jd
+++ b/docs/html-intl/intl/vi/preview/setup-sdk.jd
@@ -42,8 +42,6 @@
như mô tả dưới đây.
</p>
-<iframe width="400" height="225" src="//www.youtube.com/embed/SBbWGxXCMqQ?autohide=1&showinfo=0" frameborder="0" allowfullscreen="" style="float: right; margin: 0 0 20px 20px;"></iframe>
-
<p>Android Studio 2.1 hiện đang có dưới dạng bản xem trước trong kênh
phát hành Canary. Nếu bạn đã
có Android Studio và không muốn cập nhật lên kênh canary thì bạn có thể
diff --git a/docs/html-intl/intl/zh-cn/preview/setup-sdk.jd b/docs/html-intl/intl/zh-cn/preview/setup-sdk.jd
index 69fe250..216d464 100644
--- a/docs/html-intl/intl/zh-cn/preview/setup-sdk.jd
+++ b/docs/html-intl/intl/zh-cn/preview/setup-sdk.jd
@@ -42,8 +42,6 @@
</p>
-<iframe width="400" height="225" src="//www.youtube.com/embed/SBbWGxXCMqQ?autohide=1&showinfo=0" frameborder="0" allowfullscreen="" style="float: right; margin: 0 0 20px 20px;"></iframe>
-
<p>在 Canary 发布渠道中,Android Studio 2.1 当前可以预览版的形式提供。如果您已拥有 Android Studio 且不需要更新到 Canary 发布渠道,您可通过单独安装的形式下载 Android Studio 2.1,并使用它在 Android N 中进行开发,从而使您的主要 Android Studio 环境不受影响。
diff --git a/docs/html-intl/intl/zh-tw/preview/setup-sdk.jd b/docs/html-intl/intl/zh-tw/preview/setup-sdk.jd
index a5ce556b..d2a9d9e 100644
--- a/docs/html-intl/intl/zh-tw/preview/setup-sdk.jd
+++ b/docs/html-intl/intl/zh-tw/preview/setup-sdk.jd
@@ -42,8 +42,6 @@
</p>
-<iframe width="400" height="225" src="//www.youtube.com/embed/SBbWGxXCMqQ?autohide=1&showinfo=0" frameborder="0" allowfullscreen="" style="float: right; margin: 0 0 20px 20px;"></iframe>
-
<p>Android Studio 2.1 目前在早期測試發行管道中是以預覽版形式提供。如果您已經有 Android Studio 但不想要更新到早期測試管道,您可以下載 Android Studio 2.1 並另行安裝,並使用它來針對 Android N 進行開發,這樣並不會影響您的主要 Android Studio 環境。
diff --git a/docs/html/_redirects.yaml b/docs/html/_redirects.yaml
index d6e6403..a5a4613 100644
--- a/docs/html/_redirects.yaml
+++ b/docs/html/_redirects.yaml
@@ -837,21 +837,21 @@
to: /studio/run/index.html#instant-run
- from: /reference/org/apache/http/...
to: /about/versions/marshmallow/android-6.0-changes.html#behavior-apache-http-client
-- from: /shareables/
- to: https://commondatastorage.googleapis.com/androiddevelopers/shareables/
-- from: /downloads/
- to: https://commondatastorage.googleapis.com/androiddevelopers/
+- from: /shareables/...
+ to: https://commondatastorage.googleapis.com/androiddevelopers/shareables/...
+- from: /downloads/...
+ to: https://commondatastorage.googleapis.com/androiddevelopers/...
# Redirects for the new [dac]/topic/libraries/ area
-- from: /tools/support-library
- to: /topic/libraries/support-library
+- from: /tools/support-library/...
+ to: /topic/libraries/support-library/...
- from: /tools/data-binding/...
- to: /topic/libraries/data-binding
+ to: /topic/libraries/data-binding/index.html
-- from: /tools/testing-support-library
- to: /topic/libraries/testing-support-library
+- from: /tools/testing-support-library/...
+ to: /topic/libraries/testing-support-library/...
# GCM redirects
- from: /reference/com/google/...
@@ -1148,4 +1148,4 @@
- from: /bgopt
to: /preview/features/background-optimization.html
- from: /bgopt/
- to: /preview/features/background-optimization.html
\ No newline at end of file
+ to: /preview/features/background-optimization.html
diff --git a/docs/html/develop/index.jd b/docs/html/develop/index.jd
index de38f3d..6835beb 100644
--- a/docs/html/develop/index.jd
+++ b/docs/html/develop/index.jd
@@ -9,7 +9,7 @@
excludeFromSuggestions=true
@jd:body
-<section class="dac-expand dac-hero dac-section-light" style="background:#FFE57F">
+<section class="dac-expand dac-hero dac-section-light">
<div class="wrap">
<div class="cols dac-hero-content">
<div class="col-1of2 col-push-1of2 dac-hero-figure">
@@ -19,14 +19,14 @@
frameborder="0" allowfullscreen=""
style="float: right;"></iframe>
-->
- <a href="{@docRoot}sdk/index.html">
+ <a href="{@docRoot}studio/index.html">
<img class="dac-hero-image" src="{@docRoot}images/tools/studio/studio-feature-instant-run_2x.png" />
</a>
</div>
<div class="col-1of2 col-pull-1of2">
<h1 class="dac-hero-title">
- <a style="color:inherit" href="{@docRoot}sdk/index.html">
- Android Studio 2.0,<br>now available!</a></h1>
+ <a style="color:inherit" href="{@docRoot}studio/index.html">
+ Android Studio 2.1,<br>now available!</a></h1>
<p class="dac-hero-description">
The latest version of Android Studio is the biggest update yet.
It includes new features like <strong>Instant Run</strong>, which
@@ -35,7 +35,7 @@
</p>
<div class="cols">
<div class="col-1of2">
- <a class="dac-hero-cta" href="{@docRoot}sdk/index.html">
+ <a class="dac-hero-cta" href="{@docRoot}studio/index.html">
<span class="dac-sprite dac-auto-chevron"></span>
Get Android Studio
</a><br>
diff --git a/docs/html/distribute/essentials/_book.yaml b/docs/html/distribute/essentials/_book.yaml
index 6a2c8f5..222cb36 100644
--- a/docs/html/distribute/essentials/_book.yaml
+++ b/docs/html/distribute/essentials/_book.yaml
@@ -20,6 +20,9 @@
- title: Auto App Quality
path: /distribute/essentials/quality/auto.html
+- title: Building for Billions
+ path: /distribute/essentials/quality/billions.html
+
- title: Launch Checklist
path: /distribute/tools/launch-checklist.html
path_attributes:
diff --git a/docs/html/distribute/essentials/essentials_toc.cs b/docs/html/distribute/essentials/essentials_toc.cs
index a78252d..374f338 100644
--- a/docs/html/distribute/essentials/essentials_toc.cs
+++ b/docs/html/distribute/essentials/essentials_toc.cs
@@ -29,6 +29,12 @@
</a>
</div>
</li>
+ <li class="nav-section">
+ <div class="nav-section-header empty" style="font-weight:normal"><a href="<?cs var:toroot?>distribute/essentials/quality/billions.html">
+ <span class="en">Building for Billions</span>
+ </a>
+ </div>
+ </li>
<li class="nav-section">
<div class="nav-section empty" style="font-weight:normal"><a href="<?cs var:toroot?>distribute/tools/launch-checklist.html" zh-cn-lang="发布检查清单">
diff --git a/docs/html/distribute/essentials/quality/billions.jd b/docs/html/distribute/essentials/quality/billions.jd
new file mode 100644
index 0000000..7042143
--- /dev/null
+++ b/docs/html/distribute/essentials/quality/billions.jd
@@ -0,0 +1,788 @@
+page.title=Building for Billions
+page.metaDescription=Best practices on how to optimize Android apps for low- and no-bandwidth and low-cost devices.
+page.image=/distribute/images/billions-guidelines.png
+
+@jd:body
+
+<!-- table of contents -->
+<div id="qv-wrapper"><div id="qv">
+<h2><a href="#connectivity">Connectivity</a></h2>
+ <ol>
+ <li><a href="#images">Optimize images</a></li>
+ <li><a href="#network">Optimize networking</a></li>
+ <li><a href="#transfer">Fine-tune data transfer</a></li>
+ </ol>
+<h2><a href="#capability">Device Capability</a></h2>
+ <ol>
+ <li><a href="#screens">Support varying screen sizes</a></li>
+ <li><a href="#compatibility">Backward compatibility</a></li>
+ <li><a href="#memory">Efficient memory usage</a></li>
+ </ol>
+
+<h2><a href="#cost">Data Cost</a></h2>
+ <ol>
+ <li><a href="#appsize">Reduce app size</a></li>
+ <li><a href="#configurablenetwork">Offer configurable network usage</a></li>
+ </ol>
+
+<h2><a href="#consumption">Battery Consumption</a></h2>
+ <ol>
+ <li><a href="#consumption-reduce">Reduce battery consumption</a></li>
+ <li><a href="#consumption-benchmark">Benchmark battery usage</a></li>
+ </ol>
+
+<h2><a href="#contentsection">Content</a></h2>
+ <ol>
+ <li><a href="#content-responsive">Fast and responsive UI</a></li>
+ <li><a href="#ui">UI Best practices</a></li>
+ <li><a href="#localization">Localization</a></li>
+ </ol>
+</div>
+</div>
+
+<!-- intro -->
+<p>Internet use—and smartphone penetration—is growing fastest in markets with
+ low, intermittent, or expensive connectivity. Successful apps in these
+ markets need to perform across a variety of speeds and devices, as well as
+ conserve and share information about battery and data consumption.</p>
+
+<p>To help you address these important considerations, we’ve compiled the
+ following checklist. These do not follow a particular order, and as
+ always it's a good idea to research particularities of any market or country
+ you're targeting.
+</p>
+
+<!-- connectivity -->
+<div class="headerLine">
+ <h2 id="connectivity">Connectivity</h2>
+</div>
+
+<p>Over half of the users in the world still experience your app over 2G
+ connections. To improve their experience, optimize for no- and low-connection
+ speeds. For offline and slow connections: store data, queue requests, and handle
+ images for optimal performance.
+</p>
+
+<h3 id="images">Optimize images</h3>
+<h4 id="images-format">Serve WebP images</h4>
+ <ul>
+ <li>Serve <a
+ href="https://developers.google.com/speed/webp/">WebP</a> files over the
+ network. WebP reduces image load times, saves network bandwidth, and often
+ results in smaller file sizes than its PNG and JPG counterparts, with at
+ least the same image quality. Even at lossy settings, WebP can produce a
+ nearly identical image. Android has had lossy <a
+ href="{@docRoot}guide/appendix/media-formats.html">WebP support</a> since
+ Android 4.0 (API level 14: Ice Cream Sandwich) and support for lossless /
+ transparent WebP since Android 4.2 (API level 17: Jelly Bean).</li>
+ </ul>
+<h4 id="images-sizing">Dynamic image sizing</h4>
+ <ul>
+ <li>Have your apps request images at the targeted rendering size, and have
+ your server provide those images to fit; the target rendering size will
+ vary based on device specifications. Doing this minimizes the network
+ overhead and reduces the amount of memory needed to hold each image,
+ resulting in improved performance and user satisfaction.</li>
+ <li>Your user experience degrades when users are waiting for images to
+ download. Using appropriate image sizes helps to address these issues.
+ Consider making image size requests based on network type or network
+ quality; this size could be smaller than the target rendering size.</li>
+ <li>Dynamic placeholders like <a
+ href="{@docRoot}reference/android/support/v7/graphics/Palette.html">
+ pre-computed palette values</a> or low-resolution thumbnails can improve
+ the user experience while the image is being fetched.</li>
+ </ul>
+<h4 id="images-libraries">Use image loading libraries</h4>
+ <ul>
+ <li>Your app should not have to fetch any image more than once. Image
+ loading libraries such as <a class="external-link"
+ href="https://github.com/bumptech/glide">Glide</a> and <a
+ class="external-link" href="http://square.github.io/picasso/">Picasso</a>
+ fetch the image, cache it, and provide hooks into your Views to show
+ placeholder images until the actual images are ready. Because images are
+ cached, these libraries return the local copy the next time they are
+ requested.</li>
+ <li>Image-loading libraries manage their cache, holding onto the most recent
+ images so that your app storage doesn’t grow indefinitely.</li>
+ </ul>
+
+<h3 id="network">Optimize networking</h3>
+<h4 id="network-offline">Make your app usable offline</h4>
+ <ul>
+ <li>In places like subways, planes, elevators, and parking garages, it is
+ common for devices to lose network connectivity. Creating a useful offline
+ state results in users being able to interact with the app at all times, by
+ presenting cached information. Ensure that your app is usable offline or
+ when network connectivity is poor by storing data locally, caching data,
+ and queuing outbound requests for when connectivity is restored.</li>
+ <li>Where possible, apps should not notify users that connectivity has
+ been lost. It is only when the user performs an operation where connectivity
+ is essential that the user needs to be notified.</li>
+ <li>When a device lacks connectivity, your app should batch up network
+ requests—on behalf of the user—that can be executed when
+ connectivity is restored. An example of this is an email client that allows
+ users to compose, send, read, move, and delete existing mails even when the
+ device is offline. These operations can be cached and executed when
+ connectivity is restored. In doing so, the app is able to provide a similar
+ user experience whether the device is online or offline.</li>
+ </ul>
+<h4 id="network-arch">Use GcmNetworkManager and/or Content Providers</h4>
+ <ul>
+ <li>Ensure that your app stores all data on disk via a database or similar
+ structure so that it performs optimally regardless of network conditions
+ (for example, via SQLite + ContentProvider). The <a
+ href="https://developers.google.com/cloud-messaging/network-manager">
+ GCM Network Manager</a>
+ (<a href="https://developers.google.com/android/reference/com/google/android/gms/gcm/GcmNetworkManager">
+ <code>GcmNetworkManager</code></a>) can result in a robust mechanism to
+ sync data with servers while <a
+ href="{@docRoot}guide/topics/providers/content-providers.html">content
+ providers</a> ({@link android.content.ContentProvider}) cache that data,
+ combining to provide an architecture that enables a useful offline state.</li>
+ <li>Apps should cache content that is fetched from the network. Before making
+ subsequent requests, apps should display locally cached data. This ensures
+ that the app is functional regardless of whether the device is offline or
+ on a slow/unreliable network.</li>
+ </ul>
+<h4 id="network-duplicate">Deduplicate network requests</h4>
+ <ul>
+ <li>An offline-first architecture initially tries to fetch data from local
+ storage and, failing that, requests the data from the network. After being
+ retrieved from the network, the data is cached locally for future
+ retrieval. This helps to ensure that network requests for the same piece of
+ data only occur once—the rest of the requests are satisfied locally. To
+ achieve this, use a local database for long-lived data (usually
+ {@link android.database.sqlite} or
+ {@link android.content.SharedPreferences}).</li>
+ <li>An offline-first architecture always looks for data locally first, then
+ makes the request over the network. The response is cached and then returned
+ locally. Such an architecture simplifies an app’s flow between offline and
+ online states as one side fetches from the network to the cache, while the
+ other retrieves data from the cache to present to the user.</li>
+ <li>For transitory data, use a bounded disk cache such as a <a class="external-link"
+ href="https://github.com/JakeWharton/DiskLruCache"><code>DiskLruCache</code>
+ </a>. Data that doesn’t typically change should only be requested once over
+ the network and cached for future use. Examples of such data are images and
+ non-temporal documents like news articles or social posts.</li>
+ </ul>
+
+<h3 id="transfer">Fine-tune data transfer</h3>
+<h4 id="transfer-prioritize">Prioritize bandwidth</h4>
+ <ul>
+ <li>Writers of apps should not assume that any network that the device is
+ connected to is long-lasting or reliable. For this reason, apps should
+ prioritize network requests to display the most useful information to the
+ user as soon as possible.</li>
+ <li>Presenting users with visible and relevant information immediately is a
+ better user experience than making them wait for information that might not
+ be necessary. This reduces the time that the user has to wait and
+ increases the usefulness of the app on slow networks.</li>
+ <li>To achieve this, sequence your network requests such that text is
+ fetched before rich media. Text requests tend to be smaller, compress
+ better, and hence transfer faster, meaning that your app can display useful
+ content quickly. For more information on managing network requests, visit
+ the Android training on <a
+ href="{@docRoot}training/basics/network-ops/managing.html">Managing Network
+ Usage</a>.</li>
+ </ul>
+<h4 id="network-bandwidth">Use less bandwidth on slower connections</h4>
+ <ul>
+ <li>The ability for your app to transfer data in a timely fashion is
+ dependent on the network connection. Detecting the quality of the network
+ and adjusting the way your app uses it can help provide an excellent user
+ experience.</li>
+ <li>You can use the following methods to detect the underlying network
+ quality. Using the data from these methods, your app should tailor its use
+ of the network to continue to provide a timely response to user actions:
+ <ul>
+ <li>{@link android.net.ConnectivityManager}>
+ {@link android.net.ConnectivityManager#isActiveNetworkMetered}</li>
+ <li>{@link android.net.ConnectivityManager}>
+ {@link android.net.ConnectivityManager#getActiveNetworkInfo}</li>
+ <li>{@link android.net.ConnectivityManager}>
+ {@link android.net.ConnectivityManager#getNetworkCapabilities}</li>
+ <li>{@link android.telephony.TelephonyManager}>
+ {@link android.telephony.TelephonyManager#getNetworkType}</li>
+ </ul>
+ </li>
+ <li>On slower connections, consider downloading only lower-resolution media
+ or perhaps none at all. This ensures that your users are still able to use
+ the app on slow connections. Where you don’t have an image or the image is
+ still loading, you should always show a placeholder. You can create a
+ dynamic placeholder by using the <a
+ href="{@docRoot}tools/support-library/features.html#v7-palette">
+ Palette library</a> to generate placeholder colors that match the target
+ image.</li>
+ <li>Prioritize network requests such that text is fetched before rich media.
+ Text requests tend to be smaller, compress better, and hence transfer
+ faster, meaning that your app can display useful content quickly. For more
+ information on adjusting bandwidth based on network connection, see the
+ Android training on <a
+ href="{@docRoot}training/basics/network-ops/managing.html">Managing Network
+ Usage</a>.</li>
+ </ul>
+<h4 id="network-behavior">Detect network changes, then change app behavior</h4>
+ <ul>
+ <li>Network quality is not static; it changes based on location, network
+ traffic, and local population density. Apps should detect changes in
+ network and adjust bandwidth accordingly. By doing so, your app can tailor
+ the user experience to the network quality. Detect network state using
+ these methods:
+ <ul>
+ <li>{@link android.net.ConnectivityManager}>
+ {@link android.net.ConnectivityManager#getActiveNetworkInfo}</li>
+ <li>{@link android.net.ConnectivityManager}>
+ {@link android.net.ConnectivityManager#getNetworkCapabilities}</li>
+ <li>{@link android.telephony.TelephonyManager}>
+ {@link android.telephony.TelephonyManager#getDataState}</li>
+ </ul>
+ </li>
+ <li>As the network quality degrades, scale down the number and size of
+ requests. As the connection quality improves, you can scale up your
+ requests to optimal levels.</li>
+ <li>On higher quality, unmetered networks, consider <a
+ href="{@docRoot}training/efficient-downloads/efficient-network-access.html#PrefetchData">
+ prefetching data</a> to make it available ahead of time. From a user
+ experience standpoint, this might mean that news reader apps only fetch
+ three articles at a time on 2G but fetch twenty articles at a time on
+ Wi-Fi. For more information on adjusting app behavior based on network changes,
+ visit the Android training on <a
+ href="{@docRoot}training/monitoring-device-state/connectivity-monitoring.html">
+ Monitoring the Connectivity Status</a>.</li>
+ <li>The broadcast <a
+ href="{@docRoot}reference/android/net/ConnectivityManager.html#CONNECTIVITY_ACTION">
+ <code>CONNECTIVITY_CHANGE</code></a> is sent when a change in network
+ connectivity occurs. When your app is in the foreground, you can call <a
+ href="{@docRoot}reference/android/content/Context.html#registerReceiver(android.content.BroadcastReceiver,%20android.content.IntentFilter)">
+ <code>registerReceiver</code></a> to receive this broadcast. After receiving
+ the broadcast, you should reevaluate the current network state and adjust
+ your UI and network usage appropriately. You should not declare this receiver
+ in your manifest, as it will no longer function beginning with Android N.
+ For more details see <a href="{@docRoot}preview/behavior-changes.html">
+ Android N behavior changes</a>.</li>
+ </ul>
+
+<h3 class="rel-resources clearfloat">Related resources</h3>
+<div class="resource-widget resource-flow-layout col-13"
+ data-query="collection:distribute/essentials/billionsquality/connectivity"
+ data-sortOrder="-timestamp"
+ data-cardSizes="6x3"
+ data-maxResults="6"></div>
+
+<!-- capability -->
+<div class="headerLine">
+ <h2 id="capability">Device Capability</h2>
+</div>
+<p>Reaching new users means supporting an increasing variety of Android
+ platform versions and device specifications. Optimize for common RAM and
+ screen sizes and resolutions to improve the user experience. </p>
+
+<h3 id="screens">Support varying screen sizes</h3>
+<h4 id="screens-dp">Use density-independent pixels (dp)</h4>
+ <ul>
+ <li>Defining layout dimensions with pixels is a problem because different
+ screens have different pixel densities, so the same number of pixels may
+ correspond to different physical sizes on different devices. The
+ density-independent pixel (dp) corresponds to the physical size of a pixel
+ at 160 dots per inch (mdpi density).</li>
+ <li>Defining layouts with dp ensures that the physical size of your user
+ interface is consistent regardless of device. Visit the Android
+ guide on <a
+ href="https://developer.android.com/guide/practices/screens_support.html">
+ Supporting Multiple Screens</a> for best practices using
+ density-independent pixels.</li>
+ </ul>
+<h4 id="screens-density">Test graphics on ldpi/mdpi screen densities</h4>
+ <ul>
+ <li>Ensure that your app layouts work well on low- and medium-density
+ (ldpi/mdpi) screens because these are <a
+ href="https://developer.android.com/about/dashboards/index.html#Screens">
+ common densities</a>, especially in lower-cost devices. Testing on
+ lower-density screens helps to validate that your layouts are legible on
+ lower-density screens.</li>
+ <li>Lower-density screens can result in unclear text where the finer details
+ aren't visible. The Material Design guidelines describe <a
+ class="external-link" href="https://www.google.com/design/spec/layout/metrics-keylines.html">
+ metrics and keylines</a> to ensure that your layouts can scale across
+ screen densities.</li>
+ <li>Devices with lower-density screens tend to have lower hardware
+ specifications. To ensure that your app performs well on these devices,
+ consider reducing or eliminating heavy loads, such as animations and
+ transitions. For more information on supporting different densities, see
+ the Android training on <a
+ href="https://developer.android.com/training/multiscreen/screendensities.html">
+ Supporting Different Densities</a>.</li>
+ </ul>
+<h4 id="screens-sizes">Test layouts on small/medium screen sizes</h4>
+ <ul>
+ <li>Validate that your layouts scale down by testing on smaller screens. As
+ screen sizes shrink, be very selective about visible UI elements, because
+ there is limited space for them.</li>
+ <li>Devices with smaller screens tend to have lower hardware specifications.
+ To ensure that your app performs well on these devices, try reducing or
+ eliminating heavy loads, such as animations or transitions. For more
+ information on supporting different screen sizes, see the Android
+ training on <a
+ href="https://developer.android.com/training/multiscreen/screendensities.html">
+ Supporting Different Screen Sizes</a>.</li>
+ </ul>
+
+<h3 id="compatibility">Backward compatibility</h3>
+<h4 id="compatibility-sdkversion">Set your targetSdkVersion and minSdkVersion
+ appropriately</h4>
+ <ul>
+ <li>Apps should build and target a recent version of Android to ensure most
+ current behavior across a broad range of devices; this still provides
+ backward compatibility to older versions. Here are the best practices for
+ targeting API levels appropriately:
+ <ul>
+ <li><a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">
+ {@code targetSdkVersion}</a> should be the latest version of Android.
+ Targeting the most recent version ensures that your app inherits newer
+ runtime behaviors when running newer versions of Android. Be sure to
+ test your app on newer Android versions when updating the
+ targetSdkVersion as it can affect app behavior.</li>
+ <li><a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">
+ {@code minSdkVersion}</a> sets the minimum supported Android version.
+ Use Android 4.0 (API level 14: Ice Cream Sandwich) or Android 4.1 (API
+ level 16: Jelly Bean)—these versions give maximum coverage for modern
+ devices. Setting {@code minSdkVersion} also results in the Android build
+ tools reporting incorrect use of new APIs that might not be available in
+ older versions of the platform. By doing so, developers are protected
+ from inadvertently breaking backward compatibility.</li>
+ </ul>
+ </li>
+ <li>Consult the <a
+ href="https://developer.android.com/about/dashboards/index.html#Platform">
+ Android dashboards</a>, the <a class="external-link"
+ href="https://play.google.com/apps/publish/">Google Play Developer
+ Console</a> for your app, and industry research in your target markets to
+ gauge which versions of Android to target, based on your target users.</li>
+ </ul>
+<h4 id="compatibility-libraries">Use the Android Support libraries</h4>
+ <ul>
+ <li>Ensure your app provides a consistent experience across OS versions by
+ using the Google-provided support libraries such as AppCompat and the Design
+ Support Library. The Android Support Library package is a set of code
+ libraries that provides backward-compatible versions of Android framework
+ APIs as well as features that are only available through the library APIs.
+ </li>
+ <li>Some of the the highlights include:
+ <ul>
+ <li>v4 & v7 support library: Many framework APIs for older versions of
+ Android such as {@link android.support.v4.view.ViewPager},
+ {@link android.app.ActionBar},
+ {@link android.support.v7.widget.RecyclerView}, and
+ {@link android.support.v7.graphics.Palette}.</li>
+ <li><a href="{@docRoot}tools/support-library/features.html#design">Design
+ Support</a> library: APIs to support adding Material Design components
+ and patterns to your apps.</li>
+ <li><a href="{@docRoot}tools/support-library/features.html#multidex">
+ Multidex Support</a> library: provides support for large apps that have
+ more than 65K methods. This can happen if your app is using many
+ libraries.</li>
+ </ul>
+ </li>
+ <li>For more information on the available support libraries, see the <a
+ href="https://developer.android.com/tools/support-library/features.html">
+ Support Libraries Features</a> section of the Android Developer site.</li>
+ </ul>
+<h4 id="compatibility-playservices">Use Google Play services</h4>
+ <ul>
+ <li>Google Play services brings the best of Google APIs independent of
+ Android platform version. Consider using features from Google Play services
+ to offer the most streamlined Google experience on Android devices.</li>
+ <li>Google Play services also include useful APIs such as <a
+ href="https://developers.google.com/android/reference/com/google/android/gms/gcm/GcmNetworkManager">
+ <code>GcmNetworkManager</code></a>, which provides much of Android 5.0’s
+ {@link android.app.job.JobScheduler} API for older versions of Android. </li>
+ <li>Updates to Google Play services are distributed automatically by the
+ Google Play Store, and new versions of the client library are delivered
+ through the Android SDK Manager. </li>
+ </ul>
+<h3 id="memory">Efficient memory usage</h3>
+<h4 id="memory-footprint">Reduce memory footprint on low-cost devices</h4>
+ <ul>
+ <li>Adjusting your memory footprint dynamically helps to ensure compatibility
+ across devices with different RAM configurations.</li>
+ <li>Methods such as {@link android.app.ActivityManager#isLowRamDevice} and
+ {@link android.app.ActivityManager#getMemoryClass()} help determine memory
+ constraints at runtime. Based on this information, you can scale down your
+ memory usage. As an example, you can use lower resolution images on low memory
+ devices.</li>
+ <li>For more information on managing your app’s memory, see the Android
+ training on <a href="{@docRoot}training/articles/memory.html">Managing
+ Your App's Memory</a>.</li>
+ </ul>
+<h4 id="memory-longprocesses">Avoid long-running processes</h4>
+ <ul>
+ <li>Long-running processes stay resident in memory and can result in slowing
+ down the device. In most situations, your app should wake up for a given
+ event, process data, and shut down. You should use <a
+ href="https://developers.google.com/cloud-messaging">Google Cloud Messaging
+ (GCM)</a> and/or <a
+ href="https://developers.google.com/android/reference/com/google/android/gms/gcm/GcmNetworkManager">
+ <code>GcmNetworkManager</code></a> to avoid long running background
+ services and reduce memory pressure on the user’s device.</li>
+ </ul>
+<h4 id="memory-benchmark">Benchmark memory usage</h4>
+ <ul>
+ <li>Android Studio provides memory benchmarking and profiling tools, enabling
+ you to measure memory usage at run time. Benchmarking your app’s memory
+ footprint enables you to monitor memory usage over multiple versions of
+ the app. This can help catch unintentional memory footprint growth. These
+ tools can be used in the following ways:
+ <ul>
+ <li>Use the <a
+ href="{@docRoot}tools/performance/memory-monitor/index.html">Memory
+ Monitor</a> tool to find out whether undesirable garbage collection (GC)
+ event patterns might be causing performance problems.</li>
+ <li>Run <a
+ href="{@docRoot}tools/performance/heap-viewer/index.html">Heap Viewer</a>
+ to identify object types that get or stay allocated unexpectedly or
+ unnecessarily.</li>
+ <li>Use <a
+ href="{@docRoot}tools/performance/allocation-tracker/index.html">
+ Allocation Tracker</a> to identify where in your code the problem might
+ be.</li>
+ </ul>
+ </li>
+ <li>For more information on benchmarking memory usage, see the <a
+ href="{@docRoot}tools/performance/comparison.html">
+ Memory Profilers</a> tools on the Android Developers site.</li>
+ </ul>
+
+<h3 class="rel-resources clearfloat">Related resources</h3>
+<div class="resource-widget resource-flow-layout col-13"
+ data-query="collection:distribute/essentials/billionsquality/capability"
+ data-sortOrder="-timestamp"
+ data-cardSizes="6x3"
+ data-maxResults="6"></div>
+
+<!-- cost -->
+<div class="headerLine">
+ <h2 id="cost">Data Cost</h2>
+</div>
+<p>Data plans in some countries can cost upwards of 10% of monthly income.
+ Conserve data and give control to optimize user experience. Reduce data
+ consumption and give users control over your app’s use of data.</p>
+
+<h3 id="appsize">Reduce app size</h3>
+<h4 id="appsize-graphics">Reduce APK graphical asset size</h4>
+ <ul>
+ <li>Graphical assets are often the largest contributor to the size of the
+ APK. Optimizing these can result in smaller downloads and thus faster
+ installation times for users.</li>
+ <li>For graphical assets like icons, use Scalable Vector Graphics (SVG)
+ format. SVG images are relatively tiny in size and can be rendered at
+ runtime to any resolution. The <a
+ href="{@docRoot}tools/support-library/index.html">Android Support</a>
+ library provides a backward-compatible implementation for vector resources as
+ far back as Android 2.1 (API level 7). Get started with vectors with <a
+ class="external-link"
+ href="https://medium.com/@chrisbanes/appcompat-v23-2-age-of-the-vectors-91cbafa87c88">
+ this Medium post</a>. </li>
+ <li>For non-vector images, like photos, use <a
+ href="https://developers.google.com/speed/webp/">WebP</a>. WebP reduces
+ image load times, saves network bandwidth, and is proven to result in
+ smaller file sizes than its PNG and JPG counterparts, with at least the
+ same image quality. Even at lossy settings, WebP can produce a nearly
+ identical image. Android has had lossy WebP support since Android 4.0 (API
+ level 14: Ice Cream Sandwich) and support for lossless / transparent WebP since Android 4.2 (API level 17: Jelly Bean).</li>
+ <li>If you have many large images across multiple densities, consider
+ using <a href="{@docRoot}google/play/publishing/multiple-apks.html">Multiple
+ APK support</a> to split your APK by density. This results in builds
+ targeted for specific densities, meaning users with low-density devices
+ won’t have to incur the penalty of unused high-density assets.</li>
+ <li>A detailed guide on reducing your APK size can be found in <a
+ class="external-link" href="https://medium.com/@wkalicinski/smallerapk-part-4-multi-apk-through-abi-and-density-splits-477083989006">
+ series of Medium posts</a>.</li>
+ </ul>
+<h4 id="appsize-code">Reduce code size</h4>
+ <ul>
+ <li>Be careful about using external libraries because not all libraries are
+ meant to be used in mobile apps. Ensure that the libraries your app is
+ using are optimized for mobile use.</li>
+ <li>Every library in your Android project is adding potentially unused code
+ to your APK. There are also some libraries that aren’t designed with mobile
+ development in mind. These libraries can end up contributing to significant
+ APK bloat.</li>
+ <li>Consider optimizing your compiled code using a tool such as <a
+ href="{@docRoot}tools/help/proguard.html">ProGuard</a>. ProGuard identifies
+ code that isn’t being used and removes it from your APK. Also <a
+ class="external-link"
+ href="http://tools.android.com/tech-docs/new-build-system/resource-shrinking">
+ enable resource shrinking</a> at build time by setting
+ <code>minifyEnabled=true</code>, <code>shrinkResources=true</code> in
+ <code>build.gradle</code>—this automatically removes unused resources from
+ your APK.</li>
+ <li>When using Google Play services, you should <a
+ href="{@docRoot}google/play-services/setup.html#add_google_play_services_to_your_project">
+ selectively include</a> only the necessary APIs into your APK.</li>
+ <li>For more information on reducing code size in your APK, see the Android
+ training on how to <a
+ href="{@docRoot}training/articles/memory.html#DependencyInjection">Avoid
+ dependency injection frameworks</a>.</li>
+ </ul>
+<h4 id="appsize-external">Allow app to be moved to external (SD) storage</h4>
+ <ul>
+ <li>Low-cost devices often come with little on-device storage. Users can
+ extend this with SD cards; however, apps need to explicitly declare that
+ they support being installed to external storage before users can move them.
+ </li>
+ <li>Allow your app to be installed to external storage using the <a
+ href="{@docRoot}guide/topics/manifest/manifest-element.html#install"><code>
+ android:installLocation</code></a> flag in your AndroidManifest. For more
+ information on enabling your app to be moved to external storage, see the
+ Android guide on <a
+ href="{@docRoot}guide/topics/data/install-location.html">App Install
+ Location</a>.</li>
+ </ul>
+
+<h4 id="appsize-postinstall">Reduce post-install app disk usage</h4>
+ <ul>
+ <li>Keeping your app’s disk usage low means that users are less likely to
+ uninstall your app when the device is low on free space. When using caches,
+ it’s important to apply bounds around your caches—this prevents your app’s
+ disk usage from growing indefinitely. Be sure you put your cached data in
+ {@link android.content.Context#getCacheDir()}—the system can delete files
+ placed here as needed, so they won’t show up as storage committed to the
+ app.</li>
+ </ul>
+
+<h3 id="configurablenetwork">Offer configurable network usage</h3>
+<h4 id="configurablenetwork-onboarding">Provide onboarding experiences for
+subjective user choices</h4>
+ <ul>
+ <li>Apps that allow users to reduce data usage are well received, even if
+ they demand heavy data requirements. If your app uses a considerable amount
+ of bandwidth (for example, video streaming apps), you can provide an
+ onboarding experience for users to configure network usage. For example,
+ you could allow the user to force lower-bitrate video streams on cellular
+ networks. </li>
+ <li>Additional settings for users to control data syncing, prefetching, and
+ network usage behavior (for example, prefetch all starred news categories on
+ Wi-Fi only), also help users tailor your app’s behavior to their needs.</li>
+ <li>For more information on managing network usage, see the Android training
+ on <a href="{@docRoot}training/basics/network-ops/managing.html">Managing
+ Network Usage</a>.</li>
+ </ul>
+<h4 id="configurablenetwork-preferences">Provide a network preferences
+screen</h4>
+ <ul>
+ <li>You can navigate to the app’s network settings from outside the app by
+ means of a network preferences screen. You can invoke this screen from
+ either the system settings screen or the system data usage screen.</li>
+ <li>To provide a network preferences screen that users can access from within
+ your app as well as from the system settings, in your app include an
+ activity that supports the
+ {@link android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} action.</li>
+ <li>For further information on adding a network preferences screen, see the
+ Android training on <a
+ href="{@docRoot}training/basics/network-ops/managing.html#prefs">
+ Implementing a Preferences Activity</a>.</li>
+ </ul>
+
+
+
+<h3 class="rel-resources clearfloat">Related resources</h3>
+<div class="resource-widget resource-flow-layout col-13"
+ data-query="collection:distribute/essentials/billionsquality/cost"
+ data-sortOrder="-timestamp"
+ data-cardSizes="6x3"
+ data-maxResults="6"></div>
+
+
+<!-- consumption -->
+<div class="headerLine">
+ <h2 id="consumption">Battery Consumption</h2>
+</div>
+<p>Access to reliable power supplies varies, and outages can disrupt planned
+charges. Defend your users' batteries against unnecessary drain by benchmarking
+your battery use, avoiding wakelocks, scheduling tasks, and monitoring sensor
+requests.</p>
+<h3 id="consumption-reduce">Reduce battery consumption</h3>
+ <ul>
+ <li>Your app should do minimal activity when in the background and when the
+ device is running on battery power.</li>
+ <li><a href="{@docRoot}reference/android/os/PowerManager.WakeLock.html">Wake
+ locks</a> are mechanisms to keep devices on so that they can perform
+ background activities. Avoid using wake locks because they prevent the
+ device from going into low-power states.</li>
+ <li>To reduce the number of device wake-ups, batch network activity. For more
+ information on batching, see the Android training on <a
+ href="{@docRoot}training/efficient-downloads/efficient-network-access.html">
+ Optimizing Downloads for Efficient Network Access</a>.</li>
+ <li><a
+ href="https://developers.google.com/android/reference/com/google/android/gms/gcm/GcmNetworkManager">
+ <code>GcmNetworkManager</code></a> schedules tasks and lets Google Play
+ services batch operations across the system. This greatly
+ simplifies the implementation of common patterns, such as waiting for
+ network connectivity, device charging state, retries, and backoff. Use
+ <code>GcmNetworkManager</code> to perform non-essential background activity
+ when the device is charging and is connected to an unmetered network.</li>
+ <li>Sensors, like GPS, can also have a significant drain on the battery. The
+ recommended way to request location is to use the FusedLocationProvider API.
+ The <a
+ href="https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderApi">FusedLocationProvider</a> API manages the
+ underlying location technology and provides a simple API so that you can
+ specify requirements—like high accuracy or low power—at a high
+ level. It also optimizes the device's use of battery power by caching
+ locations and batching requests across apps. For more information on the
+ ideal ways to request location, see the <a
+ href="{@docRoot}training/location/retrieve-current.html">Getting the Last
+ Known Location</a> training guide.
+ </li>
+ </ul>
+<h3 id="consumption-benchmark">Benchmark battery usage</h3>
+ <ul>
+ <li>Benchmarking your app’s usage in a controlled environment helps you
+ understand the battery-heavy tasks in your app. It is a good practice to
+ benchmark your app’s battery usage to gauge efficiency and track changes
+ over time.
+</li>
+ <li><a
+ href="{@docRoot}tools/performance/batterystats-battery-historian/index.html">
+ Batterystats</a> collects battery data about your apps, and <a
+ href="{@docRoot}tools/performance/batterystats-battery-historian/index.html">
+ Battery Historian</a> converts that data into an HTML visualization. For
+ more information on reducing battery usage, see the Android training on <a
+ href="{@docRoot}training/monitoring-device-state/index.html">Optimizing
+ Battery Life</a>.</li>
+ </ul>
+
+<h3 class="rel-resources clearfloat">Related resources</h3>
+<div class="resource-widget resource-flow-layout col-13"
+ data-query="collection:distribute/essentials/billionsquality/consumption"
+ data-sortOrder="-timestamp"
+ data-cardSizes="6x3"
+ data-maxResults="6"></div>
+
+<!-- content -->
+<div class="headerLine">
+ <h2 id="contentsection">Content</h2>
+</div>
+<p>Make sure that your app works well on a variety of screens: offering good,
+ crisp graphics and appropriate layouts on low resolution and physically small
+ screens. Ensure that your app is designed to be easily localized by
+ accommodating the variations between languages: allow for spacing, density,
+ order, emphasis, and wording variations. Also make sure that date, time, and
+ the like are internationalized and displayed according to the phone’s
+ settings.</p>
+
+<h3 id="content-responsive">Fast and responsive UI</h3>
+<h4 id="content-feedback">Touch feedback on all touchable items</h4>
+ <ul>
+ <li>Touch feedback adds a tactile feeling to the user interface. You should
+ ensure your app provides touch feedback on all touchable elements to reduce
+ the perceived app latency as much as possible.
+</li>
+ <li><a
+ href="https://www.google.com/design/spec/animation/responsive-interaction.html">
+ Responsive interaction</a> encourages deeper exploration of an app by
+ creating timely, logical, and delightful screen reactions to user input.
+ Responsive interaction elevates an app from an information-delivery service
+ to an experience that communicates using multiple visual and tactile
+ responses.</li>
+ <li>For more information, see the Android training on <a
+ href="{@docRoot}training/material/animations.html#Touch">Customizing Touch
+ Feedback</a>.</li>
+ </ul>
+<h4 id="content-interactive">UI should always be interactive</h4>
+ <ul>
+ <li>Apps that are unresponsive when performing background activity feel slow
+ and reduce user satisfaction. Ensure your app always has a responsive UI
+ regardless of any background activity. Achieve this by performing network
+ operations or any heavy-duty operations in a background thread—keep the UI
+ thread as idle as you can.</li>
+ <li>Material Design apps use minimal visual changes when your app is loading
+ content by representing each operation with a single activity indicator.
+ Avoid blocking dialogs with <a
+ href="https://www.google.com/design/spec/components/progress-activity.html">
+ loading indicators</a>.</li>
+ <li><a
+ href="http://www.google.com/design/spec/patterns/empty-states.html">Empty
+ states</a> occur when the regular content of a view can’t be shown. It might
+ be a list that has no items or a search that returns no results. Avoid
+ completely empty states. The most basic empty state displays a
+ non-interactive image and a text tagline. Where you don’t have an image, or
+ the image is still loading, you should always show either a static
+ placeholder, or create a dynamic placeholder by using the <a
+ href="{@docRoot}tools/support-library/features.html#v7-palette">Palette
+ library</a> to generate placeholder colors that match the target image.</li>
+ <li>For more information, see the Android training on <a
+ href="{@docRoot}training/articles/perf-anr.html">Keeping Your App
+ Responsive</a>.</li>
+ </ul>
+<h4 id="content-60fps">Target 60 frames per second on low-cost devices</h4>
+ <ul>
+ <li>Ensure that your app always runs fast and smoothly, even on low-cost
+ devices.</li>
+ <li>Overdraw can significantly slow down your app—it occurs when the pixels
+ are being drawn more than once per pass. An example of this is when you have
+ an image with a button placed on top of it. While some overdraw is
+ unavoidable, it should be minimized to ensure a smooth frame rate. Perform
+ <a href="{@docRoot}tools/performance/debug-gpu-overdraw/index.html">Debug
+ GPU overdraw</a> on your app to ensure it is minimized.</li>
+ <li>Android devices refresh the screen at 60 frames per second (fps), meaning
+ your app has to update the screen within roughly 16 milliseconds. <a
+ href="{@docRoot}tools/performance/profile-gpu-rendering/index.html">Profile
+ your app</a> using on-device tools to see if and when your app is not
+ meeting this 16-ms average.</li>
+ <li>Reduce or remove animations on low-cost devices to lessen the burden on
+ the device’s CPU and GPU. For more information, see the Android training on
+ <a href="{@docRoot}training/improving-layouts/index.html">Improving Layout
+ Performance</a>. </li>
+ </ul>
+<h4 id="content-firstload">If anticipated start speed is low, use launch screen
+on first load</h4>
+ <ul>
+ <li>The launch screen is a user’s first experience of your application.
+ Launching your app while displaying a blank canvas increases its perceived
+ loading time, so consider using a placeholder UI or a branded launch screen
+ to reduce the perceived loading time.</li>
+ <li>A<a href="https://www.google.com/design/spec/patterns/launch-screens.html#launch-screens-types-of-launch-screens">
+ placeholder UI</a> is the most seamless launch transition, appropriate for
+ both app launches and in-app activity transitions.</li>
+ <li><a
+ href="https://www.google.com/design/spec/patterns/launch-screens.html#launch-screens-placeholder-ui">
+ Branded launch screens</a> provide momentary brand exposure, freeing the UI
+ to focus on content.</li>
+ <li>For more information on implementing splash screens, see the <a
+ href="https://www.google.com/design/spec/patterns/launch-screens.html">
+ Launch screens</a> section of the Material Design spec.</li>
+ </ul>
+<h3 id="ui">UI best practices</h3>
+ <ul>
+ <li><a
+ href="https://www.google.com/design/spec/material-design/introduction.html">
+ Material Design</a> is a visual language that synthesizes the classic
+ principles of good design with the innovation and possibility of technology
+ and science. Material Design aims to develop a single underlying system that
+ allows for a unified experience across platforms and device sizes. Consider
+ using key Material Design components so that users intuitively know how to
+ use your app.</li>
+ <li>Ready-to-use Material Design components are available via the <a
+ href="{@docRoot}tools/support-library/features.html#design">Design Support
+ library</a>. These components are supported in Android 2.1 (API level 7) and
+ above.</li>
+ </ul>
+<h3 id="localization">Localization</h3>
+ <ul>
+ <li>Your users could be from any part of the world and their first language
+ may not be yours. If you don’t present your app in a language that your
+ users can read, it is a missed opportunity. You should therefore
+ localize your app for key regional languages.</li>
+ <li>To learn more, visit the Android training on <a
+ href="{@docRoot}training/basics/supporting-devices/languages.html">
+ Supporting Different Languages</a>.</li>
+ </ul>
+
+<h3 class="rel-resources clearfloat">Related resources</h3>
+<div class="resource-widget resource-flow-layout col-13"
+ data-query="collection:distribute/essentials/billionsquality/content"
+ data-sortOrder="-timestamp"
+ data-cardSizes="6x3"
+ data-maxResults="6"></div>
diff --git a/docs/html/distribute/images/billions-guidelines.png b/docs/html/distribute/images/billions-guidelines.png
new file mode 100644
index 0000000..05f71b6
--- /dev/null
+++ b/docs/html/distribute/images/billions-guidelines.png
Binary files differ
diff --git a/docs/html/google/play/billing/billing_admin.jd b/docs/html/google/play/billing/billing_admin.jd
index ff7acc9..2203f71 100644
--- a/docs/html/google/play/billing/billing_admin.jd
+++ b/docs/html/google/play/billing/billing_admin.jd
@@ -8,10 +8,11 @@
<h2>In this document</h2>
<ol>
<li><a href="#billing-list-setup">Creating a Product List</a></li>
+ <li><a href="#pricing-template">Pricing Templates</a></li>
<li><a href="#billing-purchase-type">Choosing a Product Type</a></li>
- <li><a href="#billing-testing-setup">Setting up Test Accounts</a></li>
<li><a href="#billing-refunds">Handling Refunds</a></li>
<li><a href="#billing-refunds">Working with Order Numbers</a></li>
+ <li><a href="#billing-testing-setup">Setting up Test Accounts</a></li>
<li><a href="#billing-support">Where to Get Support</a></li>
</ol>
@@ -44,7 +45,19 @@
<p>The Google Play Developer Console provides a product list for each of your published
applications. You can sell an item using Google Play's in-app billing feature only if the item is
listed on an application's product list. Each application has its own product list; you cannot sell
-items that are listed in another application's product list.</p>
+items that appear on another application's product list.</p>
+
+<div class="figure-right">
+ <figure id="fig-iap">
+ <img src="{@docRoot}images/in-app-billing/in_app_products.png" width="700"
+ alt="The Mythical Journey app lists two in-app products, Invisibility Potion and Sleeping Potion.">
+ <figcaption>
+ <b>Figure 1. </b>You can access an application's product list by
+ selecting the <strong>In-app Products</strong> link in the main Apps
+ navigation.
+ </figcaption>
+ </figure>
+</div>
<p>You can access an application's product list by clicking the <strong>In-App Products</strong>
link in applications listed in your developer account (see
@@ -58,25 +71,17 @@
you are selling in your application. It does not store any digital content. You are responsible for
storing and delivering the digital content that you sell in your applications.</p>
-<div style="margin:1em;">
-<img style="border:1px solid #ddd;padding-bottom:.5em" src="{@docRoot}images/in-app-billing/billing_product_list.png" xheight="548" id="figure1" />
-<p class="img-caption" style="padding-left:.5em;">
- <strong>Figure 1.</strong> You can access an application's product list by clicking the
- <strong>In-App Products</strong> link in the main Apps navigation.
-</p>
-</div>
-
<p>You can create a product list for any published application, or any
application in the alpha or beta channels, that's been
uploaded and saved to the Developer Console. However, you must have a Google payments merchant
account and the application's manifest must include the <code>com.android.vending.BILLING</code>
permission. If an application's manifest does not include this permission, you will be able to edit
-existing items in the product list but you will not be able to add new items to the list. For more
+existing items in the product list, but you won't be able to add new items to the list. For more
information about this permission, see
<a href="{@docRoot}google/play/billing/billing_integrate.html#billing-permission">Updating Your
Application's Manifest</a>.</p>
-<p class="note"><strong>Note:</strong> Previously you could test an app by
+<p class="note"><strong>Note:</strong> Previously, you could test an app by
uploading an unpublished "draft" version. This functionality is no longer
supported; instead, you must publish it to the alpha or beta distribution
channel. For more information, see <a
@@ -97,7 +102,9 @@
product list for testing purposes. The CSV file method is useful if your application has a large
number of in-app items.</p>
-<p class="note"><strong>Note:</strong> Batch upload of product lists containing subscriptions is not yet supported.</p>
+<p class="note"><strong>Note:</strong> Batch upload of product lists containing subscriptions is not yet supported.
+Also, you cannot perform a batch upload containing changes to in-app products that are linked to a
+<a href="#pricing-template">pricing template</a>.</p>
<h3 id="billing-form-add">Adding items one at a time to a product list</h3>
@@ -107,39 +114,67 @@
<li><a href="http://play.google.com/apps/publish">Log in</a> to your publisher account.</li>
<li>In the <strong>All Applications</strong> panel, click on the
app name, then select <strong>In-app Products</strong>.</li>
- <li>Click <strong>Add new product</strong> (see figure 2) and provide details about the item you are
- selling and then click <strong>Save</strong> or <strong>Publish</strong>.</li>
+ <li>Click <strong>Add new product</strong> (see figure 2) and provide the product type and ID for the item you are
+ selling. Click <strong>Continue</strong>.</li>
+ <li>Enter additional information about the item, then click <strong>Save</strong> or <strong>Publish</strong>.
</ol>
-<div style="margin:1em;">
-<img style="border:1px solid #ddd;padding-bottom:.5em;" src="{@docRoot}images/in-app-billing/billing_add.png" height="300" id="figure2" />
-<p class="img-caption" style="padding-left:.5em;">
- <strong>Figure 2.</strong> The Add New Product page lets you add items to an
- application's product list.
-</p>
+<div class="figure-right">
+ <figure id="fig-anp">
+ <img src="{@docRoot}images/in-app-billing/add_new_product.png" width="300"
+ alt="Adding a managed product with a Product ID of basic_sleeping_potion.">
+ <figcaption>
+ <b>Figure 2. </b>The <em>Add New Product</em> page lets you
+ provide basic information about a paid app or in-app product.
+ </figcaption>
+ </figure>
</div>
-<p>You must enter the following information for each item in a product list:</p>
+<div class="figure-right">
+ <figure id="fig-nmp">
+ <img src="{@docRoot}images/in-app-billing/new_managed_product.png" width="700"
+ alt="">
+ <figcaption>
+ <b>Figure 3. </b>The <em>New Managed Product</em> page lets you finish
+ adding items to an app’s product list.
+ </figcaption>
+ </figure>
+</div>
+
+<div class="figure-right">
+ <figure id="fig-elp">
+ <img src="{@docRoot}images/in-app-billing/edit_local_prices.png" width="700"
+ alt="An item that costs 1.99 in USD usually costs a different amount in AUD,
+ EUR, or BOB. Some countries also add tax to the price.">
+ <figcaption>
+ <b>Figure 4. </b>Specifying additional currencies for an in-app product.
+ </figcaption>
+ </figure>
+</div>
+
+<p>You must enter the following information for each item in a product list (see
+ figures 2 and 3):</p>
<ul>
<li><strong>In-app Product ID</strong>
<p>Product IDs are unique across an application's namespace. A product ID must start with a
lowercase letter or a number, and must be composed using only lowercase letters (a-z), numbers
- (0-9), underlines (_), and dots (.). The product ID "android.test" is reserved, as are all
- product IDs that start with "android.test."</p>
- <p>In addition, you cannot modify an item's product ID after it is created, and you cannot reuse
+ (0-9), underscores (_), and dots (.). The product ID <code>"android.test"</code> is reserved, as are all
+ product IDs that start with <code>"android.test"</code>.</p>
+ <p class="note"><strong>Note: </strong>Be sure to plan your product ID namespace carefully. You
+ cannot modify an item's product ID after it is created, and you cannot reuse
a product ID.</p>
</li>
<li><strong>Product Type</strong>
- <p>The product type can be <strong>Managed per user account</strong>,
- <strong>Unmanaged</strong>, or <strong>Subscription</strong>. You can never
+ <p>The product type can be <strong>Managed product</strong> or <strong>Subscription</strong>. You cannot
change an item's product type after you set it. For more information, see
- <a href="#billing-purchase-type">Choosing a product type</a> later in this
- document.</p>
+ <a href="#billing-purchase-type">Choosing a product type</a>.</p>
+ <p class="note"><strong>Note: </strong>For subscription items, note that you cannot change the
+ item's price once you have published it.</p>
</li>
<li><strong>Publishing State</strong>
<p>An item's publishing state can be <strong>Published</strong> or <strong>Unpublished
</strong>. To be visible to a user during checkout, an item's publishing state must be set to
- <strong>Published</strong> and the item's application must be published on Google Play.</p>
+ <strong>Published</strong>, and the item's application must be published on Google Play.</p>
<p class="note"><strong>Note:</strong> This is not true for test accounts. An item is visible to
a test account if the application is not published and the item is published. See <a
href="{@docRoot}google/play/billing/billing_testing.html#billing-testing-real">Testing In-app
@@ -147,12 +182,12 @@
</li>
<li><strong>Languages and Translations</strong>
<p>You can provide localized titles and descriptions for your in-app
- products using the Add Translations button. If you want Google Play to translate
- your title and description for you, based on the title and description in the
- default language, just click the languages that you want to offer. If you want
- to provide custom translations in specific languages, you can also do that. By
- default, an in-app product inherits its default language from the parent
- application.</p>
+ products by selecting <strong>Add Translations</strong>. If you want Google
+ Play to translate your title and description for you, based on the title and
+ description in the default language, just choose the languages that you
+ want to offer. If you want to provide custom translations in specific
+ languages, you can also do that. By default, an in-app product inherits its
+ default language from the parent application.</p>
</li>
<li><strong>Title</strong>
<p>The title is a short descriptor for the item. For example, "Sleeping potion."
@@ -166,36 +201,19 @@
up to 80 characters in length.</p>
</li>
<li><strong>Price</strong>
- <p>You must provide a default price in your home currency. You can also provide prices in other
- currencies, but you can do this only if a currency's corresponding country is listed as a
- target country for your application. You can specify target countries on the Edit Application
- page in the Google Play developer console.</p>
- <p>To specify prices in other currencies, you can manually enter the price for each
- currency or you can click <strong>Auto Fill</strong> and let Google Play do a one-time
- conversion from your home currency to the currencies you are targeting (see figure 3).</p>
- <p>For subscription items, note that you can not change the item's price once you have published
- it.</p>
+ <p>Provide a price in your home currency, or link the price to an existing
+ pricing template (see figure 4). Based on the price you enter or the prices
+ from the pricing template, the system autofills country-specific prices for
+ different currencies. These generated prices use today's exchange rates and
+ locally-relevant pricing patterns.</p>
+ <p>You can also change prices for other currencies manually, but you can do
+ this only if a currency is used in one of the target countries for your
+ application. You can specify target countries for your app on the
+ <strong>Pricing & Distribution</strong> page in the Google Play
+ Developer Console.</p>
</li>
</ul>
-<div style="margin:1em;">
-<img style="border:1px solid #ddd;padding-bottom:.5em"
- src="{@docRoot}images/in-app-billing/billing_list_form_2.png"
- xheight="1226" id="figure3" />
-<p class="img-caption" style="padding-left:.5em;">
- <strong>Figure 3.</strong> Specifying additional currencies for an in-app product.
-</p>
-</div>
-
-<p>For more information about product IDs and product lists, see <a
-href="http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en&answer=1072599">
-Creating In-App Product IDs</a>. For more information about pricing, see <a
-href="http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en&answer=1153485">
-In-App Billing Pricing</a>.</p>
-
-<p class="note"><strong>Note</strong>: Be sure to plan your product ID namespace. You cannot reuse
-or modify product IDs after you save them.</p>
-
<h3 id="billing-bulk-add">Adding a batch of items to a product list</h3>
<p>To add a batch of items to a product list using a CSV file, you first need to create your CSV
@@ -203,17 +221,48 @@
manually through the In-app Products UI (see <a href="#billing-form-add">Adding items one at a time
to a product list</a>).
-<p>If you are importing and exporting CSV files with in-app products, please
-keep tax-inclusive pricing in mind. If you use auto-fill, you can provide a
-tax-exclusive default price and tax-inclusive prices will be auto-filled. If you
+<p>If you are importing and exporting CSV files with in-app products, keep
+country-specific pricing in mind. If you use auto-fill, you can provide a
+tax-exclusive default price, and tax-inclusive prices will be auto-filled. If you
do not use auto-fill, prices you provide must include tax.</p>
<p class="note"><strong>Note:</strong> Batch upload of product lists containing
-subscriptions is not yet supported.</p>
+subscriptions is not yet supported. Also, you cannot perform a batch upload
+containing changes to in-app products that are linked to a
+<a href="#pricing-template">pricing template</a>.</p>
-The CSV file uses commas (,) and semi-colons (;) to separate data values.
-Commas are used to separate primary data values, and semi-colons are used to separate subvalues. For
-example, the syntax for the CSV file is as follows:</p>
+
+
+<p>To import the items that are specified in your CSV file, do the following:</p>
+
+<ol>
+ <li><a href="http://play.google.com/apps/publish">Log in</a> to your publisher account.</li>
+ <li>In the <strong>All Applications</strong> panel, click on the app
+ name, then select <strong>In-app Products</strong>.</li>
+ <li>On the In-app Products List page, click <strong>Import/Export</strong>
+ > <strong>Import in-app products from CSV file</strong>, then select your
+ CSV file.
+ <p>The CSV file must be on your local computer or on a local disk that is connected to your
+ computer.</p>
+ </li>
+ <li>Select the <strong>Overwrite</strong> checkbox if you want to overwrite existing items in
+ your product list.
+ <p>This option overwrites values of existing items only if the value of the <em>product_id</em>
+ in the CSV file matches the In-app Product ID for an existing item in the product list.
+ Overwriting doesn't delete items that are on a product list but not present in the CSV
+ file.</p>
+ </li>
+</ol>
+
+<p>You can also export an existing product list to a CSV file by clicking <strong>Export to CSV
+</strong> on the In-app Product List page. This is useful if you have manually added items to
+a product list and you want to start managing the product list through a CSV file.</p>
+
+<h4 id="billing-bulk-format">Formatting batches of items</h4>
+
+<p>The CSV file uses commas (,) and semi-colons (;) to separate data values.
+Commas are used to separate primary data values, and semi-colons are used to
+separate subvalues. For example, the syntax for the CSV file is as follows:</p>
<p>"<em>product_id</em>","<em>publish_state</em>","<em>purchase_type</em>","<em>autotranslate</em>
","<em>locale</em>; <em>title</em>; <em>description</em>","<em>autofill</em>","<em>country</em>;
@@ -266,36 +315,39 @@
<li><em>title</em>
<p>This is equivalent to the Title setting in the In-app Products UI. If the <em>title</em>
contains a semicolon, it must be escaped with a backslash (for example, "\;"). A backslash
- should also be escaped with a backslash (for example, "\\">.</p>
+ should also be escaped with a backslash (for example, "\\").</p>
</li>
<li><em>description</em>
<p>This is equivalent to the Description in the In-app Products UI. If the <em>description</em>
contains a semicolon, it must be escaped with a backslash (for example, "\;"). A backslash
- should also be escaped with a backslash (for example, "\\">.</p>
+ should also be escaped with a backslash (for example, "\\").</p>
</li>
<li><em>autofill</em>
<p>This is equivalent to clicking <strong>Auto Fill</strong> in the In-app Products UI. Can be
<code>true</code> or <code>false</code>. The syntax for specifying the <em>country</em>
and <em>price</em> varies depending on which <em>autofill</em> setting you use.</p>
<p>If <em>autofill</em> is set to <code>true</code>, you need to specify only the default
- price in your home currency and you must use this syntax:</p>
+ price in your home currency, and you must use this syntax:</p>
<p>"true","<em>default_price_in_home_currency</em>"
<p>If <em>autofill</em> is set to <code>false</code>, you need to specify a <em>country</em>
- and a <em>price</em> for each currency and you must use the following syntax:</p>
+ and a <em>price</em> for each currency, and you must use the following syntax:</p>
<p>"false", "<em>home_country</em>; <em>default_price_in_home_currency</em>; <em>country_2</em>;
<em>country_2_price</em>; <em>country_3</em>; <em>country_3_price</em>; ..."</p>
+ <p class="note"><strong>Note: </strong>If you use an <em>autofill</em> value of <code>false</code>
+ and set country prices manually, you must incorporate country-specific
+ pricing patterns, including tax rates, into the prices you provide.</p>
</li>
<li><em>country</em>
<p>The country for which you are specifying a price. You can only list countries that your
application is targeting. The country codes are two-letter uppercase
- ISO country codes (such as "US") as defined by
+ ISO country codes (such as "US"), as defined by
<a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">ISO 3166-2</a>.</p>
</li>
<li><em>price</em>
<p>This is equivalent to the Price in the In-app Products UI. The price must be specified in
micro-units. To convert a currency value to micro-units, you multiply the real value by
1,000,000.
- For example, if you want to sell an in-app item for $1.99 you specify 1990000 in the
+ For example, if you want to sell an in-app item for $1.99, you specify <code>1990000</code> in the
<em>price</em> field.</p>
</li>
</ul>
@@ -373,32 +425,352 @@
</tr>
</table>
-<p>To import the items that are specified in your CSV file, do the following:</p>
+<h2 id="pricing-template">
+ Pricing Templates
+</h2>
+
+<p>
+ If you sell multiple apps at the same price—or multiple in-app products at
+ the same price across one or more apps—you can add <em>pricing
+ templates</em>. These templates make it easier to manage shared prices.
+</p>
+
+<h3 id="add-pricing-template">
+ Adding a pricing template
+</h3>
+
+<p>
+ When creating a template, you can provide new pricing information, or you can
+ apply pricing information from an existing paid app or in-app product.
+</p>
+
+<div class="figure-right">
+ <figure id="fig-npt">
+ <img src="{@docRoot}images/in-app-billing/new_pricing_template.png"
+ srcset="{@docRoot}images/in-app-billing/new_pricing_template.png 1x, {@docRoot}images/in-app-billing/new_pricing_template_2x.png 2x"
+ width="400" alt="A template with the name Basic inventory uses a price of
+ USD 0.99.">
+ <figcaption>
+ <b>Figure 5. </b>The <em>Pricing template</em> page, where you add pricing
+ details for the new template you're creating.
+ </figcaption>
+ </figure>
+</div>
+
+<p>
+ To add a pricing template, do the following:
+</p>
<ol>
- <li><a href="http://play.google.com/apps/publish">Log in</a> to your publisher account.</li>
- <li>In the <strong>All Applications</strong> panel, click on the app
- name, then select <strong>In-app Products</strong>.</li>
- <li>On the In-app Products List page, click <strong>Import/Export</strong>
- > <strong>Import in-app products from CSV file</strong>, then select your
- CSV file.
- <p>The CSV file must be on your local computer or on a local disk that is connected to your
- computer.</p>
+ <li>
+ <a href="http://play.google.com/apps/publish">Log in</a> to your publisher
+ account.
</li>
- <li>Select the <strong>Overwrite</strong> checkbox if you want to overwrite existing items in
- your product list.
- <p>This option overwrites values of existing items only if the value of the <em>product_id</em>
- in the CSV file matches the In-app Product ID for an existing item in the product list.
- Overwriting does not delete items that are on a product list but not present in the CSV
- file.</p>
+
+ <li>In the <strong>Settings</strong> panel, select <strong>Pricing
+ template</strong>.
+ </li>
+
+ <li>
+ <p>
+ If you are adding your first pricing template, the <strong>Add a Pricing
+ Template</strong> banner appears. Select <strong>Add template</strong> to
+ create a new template. The <strong>Pricing Template</strong> page
+ appears.
+ </p>
+
+ <p>
+ Otherwise, you see a list of your pricing templates. Select <strong>New
+ pricing template</strong>. The <strong>Pricing Template</strong> page
+ appears.
+ </p>
+ </li>
+
+ <li>
+ <p>
+ Provide details about the template. These details include the name, the
+ price, and whether to include tax as part of your country-specific
+ prices.
+ </p>
+ <p>
+ Based on the price and tax option you provide, the Developer Console
+ generates prices for international currencies using today's exchange
+ rates and country-specific pricing patterns.
+ </p>
+ </li>
+ <li>Select <strong>Create template</strong> to finish adding the template.
</li>
</ol>
-<p>You can also export an existing product list to a CSV file by clicking <strong>Export to CSV
-</strong> on the In-app Product List page. This is useful if you have manually added items to
-a product list and you want to start managing the product list through a CSV file.</p>
+<h3 id="link-pricing-template">
+ Linking a pricing template
+</h3>
-<h3 id="billing-purchase-type">Choosing a Product Type</h3>
+<p>
+ You can link shared prices across paid apps or in-app products to a pricing
+ template. To complete the linking process, use either the template's
+ <em>Linked Items</em> tab or the Price section within a paid app or in-app
+ product's pricing page.
+</p>
+
+<p class="note">
+ <strong>Note:</strong> Since a subscription within your app has a constant
+ price, you cannot link a subscription with a pricing template. You can,
+ however, import the prices from a template and apply them to a new
+ subscription.
+</p>
+
+<h4>
+ Linking a pricing template or paid app to an in-app product
+</h4>
+
+<p>
+ After you create a pricing template, you can link the prices of in-app
+ products and paid apps to that template. After completing this linking
+ process, any changes you make to the pricing template are applied to the
+ prices of items that you've linked to the template.
+</p>
+
+<div class="figure-right">
+ <figure id="fig-lpt">
+ <img src="{@docRoot}images/in-app-billing/link_pricing_template.png"
+ width="700" alt="The Sleeping Potion in-app product is linked to the Basic
+ Inventory item, but the Invisibility Potion is not.">
+ <figcaption>
+ <b>Figure 6. </b>Use the Linked Items tab of the
+ <em>Pricing Template</em> page to change which in-app products and paid
+ apps are linked to a pricing template.
+ </figcaption>
+ </figure>
+</div>
+
+<p>
+ To link a pricing template to an in-app product, do the following:
+</p>
+
+<ol>
+ <li>
+ <a href="http://play.google.com/apps/publish">Log in</a> to your publisher
+ account.
+ </li>
+
+ <li>In the <strong>Settings</strong> panel, select <strong>Pricing
+ template</strong>. The <strong>Pricing Template</strong> page appears,
+ showing the list of pricing templates you have created for your account.
+ </li>
+
+ <li>Choose the pricing template that you want to link to an in-app product,
+ then select the <em>Linked Items</em> tab. A page appears, showing options to
+ link your pricing template to in-app products and paid apps.
+ </li>
+
+ <li>In the Link In-App Products section of the page, enter or choose the name
+ of an app. This app should contain the in-app product that you want to link
+ to your pricing template.
+ </li>
+
+ <li>Based on the app that you selected, you see a list of in-app products
+ that are active and are not yet linked to a pricing template. Select the
+ in-app product that you want to link to the pricing template by selecting the
+ <strong>Link</strong> button that appears in the same row as the in-app
+ product.
+ </li>
+
+ <li>The price of the in-app product is now linked to your pricing template.
+ Any changes you make to the prices within your pricing template affect the
+ prices of the linked in-app product.
+ </li>
+</ol>
+
+<p>
+ To link a pricing template to the price of a paid app, you follow a similar
+ process. On the pricing template's <em>Linked Items</em> tab, choose a paid
+ app in the Link Paid Apps section.
+</p>
+
+<h4>
+ Linking an in-app product or paid app with a pricing template
+</h4>
+
+<p>
+ After you create a paid app or in-app product, you can link its pricing
+ information to a pricing template.
+</p>
+
+<div class="figure-right">
+ <figure id="fig-spt">
+ <img src="{@docRoot}images/in-app-billing/select_pricing_template.png"
+ width="700" alt="">
+ <figcaption>
+ <b>Figure 7. </b>Choosing a pricing template to link to a particular
+ in-app product or paid app.
+ </figcaption>
+ </figure>
+</div>
+
+<p>
+ To link an in-app product to a pricing template, do the following:
+</p>
+
+<ol>
+ <li>
+ <a href="http://play.google.com/apps/publish">Log in</a> to your publisher
+ account.
+ </li>
+
+ <li>In the <strong>All Applications</strong> panel, choose the app that
+ contains the in-app product that you want to link to a pricing template.
+ </li>
+
+ <li>Within the app's panel, choose the <strong>In-app Products</strong>
+ sub-panel.
+ </li>
+
+ <li>Choose the in-app product that you want to link to a pricing template.
+ The <em>Managed Product Details</em> page appears.
+ </li>
+
+ <li>In the Pricing section, choose the pricing template that you want to link
+ to the price of this in-app product (see figure 7).
+ </li>
+
+ <li>The price of the in-app product is now linked to the pricing template you
+ selected. Any changes you make to the prices within your pricing template
+ affect the prices of this in-app product.
+ </li>
+</ol>
+
+<p>
+ To link the price of a paid app to a pricing template, you follow a similar
+ process within the app's <strong>Pricing & Distribution</strong>
+ sub-panel.
+</p>
+
+<h3 id="delete-linked-item">
+ Deleting an item that is linked to a pricing template
+</h3>
+
+<p>
+ As your app evolves, you may find it useful to remove older versions of
+ in-app products or apps, some of which may be linked to pricing templates. To
+ delete an in-app product or app that is linked to a pricing template, simply
+ remove it by completing the following steps. You don't need to unlink the
+ in-app product or app from the pricing template beforehand.
+</p>
+
+<h4>
+ Deleting an in-app product that is linked to a template
+</h4>
+
+<div class="figure-right">
+ <figure id="fig-diap">
+ <img src="{@docRoot}images/in-app-billing/delete_iap.png"
+ width="700" alt="">
+ <figcaption>
+ <b>Figure 8. </b>Deleting an in-app product that is linked to a pricing
+ template.
+ </figcaption>
+ </figure>
+</div>
+
+<p>
+ To delete an in-app product that is linked to a template, do the following:
+</p>
+
+<ol>
+ <li>
+ <a href="http://play.google.com/apps/publish">Log in</a> to your publisher
+ account.
+ </li>
+
+ <li>In the Google Play Developer Console, navigate to the app that contains
+ the in-app product you want to delete.
+ </li>
+
+ <li>Open the app's <strong>In-app Products</strong> page.
+ </li>
+
+ <li>Choose the in-app product that you want to delete.
+ </li>
+
+ <li>Select the button that indicates whether the in-app product is active or
+ inactive (enclosed in a box within figure 8). The drop-down menu includes a
+ <strong>Delete</strong> option.
+ </li>
+ <li>Select <strong>Delete</strong>, then select <strong>Yes</strong> in the
+ confirmation dialog that appears.
+ </li>
+</ol>
+
+<h4>
+ Deleting a paid app that is linked to a template
+</h4>
+
+<div class="figure-right">
+ <figure id="fig-upa">
+ <img src="{@docRoot}images/in-app-billing/unpublish_paid_app.png"
+ width="700" alt="">
+ <figcaption>
+ <b>Figure 9. </b>Unpublishing an app that has already been published and is
+ linked to a pricing template.
+ </figcaption>
+ </figure>
+</div>
+
+<p>
+ To delete a paid app that is linked to a template, do the following:
+</p>
+
+<ol>
+ <li>
+ <a href="http://play.google.com/apps/publish">Log in</a> to your publisher
+ account.
+ </li>
+
+ <li>In the Google Play Developer Console, choose the app that you want to
+ delete.
+ </li>
+
+ <li>Choose either <strong>Unpublish app</strong> (enclosed in a box within
+ figure 9) if you have already published the app, or
+ <strong>Delete app</strong> if your app is still in the "draft" state.
+ </li>
+ <li>Confirm your choice in the dialog that appears.
+ </li>
+</ol>
+
+<h3 id="delete-pricing-template">
+ Deleting a pricing template
+</h3>
+
+<p>
+ If you no longer need a pricing template, you can delete it by completing the
+ following steps:
+</p>
+
+<ol>
+ <li>
+ <a href="http://play.google.com/apps/publish">Log in</a> to your publisher
+ account.
+ </li>
+
+ <li>In the <strong>Settings</strong> panel, select <strong>Pricing
+ template</strong>. The <strong>Pricing Template</strong> page appears,
+ showing the list of pricing templates you have created for your account.
+ </li>
+
+ <li>Select the pricing template that you wish to delete.
+ </li>
+
+ <li>In the <em>Linked Items</em> tab on the pricing template details page,
+ unlink the pricing template from all in-app products and paid apps.
+ </li>
+
+ <li>Select <strong>Delete template</strong>.
+ </li>
+</ol>
+
+<h2 id="billing-purchase-type">Choosing a Product Type</h3>
<p>An item's product type controls how Google Play manages the purchase of the item. There are
several product types, including "managed per user account", "unmanaged," and "subscription." However,
@@ -408,8 +780,7 @@
<p>For details, refer to the documentation for <a
href="{@docRoot}google/play/billing/api.html#producttype">In-app Billing Version
-3</a> or <a href="{@docRoot}google/play/billing/v2/api.html#producttype">In-app
-Billing Version 2</a>.
+3</a>.
<h2 id="billing-refunds">Handling Refunds</h2>
@@ -496,26 +867,31 @@
<h3 id="license_key">Getting an app's license key</h3>
<p>The Google Play Developer Console provides a public licensing key for each
-app. To get the key for an app, follow these steps:</p>
+app.</p>
+
+<div class="figure-right">
+ <figure id="fig-bak">
+ <img src="{@docRoot}images/in-app-billing/billing_app_key.png"
+ width="700" alt="">
+ <figcaption>
+ <b>Figure 10. </b>You can find the license key for each app in the
+ <strong>Services & APIs</strong> panel.
+ </figcaption>
+ </figure>
+</div>
+
+<p>To get the key for an app, follow these steps:</p>
<ol>
<li>Open the <strong>All Applications</strong> panel.</li>
<li>Click on the app name, then select <strong>Services & APIs</strong>.</li>
<li>Scroll down to the <strong>Your License Key for this Application</strong>
-field to locate the key for the app, as shown in the figure below.</li>
+field to locate the key for the app, as shown in figure 10.</li>
</ol>
<p>Previously, the Developer Console provided a single public key per developer
account. To transition apps to the new per-app public key, the Developer Console
set the app-specific key as the former developer key. This ensures compatibility
for apps that depend on the (former) developer key. </p>
-<div style="margin:1em;">
-<img style="border:1px solid #ddd;padding-bottom:.5em" src="{@docRoot}images/in-app-billing/billing_app_key.png" xheight="510" id="figure4" />
-<p class="img-caption" style="padding-left:.5em;">
- <strong>Figure 4.</strong> You can find the license key for each app in the
- <strong>Services & APIs</strong> panel.
-</p>
-</div>
-
<h2 id="billing-support">Where to Get Support</h2>
<p>If you have questions or encounter problems while implementing In-app Billing, contact the
diff --git a/docs/html/google/play/billing/billing_overview.jd b/docs/html/google/play/billing/billing_overview.jd
index cc56468..2954a83 100644
--- a/docs/html/google/play/billing/billing_overview.jd
+++ b/docs/html/google/play/billing/billing_overview.jd
@@ -122,7 +122,7 @@
<h2 id="console">Google Play Developer Console</h2>
<p>The Developer Console is where you can publish your
-In-app Billing application, and manage the various in-app products that are
+In-app Billing application and manage the various in-app products that are
available for purchase from your application.</p>
<p>You can create a product list of
digital goods that are associated with your application, including items for
@@ -130,6 +130,14 @@
information such as the item’s unique product ID (also called its SKU), product
type, pricing, description, and how Google Play should handle and track
purchases for that product.</p>
+<p>If you sell several of your apps or in-app products at the same price, you
+can add <em>pricing templates</em> to manage these price points from a
+centralized location. When using pricing templates, you can include the local
+tax within the prices you provide, or you can provide prices and have the system
+add local taxes to these prices. You can make changes to the prices in your
+templates—such as refreshing the exchange rates for certain
+countries—and your changes are applied to the apps and in-app products
+that you link to the template.</p>
<p>You can also create test accounts to authorize
access for testing applications that are unpublished.</p>
<p>To learn how to use the Developer Console to configure your in-app
diff --git a/docs/html/google/play/billing/billing_reference.jd b/docs/html/google/play/billing/billing_reference.jd
index 45ec785..95ab8e5 100644
--- a/docs/html/google/play/billing/billing_reference.jd
+++ b/docs/html/google/play/billing/billing_reference.jd
@@ -139,7 +139,8 @@
<td>{@code price_amount_micros}</td>
<td>Price in micro-units, where 1,000,000 micro-units equal one unit of the
currency. For example, if {@code price} is {@code "€7.99"}, {@code
- price_amount_micros} is {@code "7990000"}.</td>
+ price_amount_micros} is {@code "7990000"}. This value represents the
+ localized, rounded price for a particular currency.</td>
</tr>
<tr>
<td>{@code price_currency_code}</td>
diff --git a/docs/html/google/play/billing/index.jd b/docs/html/google/play/billing/index.jd
index b2e9fe4..795aceb 100644
--- a/docs/html/google/play/billing/index.jd
+++ b/docs/html/google/play/billing/index.jd
@@ -14,6 +14,16 @@
<div class="sidebox">
<h2><strong>New in In-App Billing</strong></h2>
<ul>
+ <li><strong>Rounded Pricing</strong>—After developers set prices for the
+ apps and in-app products that they distribute to multiple countries, the
+ system automatically sets local prices for different currencies using
+ today’s exchange rates and country-specific pricing patterns. To satisfy
+ specific pricing needs, developers can also adjust these prices manually.</li>
+ <li><strong>Pricing Templates</strong>—Developers can add pricing
+ templates and link these templates to app prices or in-app product prices.
+ These templates include local prices across all markets. By using a
+ template, developers can apply price changes in bulk to all items linked to
+ that template.</li>
<li><strong>In-app Promotions</strong>—Developers can create promo codes
which users can redeem for content or features.</li>
<li><strong>Prorated Subscription Prices</strong>—Content providers can
@@ -29,33 +39,13 @@
<li><strong>Subscription Upgrade/Downgrade</strong>—A user can
subscribe to a higher or lower tier of subscription while their current
subscription is active. The old subscription is canceled, and the unused
- portion is applied on a pro-rated basis to the new subscription.</li>
+ portion is applied on a prorated basis to the new subscription.</li>
<li><strong>Manual Subscription Renewal</strong>—A user can purchase
a subscription at the current rate while their existing subscription is
still active. The existing subscription is extended by the appropriate
period.</li>
<li><strong>IAB Sandbox</strong>—The In-app Billing Sandbox now supports
testing subscription purchases.</li>
- <li><strong>As of January 2015, the In-App Billing v2 API is shut down.
- If your app is still using In-app Billing
- v2, please migrate to the v3 API as soon as possible.</strong></li>
- <li><strong>Seasonal subscriptions</strong>—You can now set up a
- recurring <a href="billing_subscriptions.html#user-billing">seasonal
- subscription</a> that starts and ends on the same date each year (for
- example, a sports subscription that starts every September 1 and ends every
- April 10).</li>
- <li><strong>Deferred subscription billing</strong>—You can
- <a href="billing_subscriptions.html#deferred-billing">defer</a> a
- subscriber's next billing date until the date you choose. The user still has
- access to the content but is not charged during the deferral period.</li>
- <li><strong>Weekly subscriptions</strong>—You can now set up a
- recurring <a href="billing_subscriptions.html#user-billing">subscription</a>
- that bills the user every week.</li>
- <li><strong>Payment grace period</strong>—If a subscriber misses a
- subscription payment due to an expired credit card, you can define a
- <a href="billing_subscriptions.html#grace-period">grace period</a>
- to the continue the subscription until payment is successful.</li>
-
</ul>
</div>
</div>
@@ -101,5 +91,5 @@
implementation.</dd>
<dt><strong><a href="{@docRoot}google/play/billing/billing_admin.html">Administering
In-app Billing</a></strong></dt>
- <dd>Learn how to set up your product list, register test accounts, and handle refunds.</dd>
+ <dd>Learn how to set up your product list, add pricing templates, register test accounts, and handle refunds.</dd>
</dl>
diff --git a/docs/html/guide/topics/manifest/permission-element.jd b/docs/html/guide/topics/manifest/permission-element.jd
index 4bb5f6a..d8dbf40 100644
--- a/docs/html/guide/topics/manifest/permission-element.jd
+++ b/docs/html/guide/topics/manifest/permission-element.jd
@@ -10,70 +10,80 @@
android:<a href="#label">label</a>="<i>string resource</i>"
android:<a href="#nm">name</a>="<i>string</i>"
android:<a href="#pgroup">permissionGroup</a>="<i>string</i>"
- android:<a href="#plevel">protectionLevel</a>=["normal" | "dangerous" |
+ android:<a href="#plevel">protectionLevel</a>=["normal" | "dangerous" |
"signature" | "signatureOrSystem"] /></pre></dd>
<dt>contained in:</dt>
-<dd><code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code></dd>
+<dd><code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"
+ ><manifest></a></code></dd>
<dt>description:</dt>
-<dd itemprop="description">Declares a security permission that can be used to limit access
-to specific components or features of this or other applications.
-See the <a href="{@docRoot}guide/topics/manifest/manifest-intro.html#perms">Permissions</a>
-section in the introduction,
-and the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
-document for more information on how permissions work.</dd>
+<dd itemprop="description">Declares a security permission that can be used to
+limit access to specific components or features of this or other applications.
+See the <a href="{@docRoot}guide/topics/manifest/manifest-intro.html#perms"
+>Permissions</a> section in the introduction, and the <a
+href="{@docRoot}guide/topics/security/security.html">Security and
+Permissions</a> document for more information on how permissions work.</dd>
<dt>attributes:</dt>
<dd><dl class="attr">
<dt><a name="desc"></a>{@code android:description}</dt>
-<dd>A user-readable description of the permission, longer and more
-informative than the label. It may be displayed to explain the
-permission to the user — for example, when the user is asked
+<dd>A user-readable description of the permission, longer and more
+informative than the label. It may be displayed to explain the
+permission to the user — for example, when the user is asked
whether to grant the permission to another application.
<p>
-This attribute must be set as a reference to a string resource;
+This attribute must be set as a reference to a string resource;
unlike the {@code label} attribute, it cannot be a raw string.
</p></dd>
<dt><a name="icon"></a>{@code android:icon}</dt>
-<dd>A reference to a drawable resource for an icon that represents the
+<dd>A reference to a drawable resource for an icon that represents the
permission.</dd>
<dt><a name="label"></a>{@code android:label}</dt>
-<dd>A name for the permission, one that can be displayed to users.
+<dd>A name for the permission, one that can be displayed to users.
<p>
-As a convenience, the label can be directly set
-as a raw string while you're developing the application. However,
-when the application is ready to be published, it should be set as a
-reference to a string resource, so that it can be localized like other
+As a convenience, the label can be directly set
+as a raw string while you're developing the application. However,
+when the application is ready to be published, it should be set as a
+reference to a string resource, so that it can be localized like other
strings in the user interface.
</p></dd>
<dt><a name="nm"></a>{@code android:name}</dt>
-<dd>The name of the permission. This is the name that will be used in
-code to refer to the permission — for example, in a
-<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> element and the
+<dd>The name of the permission. This is the name that will be used in
+code to refer to the permission — for example, in a
+<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"
+ ><uses-permission></a></code> element and the
{@code permission} attributes of application components.
-<p>
-The name must be unique, so it should use Java-style scoping —
-for example, "{@code com.example.project.PERMITTED_ACTION}".
-</p></dd>
+
+<p class="note">
+ <strong>Note:</strong> The system does not allow multiple packages to declare
+ a permission with the same name, unless all the packages are signed with the
+ same certificate. If a package declares a permission, the system does not permit
+ the user to install other packages with the same permission name, unless
+ those packages are signed with the same certificate as the first package. To
+ avoid naming collisions, we recommend using reverse-domain-style naming for custom
+ permissions, for example <code>com.example.myapp.ENGAGE_HYPERSPACE</code>.
+</p>
+</dd>
<dt><a name="pgroup"></a>{@code android:permissionGroup}</dt>
-<dd>Assigns this permission to a group. The value of this attribute is
-the name of the group, which must be declared with the
-<code><a href="{@docRoot}guide/topics/manifest/permission-group-element.html"><permission-group></a></code> element in this
+<dd>Assigns this permission to a group. The value of this attribute is
+the name of the group, which must be declared with the
+<code><a href="{@docRoot}guide/topics/manifest/permission-group-element.html"
+ ><permission-group></a></code> element in this
or another application. If this attribute is not set, the permission
does not belong to a group.</dd>
<dt><a name="plevel"></a>{@code android:protectionLevel}</dt>
<dd>Characterizes the potential risk implied in the permission and
indicates the procedure the system should follow when determining
-whether or not to grant the permission to an application requesting it.
+whether or not to grant the permission to an application requesting it.
The value can be set to one of the following strings:
<table>
@@ -82,9 +92,9 @@
<th>Meaning</th>
</tr><tr>
<td>"{@code normal}"</td>
- <td>The default value. A lower-risk permission that gives requesting
- applications access to isolated application-level features, with
- minimal risk to other applications, the system, or the user.
+ <td>The default value. A lower-risk permission that gives requesting
+ applications access to isolated application-level features, with
+ minimal risk to other applications, the system, or the user.
The system automatically grants this type
of permission to a requesting application at installation, without
asking for the user's explicit approval (though the user always
@@ -109,11 +119,11 @@
asking for the user's explicit approval.
</tr><tr>
<td>"{@code signatureOrSystem}"</td>
- <td>A permission that the system grants only to applications that are
+ <td>A permission that the system grants only to applications that are
in the Android system image <em>or</em> that are signed with the same
- certificate as the application that declared the permission. Please avoid using this
- option, as the {@code signature} protection level should be sufficient
- for most needs and works regardless of exactly where applications are
+ certificate as the application that declared the permission. Please avoid using this
+ option, as the {@code signature} protection level should be sufficient
+ for most needs and works regardless of exactly where applications are
installed. The "{@code signatureOrSystem}"
permission is used for certain special situations where multiple
vendors have applications built into a system image and need
diff --git a/docs/html/guide/topics/security/permissions.jd b/docs/html/guide/topics/security/permissions.jd
index f7f70b3..e7bf760 100644
--- a/docs/html/guide/topics/security/permissions.jd
+++ b/docs/html/guide/topics/security/permissions.jd
@@ -18,6 +18,7 @@
</li>
<li><a href="#defining">Defining and Enforcing Permissions</a>
<ol>
+ <li><a href="#custom-recommendations">Custom permission recommendations</a></li>
<li><a href="#manifest">...in AndroidManifest.xml</a></li>
<li><a href="#broadcasts">...when Sending Broadcasts</a></li>
<li><a href="#enforcement">Other Permission Enforcement</a></li>
@@ -540,17 +541,19 @@
<a name="declaring"></a>
<h2 id="defining">Defining and Enforcing Permissions</h2>
-<p>To enforce your own permissions, you must first declare them in your
-<code>AndroidManifest.xml</code> using one or more
-<code>{@link android.R.styleable#AndroidManifestPermission <permission>}</code>
-tags.</p>
+<p>
+ To enforce your own permissions, you must first declare them in your
+ <code>AndroidManifest.xml</code> using one or more <a href=
+ "{@docRoot}guide/topics/manifest/permission-element.html"><code><permission></code></a>
+ elements.
+</p>
<p>For example, an application that wants to control who can start one
of its activities could declare a permission for this operation as follows:</p>
<pre><manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.me.app.myapp" >
- <permission android:name="com.me.app.myapp.permission.DEADLY_ACTIVITY"
+ package="com.example.myapp" >
+ <permission android:name="com.example.myapp.permission.DEADLY_ACTIVITY"
android:label="@string/permlab_deadlyActivity"
android:description="@string/permdesc_deadlyActivity"
android:permissionGroup="android.permission-group.COST_MONEY"
@@ -558,50 +561,65 @@
...
</manifest></pre>
+<p class="note">
+ <strong>Note:</strong> The system does not allow multiple packages to declare
+ a permission with the same name, unless all the packages are signed with the
+ same certificate. If a package declares a permission, the system does not permit
+ the user to install other packages with the same permission name, unless
+ those packages are signed with the same certificate as the first package. To
+ avoid naming collisions, we recommend using reverse-domain-style naming for custom
+ permissions, for example <code>com.example.myapp.ENGAGE_HYPERSPACE</code>.
+</p>
+
<p>The {@link android.R.styleable#AndroidManifestPermission_protectionLevel
-<protectionLevel>} attribute is required, telling the system how the
+protectionLevel} attribute is required, telling the system how the
user is to be informed of applications requiring the permission, or who is
allowed to hold that permission, as described in the linked documentation.</p>
-<p>The {@link android.R.styleable#AndroidManifestPermission_permissionGroup
-<permissionGroup>} attribute is optional, and only used to help the system display
-permissions to the user. You will usually want to set this to either a standard
-system group (listed in {@link android.Manifest.permission_group
-android.Manifest.permission_group}) or in more rare cases to one defined by
-yourself. It is preferred to use an existing group, as this simplifies the
-permission UI shown to the user.</p>
+<p>
+ The <a href=
+ "{@docRoot}guide/topics/manifest/permission-group-element.html"
+ ><code>android:permissionGroup</code></a>
+ attribute is optional, and only used to help the system display permissions
+ to the user. In most cases you will want to set this to a standard system
+ group (listed in {@link android.Manifest.permission_group
+ android.Manifest.permission_group}), although you can define a group yourself.
+ It is preferable to use an existing group, as this simplifies the
+ permission UI shown to the user.
+</p>
-<p>Note that both a label and description should be supplied for the
-permission. These are string resources that can be displayed to the user when
+<p>You need to supply both a label and description for the
+permission. These are string resources that the user can see when
they are viewing a list of permissions
(<code>{@link android.R.styleable#AndroidManifestPermission_label android:label}</code>)
or details on a single permission (
<code>{@link android.R.styleable#AndroidManifestPermission_description android:description}</code>).
-The label should be short, a few words
+The label should be short; a few words
describing the key piece of functionality the permission is protecting. The
-description should be a couple sentences describing what the permission allows
-a holder to do. Our convention for the description is two sentences, the first
-describing the permission, the second warning the user of what bad things
-can happen if an application is granted the permission.</p>
+description should be a couple of sentences describing what the permission allows
+a holder to do. Our convention is a two-sentence description:
+the first sentence describes the permission, and the second sentence warns the
+user of the type of things that can go wrong if an application is granted the
+permission.</p>
<p>Here is an example of a label and description for the CALL_PHONE
permission:</p>
<pre>
- <string name="permlab_callPhone">directly call phone numbers</string>
- <string name="permdesc_callPhone">Allows the application to call
- phone numbers without your intervention. Malicious applications may
- cause unexpected calls on your phone bill. Note that this does not
- allow the application to call emergency numbers.</string>
+<string name="permlab_callPhone">directly call phone numbers</string>
+<string name="permdesc_callPhone">Allows the application to call
+ phone numbers without your intervention. Malicious applications may
+ cause unexpected calls on your phone bill. Note that this does not
+ allow the application to call emergency numbers.</string>
</pre>
-<p>You can look at the permissions currently defined in the system with the
+<p>You can view at the permissions currently defined in the system using the
Settings app and the shell command <code>adb shell pm list permissions</code>.
-To use the Settings app, go to Settings > Applications. Pick an app and
+To use the Settings app, go to <b>Settings</b> > <b>Applications</b>. Pick an app and
scroll down to see the permissions that the app uses. For developers, the adb '-s'
option displays the permissions in a form similar to how the user will see them:</p>
-<pre>
+<pre class="no-pretty-print">
$ adb shell pm list permissions -s
All Permissions:
@@ -615,14 +633,53 @@
...</pre>
+<h3 id="custom-recommendations">
+ Custom permission recommendations
+</h3>
+
+<p>
+ Apps can define their own custom permissions and request custom permissions
+ from other apps by defining <a href=
+ "{@docRoot}guide/topics/manifest/uses-permission-element.html"><code
+ ><uses-permission></code></a> elements.
+ However, you should carefully assess whether it is necessary for your app to
+ do so.
+</p>
+
+<ul>
+ <li>If you are designing a suite of apps that expose functionality to one
+ another, try to design the apps so that each permission is defined only once.
+ You must do this if the apps are not all signed with the same certificate.
+ Even if the apps are all signed with the same certificate, it's a
+ best practice to define each permission once only.
+ </li>
+
+ <li>If the functionality is only available to apps signed with the same
+ signature as the providing app, you may be able to avoid defining custom
+ permissions by using signature checks. When one of your apps makes a request
+ of another of your apps, the second app can verify that both apps are signed
+ with the same certificate before complying with the request.
+ </li>
+
+ <li>If you are developing a suite of apps runs only on your own
+ devices, you should develop and install a package that
+ manages permissions for all the apps in the suite. This package does not need
+ to provide any services itself. It just declares all the permissions, and the
+ other apps in the suite request those permissions with the <a href=
+ "{@docRoot}guide/topics/manifest/uses-permission-element.html"><code
+ ><uses-permission></code></a>
+ element.
+ </li>
+</ul>
+
<a name="manifest"></a>
<h3>Enforcing Permissions in AndroidManifest.xml</h3>
-<p>High-level permissions restricting access to entire components of the
-system or application can be applied through your
-<code>AndroidManifest.xml</code>. All that this requires is including an {@link
+<p>TYou can apply high-level permissions restricting access to entire components
+of the system or application through your
+<code>AndroidManifest.xml</code>. To do this, include an {@link
android.R.attr#permission android:permission} attribute on the desired
-component, naming the permission that will be used to control access to
+component, naming the permission that controls access to
it.</p>
<p><strong>{@link android.app.Activity}</strong> permissions
diff --git a/docs/html/images/in-app-billing/add_new_product.png b/docs/html/images/in-app-billing/add_new_product.png
new file mode 100644
index 0000000..2281ec0
--- /dev/null
+++ b/docs/html/images/in-app-billing/add_new_product.png
Binary files differ
diff --git a/docs/html/images/in-app-billing/billing_add.png b/docs/html/images/in-app-billing/billing_add.png
deleted file mode 100644
index 1ca448d..0000000
--- a/docs/html/images/in-app-billing/billing_add.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/images/in-app-billing/billing_app_key.png b/docs/html/images/in-app-billing/billing_app_key.png
index bff8500..4c5300e 100644
--- a/docs/html/images/in-app-billing/billing_app_key.png
+++ b/docs/html/images/in-app-billing/billing_app_key.png
Binary files differ
diff --git a/docs/html/images/in-app-billing/billing_list_form_2.png b/docs/html/images/in-app-billing/billing_list_form_2.png
deleted file mode 100644
index 33f17c9..0000000
--- a/docs/html/images/in-app-billing/billing_list_form_2.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/images/in-app-billing/billing_product_list.png b/docs/html/images/in-app-billing/billing_product_list.png
deleted file mode 100644
index dabdcb9..0000000
--- a/docs/html/images/in-app-billing/billing_product_list.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/images/in-app-billing/delete_iap.png b/docs/html/images/in-app-billing/delete_iap.png
new file mode 100644
index 0000000..bbaea9e
--- /dev/null
+++ b/docs/html/images/in-app-billing/delete_iap.png
Binary files differ
diff --git a/docs/html/images/in-app-billing/edit_local_prices.png b/docs/html/images/in-app-billing/edit_local_prices.png
new file mode 100644
index 0000000..2b76548
--- /dev/null
+++ b/docs/html/images/in-app-billing/edit_local_prices.png
Binary files differ
diff --git a/docs/html/images/in-app-billing/in_app_products.png b/docs/html/images/in-app-billing/in_app_products.png
new file mode 100644
index 0000000..04031cc
--- /dev/null
+++ b/docs/html/images/in-app-billing/in_app_products.png
Binary files differ
diff --git a/docs/html/images/in-app-billing/link_pricing_template.png b/docs/html/images/in-app-billing/link_pricing_template.png
new file mode 100644
index 0000000..cf762a8
--- /dev/null
+++ b/docs/html/images/in-app-billing/link_pricing_template.png
Binary files differ
diff --git a/docs/html/images/in-app-billing/new_managed_product.png b/docs/html/images/in-app-billing/new_managed_product.png
new file mode 100644
index 0000000..bdccc96
--- /dev/null
+++ b/docs/html/images/in-app-billing/new_managed_product.png
Binary files differ
diff --git a/docs/html/images/in-app-billing/new_pricing_template.png b/docs/html/images/in-app-billing/new_pricing_template.png
new file mode 100644
index 0000000..8525787
--- /dev/null
+++ b/docs/html/images/in-app-billing/new_pricing_template.png
Binary files differ
diff --git a/docs/html/images/in-app-billing/new_pricing_template_2x.png b/docs/html/images/in-app-billing/new_pricing_template_2x.png
new file mode 100644
index 0000000..ce4094b
--- /dev/null
+++ b/docs/html/images/in-app-billing/new_pricing_template_2x.png
Binary files differ
diff --git a/docs/html/images/in-app-billing/select_pricing_template.png b/docs/html/images/in-app-billing/select_pricing_template.png
new file mode 100644
index 0000000..fa8c7b6
--- /dev/null
+++ b/docs/html/images/in-app-billing/select_pricing_template.png
Binary files differ
diff --git a/docs/html/images/in-app-billing/unpublish_paid_app.png b/docs/html/images/in-app-billing/unpublish_paid_app.png
new file mode 100644
index 0000000..a36d8ce
--- /dev/null
+++ b/docs/html/images/in-app-billing/unpublish_paid_app.png
Binary files differ
diff --git a/docs/html/jd_collections.js b/docs/html/jd_collections.js
index 7ed40c0..04e7a3d 100644
--- a/docs/html/jd_collections.js
+++ b/docs/html/jd_collections.js
@@ -172,17 +172,17 @@
"wear/preview/landing": {
"title": "",
"resources": [
- "preview/api-overview.html",
- "preview/download.html",
- "preview/setup-sdk.html"
+ "wear/preview/api-overview.html",
+ "wear/preview/downloads.html",
+ "wear/preview/start.html"
]
},
"wear/preview/landing/resources": {
"title": "",
"resources": [
- "design/wear/index.html",
- "training/building-wearables.html",
- "training/wearables/ui/index.html"
+ "wear/preview/features/complications.html",
+ "wear/preview/features/notifications.html",
+ "wear/preview/features/ui-nav-actions.html"
]
},
"google/landing/services": {
@@ -375,7 +375,7 @@
"distribute/essentials/quality/tv.html",
"distribute/essentials/quality/wear.html",
"distribute/essentials/quality/auto.html",
- "https://developers.google.com/edu/guidelines"
+ "distribute/essentials/quality/billions.html"
]
},
"distribute/essentials/zhcn": {
@@ -506,7 +506,7 @@
"distribute/essentials/quality/wear.html",
"distribute/essentials/quality/tv.html",
"distribute/essentials/quality/auto.html",
- "https://developers.google.com/edu/guidelines"
+ "distribute/essentials/quality/billions.html"
]
},
"distribute/essentials/tools": {
@@ -1007,6 +1007,44 @@
"google/play/filters.html"
]
},
+ "distribute/essentials/billionsquality/connectivity": {
+ "title": "",
+ "resources": [
+ "training/basics/network-ops/managing.html",
+ "training/monitoring-device-state/connectivity-monitoring.html",
+ "guide/topics/providers/content-providers.html"
+ ]
+ },
+ "distribute/essentials/billionsquality/capability": {
+ "title": "",
+ "resources": [
+ "guide/practices/screens_support.html",
+ "training/multiscreen/screendensities.html",
+ "training/articles/memory.html"
+ ]
+ },
+ "distribute/essentials/billionsquality/cost": {
+ "title": "",
+ "resources": [
+ "https://medium.com/@wkalicinski/smallerapk-part-6-image-optimization-zopfli-webp-4c462955647d#.23hlddo3x",
+ "training/basics/network-ops/managing.html"
+ ]
+ },
+ "distribute/essentials/billionsquality/consumption": {
+ "title": "",
+ "resources": [
+ "training/efficient-downloads/efficient-network-access.html",
+ "training/monitoring-device-state/index.html"
+ ]
+ },
+ "distribute/essentials/billionsquality/content": {
+ "title": "",
+ "resources": [
+ "training/material/animations.html#Touch",
+ "training/articles/perf-anr.html",
+ "training/improving-layouts/index.html"
+ ]
+ },
"distribute/essentials/tabletguidelines": {
"title": "",
"resources": [
diff --git a/docs/html/jd_extras.js b/docs/html/jd_extras.js
index d176883..685394f 100644
--- a/docs/html/jd_extras.js
+++ b/docs/html/jd_extras.js
@@ -32,6 +32,17 @@
"type":"medium"
},
{
+ "title":"SmallerAPK, Part 6: Image optimization, Zopfli & WebP",
+ "category":"",
+ "summary":"Series of posts on minimizing your APK size.",
+ "url":"https://medium.com/@wkalicinski/smallerapk-part-6-image-optimization-zopfli-webp-4c462955647d#.23hlddo3x",
+ "group":"",
+ "keywords": [],
+ "tags": [],
+ "image":"https://cdn-images-1.medium.com/max/2000/1*chMiA9mGa_FBUOoesHHk3Q.png",
+ "type":"medium"
+ },
+ {
"title":"Measure your app’s user acquisition channels",
"titleFriendly":"",
"summary":"Get details on how to use the Developer Console User Acquisitions reports to discover where your users come from.",
@@ -999,6 +1010,19 @@
"lang": "en",
"group": "",
"tags": [],
+ "url": "training/material/animations.html#Touch",
+ "timestamp": 1194884220000,
+ "image": null,
+ "title": "Customize Touch Feedback",
+ "summary": "Provide visual confirmation when users interact with your UI.",
+ "keywords": [],
+ "type": "develop",
+ "category": "guide"
+ },
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
"url": "guide/topics/manifest/uses-feature-element.html#testing",
"timestamp": 1194884220000,
"image": null,
diff --git a/docs/html/jd_extras_en.js b/docs/html/jd_extras_en.js
index 39a7e72..b351aa5 100644
--- a/docs/html/jd_extras_en.js
+++ b/docs/html/jd_extras_en.js
@@ -277,6 +277,18 @@
"type":"medium"
},
{
+ "title":"SmallerAPK, Part 6: Image optimization, Zopfli & WebP",
+ "category":"",
+ "summary":"Series of posts on minimizing your APK size.",
+ "url":"https://medium.com/@wkalicinski/smallerapk-part-6-image-optimization-zopfli-webp-4c462955647d#.23hlddo3x",
+ "group":"",
+ "keywords": [],
+ "tags": [],
+ "image":"https://cdn-images-1.medium.com/max/2000/1*chMiA9mGa_FBUOoesHHk3Q.png",
+ "type":"medium"
+ },
+
+ {
"title":"Measure your app’s user acquisition channels",
"category":"google",
"summary":"Get details on how to use the Developer Console User Acquisitions reports to discover where your users come from.",
@@ -645,7 +657,54 @@
"image":"https://i1.ytimg.com/vi/K2dodTXARqc/maxresdefault.jpg",
"type":"video"
},
-
+ {
+ "title":"Instant Run: An Android Tool Time Deep Dive",
+ "category":"",
+ "summary":"Instant Run is an Android Studio feature that significantly reduces the time for building and deploying incremental code changes during your coding / testing / debugging lifecycle.",
+ "url":"https://www.youtube.com/watch?v=StqAZ1OQbqA&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+ "group":"",
+ "keywords": ["studio", "tools"],
+ "tags": [
+ ],
+ "image":"https://i1.ytimg.com/vi/StqAZ1OQbqA/maxresdefault.jpg",
+ "type":"youtube"
+ },
+ {
+ "title":"What’s New in Android Studio 2.1",
+ "category":"",
+ "summary":"Android Studio 2.1 is required to try out new features and APIs of the Android N developer preview including the new Jack compiler and Java 8 language support. It also includes performance improvements to Instant Run, and a number of bug fixes and stability improvements.",
+ "url":"https://www.youtube.com/watch?v=ZOz_yr8Yxq8&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+ "group":"",
+ "keywords": ["studio", "tools"],
+ "tags": [
+ ],
+ "image":"https://i1.ytimg.com/vi/ZOz_yr8Yxq8/maxresdefault.jpg",
+ "type":"youtube"
+ },
+ {
+ "title":"10 Things You Didn’t Know You Could Do",
+ "category":"",
+ "summary":"This Android Tool Time pro-tip roundup with Reto Meier shows off expert Android Studio tips designed to help you write less code, and make every keystroke count",
+ "url":"https://www.youtube.com/watch?v=eOV2owswDkE&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+ "group":"",
+ "keywords": ["studio", "tools"],
+ "tags": [
+ ],
+ "image":"https://i1.ytimg.com/vi/eOV2owswDkE/maxresdefault.jpg",
+ "type":"youtube"
+ },
+ {
+ "title":"Live Templates in Android Studio",
+ "category":"",
+ "summary":"Android Tool Time Protip: Use and create your own Live Templates in Android Studio to write more code with less keystrokes using Live Templates to insert common, templatized code snippets.",
+ "url":"https://www.youtube.com/watch?v=4rI4tTd7-J8&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+ "group":"",
+ "keywords": ["studio", "tools"],
+ "tags": [
+ ],
+ "image":"https://i1.ytimg.com/vi/4rI4tTd7-J8/maxresdefault.jpg",
+ "type":"youtube"
+ },
{
"title":"Google Play Services 7.5",
"category":"",
@@ -1162,6 +1221,20 @@
"lang": "en",
"group": "",
"tags": [],
+ "url": "training/material/animations.html#Touch",
+ "timestamp": 1194884220000,
+ "image": null,
+ "title": "Customize Touch Feedback",
+ "summary": "Provide visual confirmation when users interact with your UI.",
+ "keywords": [],
+ "type": "develop",
+ "category": "guide"
+ },
+
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
"url": "guide/topics/manifest/uses-feature-element.html#testing",
"timestamp": 1194884220000,
"image": null,
@@ -3685,11 +3758,10 @@
"develop/landing/tools": {
"title": "",
"resources": [
- "https://www.youtube.com/watch?v=K2dodTXARqc&list=PLWz5rJ2EKKc8I9gHTMh5yKkwRRGE8BjbQ",
- "https://www.youtube.com/watch?v=cD7NPxuuXYY&list=PLWz5rJ2EKKc8I9gHTMh5yKkwRRGE8BjbQ",
- "https://www.youtube.com/watch?v=JLLnhwtDoHw&list=PLWz5rJ2EKKc8I9gHTMh5yKkwRRGE8BjbQ",
- "https://www.youtube.com/watch?v=2I6fuD20qlY&list=PLWz5rJ2EKKc8I9gHTMh5yKkwRRGE8BjbQ",
- "https://www.youtube.com/watch?v=5Be2mJzP-Uw&list=PLWz5rJ2EKKc9e0d55YHgJFHXNZbGHEXJX"
+ "https://www.youtube.com/watch?v=StqAZ1OQbqA&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+ "https://www.youtube.com/watch?v=ZOz_yr8Yxq8&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+ "https://www.youtube.com/watch?v=eOV2owswDkE&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+ "https://www.youtube.com/watch?v=4rI4tTd7-J8&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa"
]
},
"google/landing/services": {
@@ -3825,7 +3897,7 @@
"distribute/essentials/quality/tv.html",
"distribute/essentials/quality/wear.html",
"distribute/essentials/quality/auto.html",
- "https://developers.google.com/edu/guidelines"
+ "distribute/essentials/quality/billions.html",
]
},
"distribute/users": {
@@ -3946,6 +4018,7 @@
"distribute/essentials/quality/wear.html",
"distribute/essentials/quality/tv.html",
"distribute/essentials/quality/auto.html",
+ "distribute/essentials/quality/billions.html",
"https://developers.google.com/edu/guidelines"
]
},
@@ -4403,6 +4476,44 @@
"distribute/tools/promote/device-art.html"
]
},
+ "distribute/essentials/billionsquality/connectivity": {
+ "title": "",
+ "resources": [
+ "training/basics/network-ops/managing.html",
+ "training/monitoring-device-state/connectivity-monitoring.html",
+ "guide/topics/providers/content-providers.html"
+ ]
+ },
+ "distribute/essentials/billionsquality/capability": {
+ "title": "",
+ "resources": [
+ "guide/practices/screens_support.html",
+ "training/multiscreen/screendensities.html",
+ "training/articles/memory.html"
+ ]
+ },
+ "distribute/essentials/billionsquality/cost": {
+ "title": "",
+ "resources": [
+ "https://medium.com/@wkalicinski/smallerapk-part-4-multi-apk-through-abi-and-density-splits-477083989006#.23hlddo3x",
+ "training/basics/network-ops/managing.html"
+ ]
+ },
+ "distribute/essentials/billionsquality/consumption": {
+ "title": "",
+ "resources": [
+ "training/efficient-downloads/efficient-network-access.html",
+ "training/monitoring-device-state/index.html"
+ ]
+ },
+ "distribute/essentials/billionsquality/content": {
+ "title": "",
+ "resources": [
+ "training/material/animations.html#Touch",
+ "training/articles/perf-anr.html",
+ "training/improving-layouts/index.html"
+ ]
+ },
"distribute/getusers/notifications": {
"title": "",
"resources": [
diff --git a/docs/html/ndk/guides/_book.yaml b/docs/html/ndk/guides/_book.yaml
index 287a92d..c015d7a 100644
--- a/docs/html/ndk/guides/_book.yaml
+++ b/docs/html/ndk/guides/_book.yaml
@@ -60,6 +60,16 @@
path: /ndk/guides/audio/basics.html
- title: OpenSL ES for Android
path: /ndk/guides/audio/opensl-for-android.html
+ - title: Audio Input Latency
+ path: /ndk/guides/audio/input-latency.html
+ - title: Audio Output Latency
+ path: /ndk/guides/audio/output-latency.html
+ - title: Floating-Point Audio
+ path: /ndk/guides/audio/floating-point.html
+ - title: Sample Rates
+ path: /ndk/guides/audio/sample-rates.html
+ - title: OpenSL ES Programming Notes
+ path: /ndk/guides/audio/opensl-prog-notes.html
- title: Vulkan
path: /ndk/guides/graphics/index.html
diff --git a/docs/html/ndk/guides/audio/basics.jd b/docs/html/ndk/guides/audio/basics.jd
index a5f0ff5..bdb85fb 100644
--- a/docs/html/ndk/guides/audio/basics.jd
+++ b/docs/html/ndk/guides/audio/basics.jd
@@ -1,4 +1,4 @@
-page.title=OpenSL ES™ Basics
+page.title=High-Performance Audio Basics
@jd:body
<div id="qv-wrapper">
@@ -6,26 +6,51 @@
<h2>On this page</h2>
<ol>
+ <li><a href="#overview">Building Great Audio Apps</a></li>
<li><a href="#adding">Adding OpenSL ES to Your App</a></li>
<li><a href="#building">Building and Debugging</a></li>
+ <li><a href="#power">Audio Power Consumption</a></li>
<li><a href="#samples">Samples</a></li>
</ol>
</div>
</div>
+<a href="https://www.youtube.com/watch?v=d3kfEeMZ65c" class="notice-developers-video">
+<div>
+ <h3>Video</h3>
+ <p>Google I/O 2013 - High Performance Audio</p>
+</div>
+</a>
<p>
-The Khronos Group's OpenSL ES standard exposes audio features
+The Khronos Group's OpenSL ES™ standard exposes audio features
similar to those in the {@link android.media.MediaPlayer} and {@link android.media.MediaRecorder}
APIs in the Android Java framework. OpenSL ES provides a C language interface as well as
C++ bindings, allowing you to call it from code written in either language.
</p>
<p>
-This page describes how to add these audio APIs into your app's source code, and how to incorporate
-them into the build process.
+This page describes the typical use cases for these high-performance audio APIs, how to add them
+into your app's source code, and how to incorporate them into the build process.
</p>
+<h2 id="overview">Building Great Audio Apps</h2>
+
+<p>
+The OpenSL ES APIs are available to help you develop and improve your app's audio performance.
+ Some typical use cases include the following:</p>
+
+<ul>
+ <li>Digital Audio Workstations (DAWs).</li>
+ <li>Synthesizers.</li>
+ <li>Drum machines.</li>
+ <li>Music learning apps.</li>
+ <li>Karaoke apps.</li>
+ <li>DJ mixing.</li>
+ <li>Audio effects.</li>
+ <li>Video/audio conferencing.</li>
+</ul>
+
<h2 id="adding">Adding OpenSL ES to your App</h2>
<p>
@@ -45,6 +70,18 @@
#include <SLES/OpenSLES_Android.h>
</pre>
+<p>
+When you include the {@code OpenSLES_Android.h} header file, the following headers are included
+automatically:
+</p>
+<pre>
+#include <SLES/OpenSLES_AndroidConfiguration.h>
+#include <SLES/OpenSLES_AndroidMetadata.h>
+</pre>
+
+<p class="note"><strong>Note: </strong>
+These headers are not required, but are shown as an aid in learning the API.
+</p>
<h2 id="building">Building and Debugging</h2>
@@ -69,9 +106,9 @@
</p>
<p>
-We use asserts in our <a href="https://github.com/googlesamples/android-ndk">examples</a>, because
-they help catch unrealistic conditions that would indicate a coding error. We have used explicit
-error handling for other conditions more likely to occur in production.
+We use asserts in our <a class="external-link" href="https://github.com/googlesamples/android-ndk">
+examples</a>, because they help catch unrealistic conditions that would indicate a coding error. We
+have used explicit error handling for other conditions more likely to occur in production.
</p>
<p>
@@ -91,18 +128,25 @@
</pre>
<p>
-To examine the log from Android Studio, either click the <em>Logcat</em> tab in the
-<a href="{@docRoot}tools/debugging/debugging-studio.html#runDebug"><em>Debug</em></a>
-window, or click the <em>Devices | logcat</em> tab in the
-<a href="{@docRoot}tools/debugging/debugging-studio.html#systemLogView"><em>Android DDMS</em></a>
+To examine the log from Android Studio, either click the <strong>Logcat</strong> tab in the
+<a href="{@docRoot}tools/debugging/debugging-studio.html#runDebug">Debug</a>
+window, or click the <strong>Devices | logcat</strong> tab in the
+<a href="{@docRoot}tools/debugging/debugging-studio.html#systemLogView">Android DDMS</a>
window.
</p>
-
+<h2 id="power">Audio Power Consumption</h2>
+<p>Constantly outputting audio incurs significant power consumption. Ensure that you stop the
+ output in the
+ <a href="{@docRoot}reference/android/app/Activity.html#onPause()">onPause()</a> method.
+ Also consider pausing the silent output after some period of user inactivity.
+</p>
<h2 id="samples">Samples</h2>
<p>
Supported and tested example code that you can use as a model for your own code resides both locally
-and on GitHub. The local examples are located in
+and on
+<a class="external-link" href="https://github.com/googlesamples/android-audio-high-performance/">
+GitHub</a>. The local examples are located in
{@code platforms/android-9/samples/native-audio/}, under your NDK root installation directory.
On GitHub, they are available from the
<a class="external-link" href="https://github.com/googlesamples/android-ndk">{@code android-ndk}</a>
@@ -122,4 +166,4 @@
For more information on differences between the reference specification and the
Android implementation, see
<a href="{@docRoot}ndk/guides/audio/opensl-for-android.html">
-OpenSL ES™ for Android</a>.
+OpenSL ES for Android</a>.
diff --git a/docs/html/ndk/guides/audio/floating-point.jd b/docs/html/ndk/guides/audio/floating-point.jd
new file mode 100644
index 0000000..76efce3
--- /dev/null
+++ b/docs/html/ndk/guides/audio/floating-point.jd
@@ -0,0 +1,101 @@
+page.title=Floating-Point Audio
+@jd:body
+
+<div id="qv-wrapper">
+ <div id="qv">
+ <h2>On this page</h2>
+
+ <ol>
+ <li><a href="#best">Best Practices for Floating-Point Audio</a></li>
+ <li><a href="#support">Floating-Point Audio in Android SDK</a></li>
+ <li><a href="#more">For More Information</a></li>
+ </ol>
+ </div>
+ </div>
+
+<a href="https://www.youtube.com/watch?v=sIcieUqMml8" class="notice-developers-video">
+<div>
+ <h3>Video</h3>
+ <p>Will it Float? The Glory and Shame of Floating-Point Audio</p>
+</div>
+</a>
+
+<p>Using floating-point numbers to represent audio data can significantly enhance audio
+ quality in high-performance audio applications. Floating point offers the following
+ advantages:</p>
+
+<ul>
+<li>Wider dynamic range.</li>
+<li>Consistent accuracy across the dynamic range.</li>
+<li>More headroom to avoid clipping during intermediate calculations and transients.</li>
+</ul>
+
+<p>While floating-point can enhance audio quality, it does present certain disadvantages:</p>
+
+<ul>
+<li>Floating-point numbers use more memory.</li>
+<li>Floating-point operations employ unexpected properties, for example, addition is
+ not associative.</li>
+<li>Floating-point calculations can sometimes lose arithmetic precision due to rounding or
+ numerically unstable algorithms.</li>
+<li>Using floating-point effectively requires greater understanding to achieve accurate
+ and reproducible results.</li>
+</ul>
+
+<p>
+ Formerly, floating-point was notorious for being unavailable or slow. This is
+ still true for low-end and embedded processors. But processors on modern
+ mobile devices now have hardware floating-point with performance that is
+ similar (or in some cases even faster) than integer. Modern CPUs also support
+ <a href="http://en.wikipedia.org/wiki/SIMD" class="external-link">SIMD</a>
+ (Single instruction, multiple data), which can improve performance further.
+</p>
+
+<h2 id="best">Best Practices for Floating-Point Audio</h2>
+<p>The following best practices help you avoid problems with floating-point calculations:</p>
+<ul>
+<li>Use double precision floating-point for infrequent calculations,
+such as computing filter coefficients.</li>
+<li>Pay attention to the order of operations.</li>
+<li>Declare explicit variables for intermediate values.</li>
+<li>Use parentheses liberally.</li>
+<li>If you get a NaN or infinity result, use binary search to discover
+where it was introduced.</li>
+</ul>
+
+<h2 id="support">Floating-Point Audio in Android SDK</h2>
+
+<p>For floating-point audio, the audio format encoding
+ <code>AudioFormat.ENCODING_PCM_FLOAT</code> is used similarly to
+ <code>ENCODING_PCM_16_BIT</code> or <code>ENCODING_PCM_8_BIT</code> for specifying
+ AudioTrack data
+formats. The corresponding overloaded method <code>AudioTrack.write()</code>
+ takes in a float array to deliver data.</p>
+
+<pre>
+ public int write(float[] audioData,
+ int offsetInFloats,
+ int sizeInFloats,
+ int writeMode)
+</pre>
+
+<h2 id="more">For More Information</h2>
+
+<p>The following Wikipedia pages are helpful in understanding floating-point audio:</p>
+
+<ul>
+<li><a href="http://en.wikipedia.org/wiki/Audio_bit_depth" class="external-link" >Audio bit depth</a></li>
+<li><a href="http://en.wikipedia.org/wiki/Floating_point" class="external-link" >Floating point</a></li>
+<li><a href="http://en.wikipedia.org/wiki/IEEE_floating_point" class="external-link" >IEEE 754 floating-point</a></li>
+<li><a href="http://en.wikipedia.org/wiki/Loss_of_significance" class="external-link" >Loss of significance</a>
+ (catastrophic cancellation)</li>
+<li><a href="https://en.wikipedia.org/wiki/Numerical_stability" class="external-link" >Numerical stability</a></li>
+</ul>
+
+<p>The following article provides information on those aspects of floating-point that have a
+ direct impact on designers of computer systems:</p>
+<ul>
+<li><a href="http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html" class="external-link" >What every
+ computer scientist should know about floating-point arithmetic</a>
+by David Goldberg, Xerox PARC (edited reprint).</li>
+</ul>
diff --git a/docs/html/ndk/guides/audio/index.jd b/docs/html/ndk/guides/audio/index.jd
index ac6e539..12d9320 100644
--- a/docs/html/ndk/guides/audio/index.jd
+++ b/docs/html/ndk/guides/audio/index.jd
@@ -1,15 +1,27 @@
-page.title=NDK Audio: OpenSL ES™
+page.title=NDK High-Performance Audio
@jd:body
<p>The NDK package includes an Android-specific implementation of the
-<a href="https://www.khronos.org/opensles/">OpenSL ES</a> API
-specification from the <a href="https://www.khronos.org">Khronos Group</a>. This library
-allows you to use C or C++ to implement high-performance, low-latency audio in your game or other
-demanding app.</p>
+<a class="external-link" href="https://www.khronos.org/opensles/">OpenSL ES™</a> API
+specification from the <a class="external-link" href="https://www.khronos.org">Khronos Group</a>.
+This library allows you to use C or C++ to implement high-performance, low-latency audio, whether
+you are writing a synthesizer, digital audio workstation, karaoke, game,
+ or other real-time app.</p>
<p>This section begins by providing some
-<a href="{@docRoot}ndk/guides/audio/basics.html">basic information</a> about the API, including how
-to incorporate it into your app. It then explains what you need to know about the
-<a href="{@docRoot}ndk/guides/audio/opensl-for-android.html">Android-specific implementation</a>
-of OpenSL ES, focusing on differences between this implementation and the reference specification.
-</p>
+<a href="{@docRoot}ndk/guides/audio/basics.html">basic information</a> about the API, including
+typical use cases and how to incorporate it into your app. It then explains what you need to know
+about the <a href="{@docRoot}ndk/guides/audio/opensl-for-android.html">Android-specific
+implementation</a> of OpenSL ES, focusing on the differences between this implementation and the
+reference specification. Next, you'll learn how to minimze
+ <a href="{@docRoot}ndk/guides/audio/input-latency.html">input latency</a>
+ when using built-in or external microphones
+and some actions that you can take to minimize
+ <a href="{@docRoot}ndk/guides/audio/output-latency.html">output latency</a>.
+ It describes the reasons that you should use
+ <a href="{@docRoot}ndk/guides/audio/floating-point.html">floating-point</a>
+ numbers to represent your audio data, and it provides information that will help you choose the
+optimal <a href="{@docRoot}ndk/guides/audio/sample-rates.html">sample rate</a>. This section
+ concludes with some supplemental <a href="{@docRoot}ndk/guides/audio/opensl-prog-notes.html">
+ programming notes</a> to ensure proper implementation of OpenSL ES.
+ </p>
diff --git a/docs/html/ndk/guides/audio/input-latency.jd b/docs/html/ndk/guides/audio/input-latency.jd
new file mode 100644
index 0000000..f1103fc
--- /dev/null
+++ b/docs/html/ndk/guides/audio/input-latency.jd
@@ -0,0 +1,95 @@
+page.title=Audio Input Latency
+@jd:body
+
+<div id="qv-wrapper">
+ <div id="qv">
+ <h2>On this page</h2>
+
+ <ol>
+ <li><a href="#check-list">Checklist</a></li>
+ <li><a href="#ways">Ways to Reduce Audio Input Latency</a></li>
+ <li><a href="#avoid">What to Avoid</a></li>
+ </ol>
+ </div>
+ </div>
+
+
+<p>This page provides guidelines to help you reduce audio input latency when recording with a
+built-in microphone or an external headset microphone.</p>
+
+<h2 id="check-list">Checklist</h2>
+
+<p>Here are a few important prerequisites:</p>
+
+<ul>
+ <li>You must use the Android-specific implementation of the
+ <a class="external-link" href="https://www.khronos.org/opensles/">OpenSL ES™</a> API.
+
+ <li>If you haven't already done so, download and install the
+ <a href="{@docRoot}tools/sdk/ndk/index.html">Android NDK</a>.</li>
+
+ <li>Many of the same requirements for low-latency audio output also apply to low-latency input,
+ so read the requirements for low-latency output in
+ <a href="{@docRoot}ndk/guides/audio/output-latency.html">Audio Output Latency</a>.</li>
+</ul>
+
+<h2 id="ways">Ways to Reduce Audio Input Latency</h2>
+
+<p>The following are some methods to help ensure low audio input latency:
+
+<ul>
+ <li>Suggest to your users, if your app relies on low-latency audio, that they use a headset
+(for example, by displaying a <em>Best with headphones</em> screen on first run). Note
+that just using the headset doesn’t guarantee the lowest possible latency. You may need to
+perform other steps to remove any unwanted signal processing from the audio path, such as by
+using the <a href="http://developer.android.com/reference/android/media/MediaRecorder.AudioSource.html#VOICE_RECOGNITION">
+VOICE_RECOGNITION</a> preset when recording.</li>
+
+ <li>It's difficult to test audio input and output latency in isolation. The best solution to
+determine the lowest possible audio input latency is to measure round-trip audio and divide
+by two.</li>
+ <li> Be prepared to handle nominal sample rates of 44,100 and 48,000 Hz as reported by
+<a href="{@docRoot}reference/android/media/AudioManager.html#getProperty(java.lang.String)">
+getProperty(String)</a> for
+<a href="{@docRoot}reference/android/media/AudioManager.html#PROPERTY_OUTPUT_SAMPLE_RATE">
+PROPERTY_OUTPUT_SAMPLE_RATE</a>. Other sample rates are possible, but rare.</li>
+
+ <li>Be prepared to handle the buffer size reported by
+<a href="{@docRoot}reference/android/media/AudioManager.html#getProperty(java.lang.String)">
+getProperty(String)</a> for
+<a href="{@docRoot}reference/android/media/AudioManager.html#PROPERTY_OUTPUT_FRAMES_PER_BUFFER">
+PROPERTY_OUTPUT_FRAMES_PER_BUFFER</a>. Typical buffer sizes include 96, 128, 160, 192, 240, 256,
+or 512 frames, but other values are possible.</li>
+</ul>
+
+<h2 id="avoid">What to Avoid</h2>
+
+<p>Be sure to take these things into account to help avoid latency issues:</p>
+
+<ul>
+ <li>Don’t assume that the speakers and microphones used in mobile devices generally have good
+acoustics. Due to their small size, the acoustics are generally poor so signal processing is
+added to improve the sound quality. This signal processing introduces latency.</li>
+
+ <li>Don't assume that your input and output callbacks are synchronized. For simultaneous input
+and output, separate buffer queue completion handlers are used for each side. There is no
+guarantee of the relative order of these callbacks or the synchronization of the audio clocks,
+even when both sides use the same sample rate. Your application should buffer the data with
+proper buffer synchronization.</li>
+
+ <li>Don't assume that the actual sample rate exactly matches the nominal sample rate. For
+example, if the nominal sample rate is 48,000 Hz, it is normal for the audio clock to advance
+at a slightly different rate than the operating system {@code CLOCK_MONOTONIC}. This is because
+the audio and system clocks may derive from different crystals.</li>
+
+ <li>Don't assume that the actual playback sample rate exactly matches the actual capture sample
+rate, especially if the endpoints are on separate paths. For example, if you are capturing from
+the on-device microphone at 48,000 Hz nominal sample rate, and playing on USB audio
+at 48,000 Hz nominal sample rate, the actual sample rates are likely to be slightly different
+from each other.</li>
+</ul>
+
+<p>A consequence of potentially independent audio clocks is the need for asynchronous sample rate
+conversion. A simple (though not ideal for audio quality) technique for asynchronous sample rate
+conversion is to duplicate or drop samples as needed near a zero-crossing point. More
+sophisticated conversions are possible.</p>
diff --git a/docs/html/ndk/guides/audio/opensl-for-android.jd b/docs/html/ndk/guides/audio/opensl-for-android.jd
index 763da5a..fa5e260 100644
--- a/docs/html/ndk/guides/audio/opensl-for-android.jd
+++ b/docs/html/ndk/guides/audio/opensl-for-android.jd
@@ -1,4 +1,4 @@
-page.title=Native Audio: OpenSL ES™ for Android
+page.title=OpenSL ES for Android
@jd:body
<div id="qv-wrapper">
@@ -6,23 +6,158 @@
<h2>On this page</h2>
<ol>
+ <li><a href="#getstarted">Getting Started</a></li>
<li><a href="#inherited">Features Inherited from the Reference Specification</a></li>
+ <li><a href="#planning">Planning for Future Versions of OpenSL ES</a></li>
<li><a href="#ae">Android Extensions</a></li>
+ <li><a href="#notes">Programming Notes</a></li>
+ <li><a href="#platform-issues">Platform Issues</a></li>
</ol>
</div>
</div>
<p>
-This page provides details about how the NDK implementation of OpenSL ES™ differs
-from the reference specification for OpenSL ES 1.0.1. When using sample code from the
+This page provides details about how the
+<a href="{@docRoot}tools/sdk/ndk/index.html">NDK</a> implementation of OpenSL
+ES™ differs from the reference specification for OpenSL ES 1.0.1. When using sample code from the
specification, you may need to modify it to work on Android.
</p>
+<p>
+Unless otherwise noted, all features are available at Android 2.3 (API level 9) and higher.
+ Some features are only available for Android 4.0 (API level 14); these are noted.
+</p>
+
+<p class="note"><strong>Note: </strong>
+The Android Compatibility Definition Document (CDD) enumerates the hardware and software
+requirements of a compatible Android device. See
+<a class="external-link" href="https://source.android.com/compatibility/">Android Compatibility</a>
+for more information on the overall compatibility program, and
+<a class="external-link" href="https://static.googleusercontent.com/media/source.android.com/en//compatibility/android-cdd.pdf">
+CDD</a> for the actual CDD document.
+</p>
+
+<p>
+<a class="external-link" href="https://www.khronos.org/opensles/">OpenSL ES</a> provides a C
+language interface that is also accessible using C++. It exposes features similar to the audio
+portions of these Android Java APIs:
+</p>
+
+<ul>
+ <li><a href="{@docRoot}reference/android/media/MediaPlayer.html">
+ android.media.MediaPlayer</a></li>
+ <li><a href="{@docRoot}reference/android/media/MediaRecorder.html">
+ android.media.MediaRecorder</a></li>
+</ul>
+
+<p>
+As with all of the Android Native Development Kit (NDK), the primary purpose of OpenSL ES for
+Android is to facilitate the implementation of shared libraries to be called using the Java Native
+Interface (<a class="external-link" href="https://en.wikipedia.org/wiki/Java_Native_Interface">JNI
+</a>). NDK is not intended for writing pure C/C++ applications. However, OpenSL ES is a
+full-featured API, and we expect that you should be able to accomplish most of your audio needs
+using only this API, without up-calls to code running in the Android runtime.
+</p>
+
+<p class="note"><strong>Note: </strong>
+Though based on OpenSL ES, the Android native audio (high-performance audio) API is not a
+conforming implementation of any OpenSL ES 1.0.1 profile (game, music, or phone). This is because
+Android does not implement all of the features required by any one of the profiles. Any known cases
+where Android behaves differently than the specification are described in the <a href="#ae">
+Android extensions</a> section below.
+</p>
+
+<h2 id="getstarted">Getting Started</h2>
+
+<p>
+This section provides the information needed to get started using the OpenSL ES APIs.
+</p>
+
+<h3>Example code</h3>
+
+<p>
+We recommend using supported and tested example code that is usable as a model for your own
+code, which is located in the NDK folder {@code platforms/android-9/samples/native-audio/}, as well
+as in the
+<a class="external-link" href="https://github.com/googlesamples/android-ndk/tree/master/audio-echo">audio-echo</a>
+and
+<a class="external-link" href="https://github.com/googlesamples/android-ndk/tree/master/native-audio">native-audio</a>
+folders of the
+<a class="external-link" href="https://github.com/googlesamples/android-ndk">android-ndk</a> GitHub
+repository.
+</p>
+
+<p class="caution"><strong>Caution: </strong>
+The OpenSL ES 1.0.1 specification contains example code in the appendices (see
+<a class="external-link" href="https://www.khronos.org/registry/sles/">Khronos OpenSL ES Registry</a>
+for more details). However, the examples in <em>Appendix B: Sample Code</em> and
+<em>Appendix C: Use Case Sample Code</em> use features that are not supported by Android. Some
+examples also contain typographical errors, or use APIs that are likely to change. Proceed with
+caution when referring to these; though the code may be helpful in understanding the full OpenSL ES
+standard, it should not be used as-is with Android.
+</p>
+
+<h3>Makefile</h3>
+
+<p>
+Modify your {@code Android.mk} file as follows:
+</p>
+<pre>
+LOCAL_LDLIBS += -lOpenSLES
+</pre>
+
+<h3>Audio content</h3>
+
+<p>
+The following are some of the many ways to package audio content for your application:
+</p>
+
+<ul>
+ <li><strong>Resources</strong>: By placing your audio files into the {@code res/raw/} folder,
+ they can be accessed easily by the associated APIs for
+ <a href="{@docRoot}reference/android/content/res/Resources.html">Resources</a>.
+ However, there is no direct native access to resources, so you must write Java
+ programming language code to copy them out before use.</li>
+ <li><strong>Assets</strong>: By placing your audio files into the {@code assets/} folder, they
+ are directly accessible by the Android native asset manager APIs. See the header files {@code
+ android/asset_manager.h} and {@code android/asset_manager_jni.h} for more information on these
+ APIs. The example code located in the NDK folder {@code platforms/android-9/samples/native-audio/}
+ uses these native asset manager APIs in conjunction with the Android file descriptor data
+ locator.</li>
+ <li><strong>Network</strong>: You can use the URI data locator to play audio content directly
+ from the network. However, be sure to read the <a href="#sandp">Security and permissions</a>
+ section below.</li>
+ <li><strong>Local file system</strong>: The URI data locator supports the {@code file:} scheme
+ for local files, provided the files are accessible by the application. Note that the Android
+ security framework restricts file access via the Linux user ID and group ID mechanisms.</li>
+ <li><strong>Recorded</strong>: Your application can record audio data from the microphone input,
+ store this content, and then play it back later. The example code uses this method for the <em>
+ Playback</em> clip.</li>
+ <li><strong>Compiled and linked inline</strong>: You can link your audio content directly into
+ the shared library, and then play it using an audio player with buffer queue data locator. This
+ is most suitable for short PCM format clips. The example code uses this technique for the <em>
+ Hello</em> and <em>Android</em> clips. The PCM data was converted to hex strings using a
+ {@code bin2c} tool (not supplied).</li>
+ <li><strong>Real-time synthesis</strong>: Your application can synthesize PCM data on the fly and
+ then play it using an audio player with buffer queue data locator. This is a relatively advanced
+ technique, and the details of audio synthesis are beyond the scope of this article.</li>
+</ul>
+
+<p class="note"><strong>Note: </strong>
+Finding or creating useful audio content for your application is beyond the scope of this article.
+You can use web search terms such as <em>interactive audio</em>, <em>game audio</em>, <em>sound
+design</em>, and <em>audio programming</em> to locate more information.
+</p>
+<p class="caution"><strong>Caution:</strong> It is your responsibility
+to ensure that you are legally permitted to play or record content. There may be privacy
+considerations for recording content.
+</p>
+
<h2 id="inherited">Features Inherited from the Reference Specification</h2>
<p>
The Android NDK implementation of OpenSL ES inherits much of the feature set from
-the reference specification, although with certain limitations.
+the reference specification, with certain limitations.
</p>
<h3>Global entry points</h3>
@@ -44,8 +179,9 @@
<h3>Objects and interfaces</h3>
<p>
-Table 1 shows which objects and interfaces the Android NDK implementation of
-OpenSL ES supports. Green cells indicate features available in this implementation.
+Table 1 shows the objects and interfaces that the Android NDK implementation of
+OpenSL ES supports. If a <em>Yes</em> appears in the cell, then the feature is available in this
+implementation.
</p>
<p class="table-caption" id="Objects-and-interfaces">
@@ -214,7 +350,9 @@
</tr>
</table>
-The next section explains limitations of some of these features.
+<p>
+The next section explains the limitations for some of these features.
+</p>
<h3>Limitations</h3>
@@ -265,7 +403,7 @@
to either <code>NULL</code> or a valid UTF-8 string. You must also initialize
<code>containerType</code> to a valid value.
In the absence of other considerations, such as portability to other
-implementations, or content format that an app cannot identify by header,
+implementations or content format that an app cannot identify by header,
we recommend that you
set <code>mimeType</code> to <code>NULL</code> and <code>containerType</code>
to <code>SL_CONTAINERTYPE_UNSPECIFIED</code>.
@@ -275,30 +413,32 @@
Android platform supports them as well:</p>
<ul>
-<li>WAV PCM</li>
-<li>WAV alaw</li>
-<li>WAV ulaw</li>
-<li>MP3 Ogg Vorbis</li>
-<li>AAC LC</li>
-<li>HE-AACv1 (AAC+)</li>
-<li>HE-AACv2 (enhanced AAC+)</li>
-<li>AMR</li>
-<li>FLAC</li>
+<li><a class="external-link" href="https://en.wikipedia.org/wiki/WAV">WAV</a> PCM.</li>
+<li>WAV alaw.</li>
+<li>WAV ulaw.</li>
+<li>MP3 Ogg Vorbis.</li>
+<li>AAC LC.</li>
+<li>HE-AACv1 (AAC+).</li>
+<li>HE-AACv2 (enhanced AAC+).</li>
+<li>AMR.</li>
+<li>FLAC.</li>
</ul>
-<p>
+<p class="note"><strong>Note: </strong>
For a list of audio formats that Android supports, see
<a href="{@docRoot}guide/appendix/media-formats.html">Supported Media Formats</a>.
</p>
<p>
-The following limitations apply to handling of these and other formats in this
+The following limitations apply to the handling of these and other formats in this
implementation of OpenSL ES:
</p>
<ul>
-<li>AAC formats must be reside within an MP4 or ADTS container.</li>
-<li>OpenSL ES for Android does not support MIDI.</li>
+<li><a class="external-link" href="https://en.wikipedia.org/wiki/Advanced_Audio_Coding">AAC</a>
+formats must reside within an MP4 or ADTS container.</li>
+<li>OpenSL ES for Android does not support
+<a class="external-link" href="https://source.android.com/devices/audio/midi.html">MIDI</a>.</li>
<li>WMA is not part of <a class="external-link" href="https://source.android.com/">AOSP</a>, and we
have not verified its compatibility with OpenSL ES for Android.</li>
<li>The Android NDK implementation of OpenSL ES does not support direct
@@ -333,13 +473,23 @@
<li>8-bit unsigned or 16-bit signed.</li>
<li>Mono or stereo.</li>
<li>Little-endian byte ordering.</li>
-<li>Sample rates of: 8,000, 11,025, 12,000, 16,000, 22,050, 24,000, 32,000, 44,100, or
-48,000 Hz.</li>
+<li>Sample rates of:
+ <ul>
+ <li>8,000 Hz.</li>
+ <li>11,025 Hz.</li>
+ <li>12,000 Hz.</li>
+ <li>16,000 Hz.</li>
+ <li>22,050 Hz.</li>
+ <li>24,000 Hz.</li>
+ <li>32,000 Hz.</li>
+ <li>44,100 Hz.</li>
+ <li>48,000 Hz.</li>
+ </ul></li>
</ul>
<p>
The configurations that OpenSL ES for Android supports for recording are
-device-dependent; usually, 16,000 Hz mono 16-bit signed is available regardless of device.
+device-dependent; usually, 16,000 Hz mono/16-bit signed is available regardless of the device.
</p>
<p>
The value of the <code>samplesPerSec</code> field is in units of milliHz, despite the misleading
@@ -393,7 +543,7 @@
An audio player or recorder with a data locator for a buffer queue supports PCM data format only.
</p>
-<h4>I/O Device data locator</h4>
+<h4>I/O device data locator</h4>
<p>
OpenSL ES for Android only supports use of an I/O device data locator when you have
@@ -421,6 +571,150 @@
We have not verified support for {@code rtsp:} with audio on the Android platform.
</p>
+<h4>Data structures</h4>
+
+<p>
+Android supports these OpenSL ES 1.0.1 data structures:
+</p>
+<ul>
+ <li>{@code SLDataFormat_MIME}</li>
+ <li>{@code SLDataFormat_PCM}</li>
+ <li>{@code SLDataLocator_BufferQueue}</li>
+ <li>{@code SLDataLocator_IODevice}</li>
+ <li>{@code SLDataLocator_OutputMix}</li>
+ <li>{@code SLDataLocator_URI}</li>
+ <li>{@code SLDataSink}</li>
+ <li>{@code SLDataSource}</li>
+ <li>{@code SLEngineOption}</li>
+ <li>{@code SLEnvironmentalReverbSettings}</li>
+ <li>{@code SLInterfaceID}</li>
+</ul>
+
+<h4>Platform configuration</h4>
+
+<p>
+OpenSL ES for Android is designed for multi-threaded applications and is thread-safe. It supports a
+single engine per application, and up to 32 objects per engine. Available device memory and CPU may
+further restrict the usable number of objects.
+</p>
+
+<p>
+These engine options are recognized, but ignored by {@code slCreateEngine}:
+</p>
+
+<ul>
+ <li>{@code SL_ENGINEOPTION_THREADSAFE}</li>
+ <li>{@code SL_ENGINEOPTION_LOSSOFCONTROL}</li>
+</ul>
+
+<p>
+OpenMAX AL and OpenSL ES may be used together in the same application. In this case, there is
+a single shared engine object internally, and the 32 object limit is shared between OpenMAX AL
+and OpenSL ES. The application should first create both engines, use both engines, and finally
+destroy both engines. The implementation maintains a reference count on the shared engine so that
+it is correctly destroyed during the second destroy operation.
+</p>
+
+<h2 id="planning">Planning for Future Versions of OpenSL ES</h2>
+
+<p>
+The Android high-performance audio APIs are based on
+<a class="external-link" href="https://www.khronos.org/registry/sles/">Khronos Group OpenSL ES
+1.0.1</a>. Khronos has released a revised version 1.1 of the standard. The
+revised version includes new features, clarifications, corrections of typographical errors, and
+some incompatibilities. Most of the expected incompatibilities are relatively minor or are in
+areas of OpenSL ES that are not supported by Android.
+</p>
+
+<p>
+An application
+developed with this version should work on future versions of the Android platform, provided
+that you follow the guidelines that are outlined in the <a href="#binary-compat">Planning for
+binary compatibility</a> section below.
+</p>
+
+<p class="note"><strong>Note: </strong>
+Future source compatibility is not a goal. That is, if you upgrade to a newer version of the NDK,
+you may need to modify your application source code to conform to the new API. We expect that most
+such changes will be minor; see details below.
+</p>
+
+<h3 id="binary-compat">Planning for binary compatibility</h3>
+
+<p>
+We recommend that your application follow these guidelines to improve future binary compatibility:
+</p>
+
+<ul>
+ <li>Use only the documented subset of Android-supported features from OpenSL ES 1.0.1.</li>
+ <li>Do not depend on a particular result code for an unsuccessful operation; be prepared to deal
+ with a different result code.</li>
+ <li>Application callback handlers generally run in a restricted context. They should be written
+ to perform their work quickly, and then return as soon as possible. Do not run complex operations
+ within a callback handler. For example, within a buffer queue completion callback, you can
+ enqueue another buffer, but do not create an audio player.</li>
+ <li>Callback handlers should be prepared to be called more or less frequently, to receive
+ additional event types, and should ignore event types that they do not recognize. Callbacks that
+ are configured with an event mask made of enabled event types should be prepared to be called
+ with multiple event type bits set simultaneously. Use "&" to test for each event bit rather than
+ a switch case.</li>
+ <li>Use prefetch status and callbacks as a general indication of progress, but do not depend on
+ specific hard-coded fill levels or callback sequences. The meaning of the prefetch status fill
+ level, and the behavior for errors that are detected during prefetch, may change.</li>
+</ul>
+
+<p class="note"><strong>Note: </strong>
+See the <a href="#bq-behavior">Buffer queue behavior</a> section below for more details.
+</p>
+
+<h3>Planning for source compatibility</h3>
+
+<p>
+As mentioned, source code incompatibilities are expected in the next version of OpenSL ES from
+Khronos Group. The likely areas of change include:
+</p>
+
+<ul>
+ <li>The buffer queue interface is expected to have significant changes, especially in the areas
+ of {@code BufferQueue::Enqueue}, the parameter list for {@code slBufferQueueCallback}, and the
+ name of field {@code SLBufferQueueState.playIndex}. We recommend that your application code use
+ Android simple buffer queues instead. In the example
+ code that is supplied with the NDK, we have used Android simple buffer queues for playback for
+ this reason. (We also use Android simple buffer queue for recording and decoding to PCM, but that
+ is because standard OpenSL ES 1.0.1 does not support record or decode to a buffer queue data
+ sink.)</li>
+ <li>There will be an addition of {@code const} to the input parameters passed by reference, and
+ to {@code SLchar *} struct fields used as input values. This should not require any changes to
+ your code.</li>
+ <li>There will be a substitution of unsigned types for some parameters that are currently signed.
+ You may need to change a parameter type from {@code SLint32} to {@code SLuint32} or similar, or
+ add a cast.</li>
+ <li>{@code Equalizer::GetPresetName} copies the string to application memory instead of returning
+ a pointer to implementation memory. This will be a significant change, so we recommend that you
+ either avoid calling this method, or isolate your use of it.</li>
+ <li>There will be additional fields in the struct types. For output parameters, these new fields
+ can be ignored, but for input parameters the new fields will need to be initialized. Fortunately,
+ all of these are expected to be in areas that are not supported by Android.</li>
+ <li>Interface <a class="external-link" href="http://en.wikipedia.org/wiki/Globally_unique_identifier">
+ GUIDs</a> will change. Refer to interfaces by symbolic name rather than GUID to avoid a
+ dependency.</li>
+ <li>{@code SLchar} will change from {@code unsigned char} to {@code char}. This primarily affects
+ the URI data locator and MIME data format.</li>
+ <li>{@code SLDataFormat_MIME.mimeType} will be renamed to {@code pMimeType}, and
+ {@code SLDataLocator_URI.URI} will be renamed to {@code pURI}. We recommend that you initialize
+ the {@code SLDataFormat_MIME} and {@code SLDataLocator_URI} data structures using a
+ brace-enclosed, comma-separated list of values, rather than by field name, to isolate your code
+ from this change. This technique is used in the example code.</li>
+ <li>{@code SL_DATAFORMAT_PCM} does not permit the application to specify the representation of
+ the data as signed integer, unsigned integer, or floating-point. The Android implementation
+ assumes that 8-bit data is unsigned integer and 16-bit is signed integer. In addition, the field
+ {@code samplesPerSec} is a misnomer, as the actual units are milliHz. These issues are expected
+ to be addressed in the next OpenSL ES version, which will introduce a new extended PCM data
+ format that permits the application to explicitly specify the representation and corrects the
+ field name. As this will be a new data format, and the current PCM data format will still be
+ available (though deprecated), it should not require any immediate changes to your code.</li>
+</ul>
+
<h2 id="ae">Android Extensions</h2>
<p>
@@ -444,8 +738,8 @@
<p>
Table 2 shows the Android-specific interfaces and data locators that Android OpenSL ES supports
-for each object type. Green cells indicate interfaces and data locators available for each
-object type.
+for each object type. The <em>Yes</em> values in the cells indicate the interfaces and data
+locators that are available for each object type.
</p>
<p class="table-caption" id="Android-extensions">
@@ -523,7 +817,7 @@
</tr>
</table>
-<h3>Android configuration interface</h3>
+<h3 id="configuration-interface">Android configuration interface</h3>
<p>
The Android configuration interface provides a means to set
@@ -581,6 +875,11 @@
that they provide.
</p>
+<p>
+Portable applications should use the OpenSL ES 1.0.1 APIs for audio effects instead of the Android
+effect extensions.
+</p>
+
<h3>Android file descriptor data locator</h3>
<p>
@@ -597,9 +896,9 @@
<p>
The Android simple buffer queue data locator and interface are
identical to those in the OpenSL ES 1.0.1 reference specification, with two exceptions: You
-can also use Android simple buffer queues with both audio players and audio recorders. Also, PCM
+can also use Android simple buffer queues with both audio players and audio recorders. Also, PCM
is the only data format you can use with these queues.
-In the reference specification, buffer queues are for audio players only, but
+In the reference specification, buffer queues are for audio players only, but they are
compatible with data formats beyond PCM.
</p>
<p>
@@ -613,7 +912,7 @@
buffer queues instead of OpenSL ES 1.0.1 buffer queues.
</p>
-<h3>Dynamic interfaces at object creation</h3>
+<h3 id="dynamic-interfaces">Dynamic interfaces at object creation</h3>
<p>
For convenience, the Android implementation of OpenSL ES 1.0.1
@@ -622,7 +921,7 @@
to add these interfaces after instantiation.
</p>
-<h3>Buffer queue behavior</h3>
+<h3 id="bq-behavior">Buffer queue behavior</h3>
<p>
The Android implementation does not include the
@@ -641,7 +940,7 @@
<p>
Similarly, there is no specification governing whether the trigger for a buffer queue callback must
be a transition to <code>SL_PLAYSTATE_STOPPED</code> or execution of
-<code>BufferQueue::Clear()</code>. Therefore, we recommend against creating a dependency on
+<code>BufferQueue::Clear()</code>. Therefore, we recommend that you do not create a dependency on
one or the other; instead, your app should be able to handle both.
</p>
@@ -679,27 +978,34 @@
</tr>
<tr>
<td>13 and below</td>
- <td>An open-source codec with a suitable license.</td>
+ <td>An open-source codec with a suitable license</td>
</tr>
<tr>
<td>14 to 15</td>
- <td>An open-source codec with a suitable license.</td>
+ <td>An open-source codec with a suitable license</td>
</tr>
<tr>
<td>16 to 20</td>
<td>
- The {@link android.media.MediaCodec} class or an open-source codec with a suitable license.
+ The {@link android.media.MediaCodec} class or an open-source codec with a suitable license
</td>
</tr>
<tr>
<td>21 and above</td>
<td>
NDK MediaCodec in the {@code <media/NdkMedia*.h>} header files, the
- {@link android.media.MediaCodec} class, or an open-source codec with a suitable license.
+ {@link android.media.MediaCodec} class, or an open-source codec with a suitable license
</td>
</tr>
</table>
+<p class="note"><strong>Note: </strong>
+There is currently no documentation for the NDK version of the {@code MediaCodec} API. However,
+you can refer to the
+<a class="external-link" href="https://github.com/googlesamples/android-ndk/tree/master/native-codec">
+native-codec</a> sample code for an example.
+</p>
+
<p>
A standard audio player plays back to an audio device, specifying the output mix as the data sink.
The Android extension differs in that an audio player instead
@@ -710,17 +1016,18 @@
<p>
This feature is primarily intended for games to pre-load their audio assets when changing to a
-new game level, similar to the functionality that the {@link android.media.SoundPool} class
-provides.
+new game level, which is similar to the functionality that the {@link android.media.SoundPool}
+class provides.
</p>
<p>
The application should initially enqueue a set of empty buffers in the Android simple
-buffer queue. After that, the app fills the buffers with with PCM data. The Android simple
+buffer queue. After that, the app fills the buffers with PCM data. The Android simple
buffer queue callback fires after each buffer is filled. The callback handler processes
the PCM data, re-enqueues the now-empty buffer, and then returns. The application is responsible for
keeping track of decoded buffers; the callback parameter list does not include
-sufficient information to indicate which buffer contains data or which buffer to enqueue next.
+sufficient information to indicate the buffer that contains data or the buffer that should be
+enqueued next.
</p>
<p>
@@ -753,8 +1060,8 @@
To decode an encoded stream to PCM but not play back immediately, for apps running on
Android 4.x (API levels 16–20), we recommend using the {@link android.media.MediaCodec} class.
For new applications running on Android 5.0 (API level 21) or higher, we recommend using the NDK
-equivalent, {@code <NdkMedia*.h>}. These header files reside under
-the {@code media/} directory, under your installation root.
+equivalent, {@code <NdkMedia*.h>}. These header files reside in
+the {@code media/} directory under your installation root.
</p>
<h3>Decode streaming ADTS AAC to PCM</h3>
@@ -796,7 +1103,7 @@
The Android buffer queue callback fires after each buffer is emptied.
The callback handler should refill and re-enqueue the buffer, and then return.
The application need not keep track of encoded buffers; the callback parameter
-list includes sufficient information to indicate which buffer to enqueue next.
+list includes sufficient information to indicate the buffer that should be enqueued next.
The end of stream is explicitly marked by enqueuing an EOS item.
After EOS, no more enqueues are permitted.
</p>
@@ -812,6 +1119,7 @@
In all respects except for the data source, the streaming decode method is the same as
the one that <a href="#da">Decode audio to PCM</a> describes.
</p>
+
<p>
Despite the similarity in names, an Android buffer queue is <em>not</em>
the same as an <a href="#simple">Android simple buffer queue</a>. The streaming decoder
@@ -840,8 +1148,8 @@
practice is to query for the key indices in the main thread after calling the {@code
Object::Realize} method, and to read the PCM format metadata values in the Android simple
buffer queue callback handler when calling it for the first time. Consult the
-<a href="https://github.com/googlesamples/android-ndk">example code in the NDK package</a>
-for examples of working with this interface.
+<a class="external-link" href="https://github.com/googlesamples/android-ndk">example code in the
+NDK package</a> for examples of working with this interface.
</p>
<p>
@@ -879,3 +1187,25 @@
audiosrc.pLocator = ...
audiosrc.pFormat = &pcm;
</pre>
+
+<h2 id="notes">Programming Notes</h2>
+<p><a href="{@docRoot}ndk/guides/audio/opensl-prog-notes.html">OpenSL ES Programming Notes</a>
+ provides supplemental information to ensure proper implementation of OpenSL ES.</p>
+<p class="note"><strong>Note: </strong>
+For your convenience, we have included a copy of the OpenSL ES 1.0.1 specification with the NDK in
+{@code docs/opensles/OpenSL_ES_Specification_1.0.1.pdf}.
+</p>
+
+<h2 id="platform-issues">Platform Issues</h2>
+
+<p>
+This section describes known issues in the initial platform release that supports these APIs.
+</p>
+
+<h3>Dynamic interface management</h3>
+
+<p>
+{@code DynamicInterfaceManagement::AddInterface} does not work. Instead, specify the interface in
+the array that is passed to Create, as shown in the example code for environmental reverb.
+</p>
+
diff --git a/docs/html/ndk/guides/audio/opensl-prog-notes.jd b/docs/html/ndk/guides/audio/opensl-prog-notes.jd
new file mode 100644
index 0000000..3263145
--- /dev/null
+++ b/docs/html/ndk/guides/audio/opensl-prog-notes.jd
@@ -0,0 +1,461 @@
+page.title=OpenSL ES Programming Notes
+@jd:body
+
+<div id="qv-wrapper">
+ <div id="qv">
+ <h2>On this page</h2>
+
+ <ol>
+ <li><a href="#init">Objects and Interface Initialization</a></li>
+ <li><a href="#prefetch">Audio Player Prefetch</a></li>
+ <li><a href="#destroy">Destroy</a></li>
+ <li><a href="#panning">Stereo Panning</a></li>
+ <li><a href="#callbacks">Callbacks and Threads</a></li>
+ <li><a href="#perform">Performance</a></li>
+ <li><a href="#sandp">Security and Permissions</a></li>
+ </ol>
+ </div>
+</div>
+
+<p>
+The notes in this section supplement the
+<a class="external-link" href="https://www.khronos.org/registry/sles/">OpenSL ES 1.0.1
+specification</a>.
+</p>
+
+<h2 id="init">Objects and Interface Initialization</h2>
+
+<p>
+Two aspects of the OpenSL ES programming model that may be unfamiliar to new developers are the
+distinction between objects and interfaces, and the initialization sequence.
+</p>
+
+<p>
+Briefly, an OpenSL ES object is similar to the object concept in
+ programming languages such as Java
+and C++, except an OpenSL ES object is only visible via its associated interfaces.
+ This includes
+the initial interface for all objects, called {@code SLObjectItf}.
+ There is no handle for an object
+itself, only a handle to the {@code SLObjectItf} interface of the object.
+</p>
+
+<p>
+An OpenSL ES object is first <em>created</em>, which returns an {@code SLObjectItf}, then
+<em>realized</em>. This is similar to the common programming pattern of first constructing an
+object (which should never fail other than for lack of memory or invalid parameters), and then
+completing initialization (which may fail due to lack of resources). The realize step gives the
+implementation a logical place to allocate additional resources if needed.
+</p>
+
+<p>
+As part of the API to create an object, an application specifies an array of desired interfaces
+that it plans to acquire later. Note that this array does not automatically
+ acquire the interfaces;
+it merely indicates a future intention to acquire them. Interfaces are distinguished as
+<em>implicit</em> or <em>explicit</em>. An explicit interface must be listed in the array if it
+will be acquired later. An implicit interface need not be listed in the
+ object create array, but
+there is no harm in listing it there. OpenSL ES has one more kind of interface called
+<em>dynamic</em>, which does not need to be specified in the object
+ create array and can be added
+later after the object is created. The Android implementation provides
+ a convenience feature to
+avoid this complexity; see the
+ <a href="#dynamic-interfaces">Dynamic interfaces at object creation</a> section above.
+</p>
+
+<p>
+After the object is created and realized, the application should acquire interfaces for each
+feature it needs, using {@code GetInterface} on the initial {@code SLObjectItf}.
+</p>
+
+<p>
+Finally, the object is available for use via its interfaces, though note that
+ some objects require
+further setup. In particular, an audio player with URI data source needs a bit
+ more preparation in
+order to detect connection errors. See the
+ <a href="#prefetch">Audio player prefetch</a> section for details.
+</p>
+
+<p>
+After your application is done with the object, you should explicitly destroy it; see the
+<a href="#destroy">Destroy</a> section below.
+</p>
+
+<h2 id="prefetch">Audio Player Prefetch</h2>
+
+<p>
+For an audio player with URI data source, {@code Object::Realize} allocates
+ resources but does not
+connect to the data source (<em>prepare</em>) or begin pre-fetching data. These occur once the
+player state is set to either {@code SL_PLAYSTATE_PAUSED} or {@code SL_PLAYSTATE_PLAYING}.
+</p>
+
+<p>
+Some information may still be unknown until relatively late in this sequence. In
+particular, initially {@code Player::GetDuration} returns {@code SL_TIME_UNKNOWN} and
+{@code MuteSolo::GetChannelCount} either returns successfully with channel count zero or the
+error result {@code SL_RESULT_PRECONDITIONS_VIOLATED}. These APIs return the proper values
+once they are known.
+</p>
+
+<p>
+Other properties that are initially unknown include the sample rate and
+ actual media content type
+based on examining the content's header (as opposed to the
+ application-specified MIME type and
+container type). These are also determined later during
+ prepare/prefetch, but there are no APIs to
+retrieve them.
+</p>
+
+<p>
+The prefetch status interface is useful for detecting when all information
+ is available, or your
+application can poll periodically. Note that some information, such as the
+ duration of a streaming
+MP3, may <em>never</em> be known.
+</p>
+
+<p>
+The prefetch status interface is also useful for detecting errors. Register a callback
+ and enable
+at least the {@code SL_PREFETCHEVENT_FILLLEVELCHANGE} and {@code SL_PREFETCHEVENT_STATUSCHANGE}
+events. If both of these events are delivered simultaneously, and
+{@code PrefetchStatus::GetFillLevel} reports a zero level, and
+{@code PrefetchStatus::GetPrefetchStatus} reports {@code SL_PREFETCHSTATUS_UNDERFLOW},
+ then this
+indicates a non-recoverable error in the data source. This includes the inability to
+ connect to the
+data source because the local filename does not exist or the network URI is invalid.
+</p>
+
+<p>
+The next version of OpenSL ES is expected to add more explicit support for
+ handling errors in the
+data source. However, for future binary compatibility, we intend to continue
+ to support the current
+method for reporting a non-recoverable error.
+</p>
+
+<p>
+In summary, a recommended code sequence is:
+</p>
+
+<ol>
+ <li>{@code Engine::CreateAudioPlayer}</li>
+ <li>{@code Object:Realize}</li>
+ <li>{@code Object::GetInterface} for {@code SL_IID_PREFETCHSTATUS}</li>
+ <li>{@code PrefetchStatus::SetCallbackEventsMask}</li>
+ <li>{@code PrefetchStatus::SetFillUpdatePeriod}</li>
+ <li>{@code PrefetchStatus::RegisterCallback}</li>
+ <li>{@code Object::GetInterface} for {@code SL_IID_PLAY}</li>
+ <li>{@code Play::SetPlayState} to {@code SL_PLAYSTATE_PAUSED}, or
+ {@code SL_PLAYSTATE_PLAYING}</li>
+</ol>
+
+<p class="note"><strong>Note: </strong>
+Preparation and prefetching occur here; during this time your callback is called with
+periodic status updates.
+</p>
+
+<h2 id="destroy">Destroy</h2>
+
+<p>
+Be sure to destroy all objects when exiting from your application.
+ Objects should be destroyed in
+reverse order of their creation, as it is not safe to destroy an object that has any dependent
+objects. For example, destroy in this order: audio players and recorders, output mix, and then
+finally the engine.
+</p>
+
+<p>
+OpenSL ES does not support automatic garbage collection or
+<a class="external-link" href="http://en.wikipedia.org/wiki/Reference_counting">reference
+counting</a> of interfaces. After you call {@code Object::Destroy}, all extant
+ interfaces that are
+derived from the associated object become undefined.
+</p>
+
+<p>
+The Android OpenSL ES implementation does not detect the incorrect use of such interfaces.
+Continuing to use such interfaces after the object is destroyed can cause your application to
+crash or behave in unpredictable ways.
+</p>
+
+<p>
+We recommend that you explicitly set both the primary object interface and all associated
+interfaces to NULL as part of your object destruction sequence, which prevents the accidental
+misuse of a stale interface handle.
+</p>
+
+<h2 id="panning">Stereo Panning</h2>
+
+<p>
+When {@code Volume::EnableStereoPosition} is used to enable stereo panning of a mono source,
+ there is a 3-dB reduction in total
+<a class="external-link" href="http://en.wikipedia.org/wiki/Sound_power_level">sound power
+level</a>. This is needed to permit the total sound power level to remain constant as
+ the source is
+panned from one channel to the other. Therefore, only enable stereo positioning if you need
+it. See the Wikipedia article on
+<a class="external-link" href="http://en.wikipedia.org/wiki/Panning_(audio)">audio panning</a>
+ for more information.
+</p>
+
+<h2 id="callbacks">Callbacks and Threads</h2>
+
+<p>
+Callback handlers are generally called synchronously with respect to the event. That is, at the
+moment and location that the event is detected by the implementation. This point is
+asynchronous with respect to the application, so you should use a non-blocking synchronization
+mechanism to control access to any variables shared between the application and the callback
+handler. In the example code, such as for buffer queues, we have either omitted this
+synchronization or used blocking synchronization in the interest of simplicity. However, proper
+non-blocking synchronization is critical for any production code.
+</p>
+
+<p>
+Callback handlers are called from internal non-application threads that are not attached to the
+Android runtime, so they are ineligible to use JNI. Because these internal threads are
+critical to
+the integrity of the OpenSL ES implementation, a callback handler should also not block
+ or perform
+excessive work.
+</p>
+
+<p>
+If your callback handler needs to use JNI or execute work that is not proportional to the
+callback, the handler should instead post an event for another thread to process. Examples of
+acceptable callback workload include rendering and enqueuing the next output buffer
+(for an AudioPlayer), processing the just-filled input buffer and enqueueing the next
+ empty buffer
+(for an AudioRecorder), or simple APIs such as most of the <em>Get</em> family. See the
+<a href="#perform">Performance</a> section below regarding the workload.
+</p>
+
+<p>
+Note that the converse is safe: an Android application thread that has entered JNI
+ is allowed to
+directly call OpenSL ES APIs, including those that block. However, blocking calls are not
+recommended from the main thread, as they may result in
+ <em>Application Not Responding</em> (ANR).
+</p>
+
+<p>
+The determination regarding the thread that calls a callback handler is largely left up to the
+implementation. The reason for this flexibility is to permit future optimizations,
+ especially on
+multi-core devices.
+</p>
+
+<p>
+The thread on which the callback handler runs is not guaranteed to have the same
+ identity across
+different calls. Therefore, do not rely on the {@code pthread_t returned by pthread_self()}
+ or the
+{@code pid_t returned by gettid()} to be consistent across calls. For the same reason,
+ do not use
+the thread local storage (TLS) APIs such as {@code pthread_setspecific()} and
+{@code pthread_getspecific()} from a callback.
+</p>
+
+<p>
+The implementation guarantees that concurrent callbacks of the same kind, for the
+ same object, does
+not occur. However, concurrent callbacks of different kinds for the same object are possible on
+different threads.
+</p>
+
+<h2 id="perform">Performance</h2>
+
+<p>
+As OpenSL ES is a native C API, non-runtime application threads that call OpenSL ES have no
+runtime-related overhead such as garbage collection pauses. With one exception described below,
+there is no additional performance benefit to the use of OpenSL ES other than this.
+ In particular,
+the use of OpenSL ES does not guarantee enhancements such as lower audio latency and higher
+scheduling priority over that which the platform generally provides. On the other hand, as the
+Android platform and specific device implementations continue to evolve, an OpenSL ES application
+can expect to benefit from any future system performance improvements.
+</p>
+
+<p>
+One such evolution is support for reduced
+<a href="{@docRoot}ndk/guides/audio/output-latency.html">audio output latency</a>.
+The underpinnings for reduced
+output latency were first included in Android 4.1 (API level 16), and then
+continued progress occurred in Android 4.2 (API level 17). These improvements are available via
+OpenSL ES for device implementations that
+ claim feature {@code android.hardware.audio.low_latency}.
+If the device doesn't claim this feature but supports Android 2.3 (API level 9)
+or later, then you can still use the OpenSL ES APIs but the output latency may be higher.
+ The lower
+output latency path is used only if the application requests a buffer size and sample rate
+ that are
+compatible with the device's native output configuration. These parameters are
+ device-specific and
+should be obtained as described below.
+</p>
+
+<p>
+Beginning with Android 4.2 (API level 17), an application can query for the
+platform native or optimal output sample rate and buffer size for the device's primary output
+stream. When combined with the feature test just mentioned, an app can now configure itself
+appropriately for lower latency output on devices that claim support.
+</p>
+
+<p>
+For Android 4.2 (API level 17) and earlier, a buffer count of two or more is
+required for lower latency. Beginning with Android 4.3 (API level 18), a buffer
+count of one is sufficient for lower latency.
+</p>
+
+<p>
+All OpenSL ES interfaces for output effects preclude the lower latency path.
+</p>
+
+<p>
+The recommended sequence is as follows:
+</p>
+
+<ol>
+ <li>Check for API level 9 or higher to confirm the use of OpenSL ES.</li>
+ <li>Check for the {@code android.hardware.audio.low_latency} feature using code such as this:
+ <pre>import android.content.pm.PackageManager;
+...
+PackageManager pm = getContext().getPackageManager();
+boolean claimsFeature = pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);
+ </pre></li>
+ <li>Check for API level 17 or higher to confirm the use of
+ {@code android.media.AudioManager.getProperty()}.</li>
+ <li>Get the native or optimal output sample rate and buffer size for this device's
+ primary output
+ stream using code such as this:
+ <pre>import android.media.AudioManager;
+...
+AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+String sampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));
+String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER));
+ </pre>
+ Note that {@code sampleRate} and {@code framesPerBuffer} are <em>strings</em>. First check for
+ null and then convert to int using {@code Integer.parseInt()}.</li>
+ <li>Now use OpenSL ES to create an AudioPlayer with PCM buffer queue data locator.</li>
+</ol>
+
+<p class="note"><strong>Note: </strong>
+You can use the
+<a class="external-link"
+ href="https://play.google.com/store/apps/details?id=com.levien.audiobuffersize">
+ Audio Buffer Size</a>
+test app to determine the native buffer size and sample rate for OpenSL ES audio
+applications on your audio device. You can also visit GitHub to view <a class="external-link"
+href="https://github.com/gkasten/high-performance-audio/tree/master/audio-buffer-size">
+audio-buffer-size</a> samples.
+
+<p>
+The number of lower latency audio players is limited. If your application requires more
+than a few
+audio sources, consider mixing your audio at the application level. Be sure to destroy your audio
+players when your activity is paused, as they are a global resource shared with other apps.
+</p>
+
+<p>
+To avoid audible glitches, the buffer queue callback handler must execute within a small and
+predictable time window. This typically implies no unbounded blocking on mutexes, conditions,
+or I/O operations. Instead consider <em>try locks</em>, locks and waits with timeouts, and
+<a class="external-link"
+ href="https://source.android.com/devices/audio/avoiding_pi.html#nonBlockingAlgorithms">
+ non-blocking algorithms</a>.
+</p>
+
+<p>
+The computation required to render the next buffer (for AudioPlayer) or consume the previous
+buffer (for AudioRecord) should take approximately the same amount of time for each callback.
+Avoid algorithms that execute in a non-deterministic amount of time or are <em>bursty</em> in
+their computations. A callback computation is bursty if the CPU time spent in any given callback
+is significantly larger than the average. In summary, the ideal is for the CPU execution time of
+the handler to have variance near zero, and for the handler to not block for unbounded times.
+</p>
+
+<p>
+Lower latency audio is possible for these outputs only:
+</p>
+
+<ul>
+ <li>On-device speakers.</li>
+ <li>Wired headphones.</li>
+ <li>Wired headsets.</li>
+ <li>Line out.</li>
+ <li><a class="external-link" href="https://source.android.com/devices/audio/usb.html">
+ USB digital
+ audio</a>.</li>
+</ul>
+
+<p>
+On some devices, speaker latency is higher than other paths due to digital signal processing for
+speaker correction and protection.
+</p>
+
+<p>
+As of API level 21,
+<a href="{@docRoot}ndk/guides/audio/input-latency.html">lower latency audio input</a>
+ is supported
+on select devices. To take advantage of
+this feature, first confirm that lower latency output is available as described above. The
+capability for lower latency output is a prerequisite for the lower latency input feature. Then,
+create an AudioRecorder with the same sample rate and buffer size as would be used for output.
+OpenSL ES interfaces for input effects preclude the lower latency path. The record preset
+{@code SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION} must be used for lower latency; this preset
+disables device-specific digital signal processing that may add latency to the input path. For
+more information on record presets, see the <a href="#configuration-interface">Android
+configuration interface</a> section above.
+</p>
+
+<p>
+For simultaneous input and output, separate buffer queue completion handlers are used for each
+side. There is no guarantee of the relative order of these callbacks, or the synchronization of
+the audio clocks, even when both sides use the same sample rate. Your application
+ should buffer the
+data with proper buffer synchronization.
+</p>
+
+<p>
+One consequence of potentially independent audio clocks is the need for asynchronous sample rate
+conversion. A simple (though not ideal for audio quality) technique for asynchronous sample rate
+conversion is to duplicate or drop samples as needed near a zero-crossing point.
+ More sophisticated
+conversions are possible.
+</p>
+
+<h2 id="sandp">Security and Permissions</h2>
+
+<p>
+As far as who can do what, security in Android is done at the process level. Java programming
+language code cannot do anything more than native code, nor can native code do anything more than
+Java programming language code. The only differences between them are the available APIs.
+</p>
+
+<p>
+Applications using OpenSL ES must request the permissions that they would need for similar
+non-native APIs. For example, if your application records audio, then it needs the
+{@code android.permission.RECORD_AUDIO} permission. Applications that use audio effects need
+{@code android.permission.MODIFY_AUDIO_SETTINGS}. Applications that play network URI resources
+need {@code android.permission.NETWORK}. See
+<a href="https://developer.android.com/training/permissions/index.html">Working with System
+Permissions</a> for more information.
+</p>
+
+<p>
+Depending on the platform version and implementation, media content parsers and
+ software codecs may
+run within the context of the Android application that calls OpenSL ES (hardware codecs are
+abstracted but are device-dependent). Malformed content designed to exploit parser and codec
+vulnerabilities is a known attack vector. We recommend that you play media only from trustworthy
+sources or that you partition your application such that code that handles media from
+untrustworthy sources runs in a relatively <em>sandboxed</em> environment. For example, you could
+process media from untrustworthy sources in a separate process. Though both processes would still
+run under the same UID, this separation does make an attack more difficult.
+</p>
diff --git a/docs/html/ndk/guides/audio/output-latency.jd b/docs/html/ndk/guides/audio/output-latency.jd
new file mode 100644
index 0000000..4aa97a6
--- /dev/null
+++ b/docs/html/ndk/guides/audio/output-latency.jd
@@ -0,0 +1,310 @@
+page.title=Audio Output Latency
+@jd:body
+
+<div id="qv-wrapper">
+ <div id="qv">
+ <h2>On this page</h2>
+
+ <ol>
+ <li><a href="#prereq">Prerequisites</a></li>
+ <li><a href="#low-lat-track">Obtain a Low-Latency Track</a></li>
+ <li><a href="#buffer-size">Use the Optimal Buffer Size When Enqueuing Audio Data</a></li>
+ <li><a href="#warmup-lat">Avoid Warmup Latency</a></li>
+ </ol>
+ <h2>Also read</h2>
+
+ <ol>
+ <li><a href="https://source.android.com/devices/audio/latency_app.html" class="external-link">
+ Audio Latency for App Developers</a></li>
+ <li><a href="https://source.android.com/devices/audio/latency_contrib.html" class="external-link">
+ Contributors to Audio Latency</a></li>
+ <li><a href="https://source.android.com/devices/audio/latency_measure.html" class="external-link">
+ Measuring Audio Latency</a></li>
+ <li><a href="https://source.android.com/devices/audio/warmup.html" class="external-link">
+ Audio Warmup</a></li>
+ <li><a href="https://en.wikipedia.org/wiki/Latency_%28audio%29" class="external-link">
+ Latency (audio)</a></li>
+ <li><a href="https://en.wikipedia.org/wiki/Round-trip_delay_time" class="external-link">
+ Round-trip delay time</a></li>
+ </ol>
+ </div>
+ </div>
+
+<a href="https://www.youtube.com/watch?v=PnDK17zP9BI" class="notice-developers-video">
+<div>
+ <h3>Video</h3>
+ <p>Audio latency: buffer sizes</p>
+</div>
+</a>
+
+<a href="https://www.youtube.com/watch?v=92fgcUNCHic" class="notice-developers-video">
+<div>
+ <h3>Video</h3>
+ <p>Building great multi-media experiences on Android</p>
+</div>
+</a>
+
+<p>This page describes how to develop your audio app for low-latency output and how to avoid
+warmup latency.</p>
+
+<h2 id="prereq">Prerequisites</h2>
+
+<p>Low-latency audio is currently only supported when using Android's implementation of the
+OpenSL ES™ API specification, and the Android NDK:
+</p>
+
+<ol>
+ <li>Download and install the <a href="{@docRoot}tools/sdk/ndk/index.html">Android NDK</a>.</li>
+ <li>Read the <a href="{@docRoot}ndk/guides/audio/opensl-for-android.html">OpenSL ES
+ documentation</a>.
+</ol>
+
+<h2 id="low-lat-track">Obtain a Low-Latency Track</h2>
+
+<p>Latency is the time it takes for a signal to travel through a system. These are the common
+types of latency related to audio apps:
+
+<ul>
+ <li><strong>Audio output latency</strong> is the time between an audio sample being generated by an
+app and the sample being played through the headphone jack or built-in speaker.</li>
+
+ <li><strong>Audio input latency</strong> is the time between an audio signal being received by a
+device’s audio input, such as the microphone, and that same audio data being available to an
+app.</li>
+
+ <li><strong>Round-trip latency</strong> is the sum of input latency, app processing time, and
+ output latency.</li>
+
+ <li><strong>Touch latency</strong> is the time between a user touching the screen and that
+touch event being received by an app.</li>
+</ul>
+
+<p>It is difficult to test audio output latency in isolation since it requires knowing exactly
+when the first sample is sent into the audio path (although this can be done using a
+<a href="https://source.android.com/devices/audio/testing_circuit.html" class="external-link">
+light testing circuit</a> and an oscilloscope). If you know the round-trip audio latency, you can
+use the rough rule of thumb: <strong>audio output latency is half the round-trip audio latency
+over paths without signal processing</strong>.
+</p>
+
+<p>To obtain the lowest latency, you must supply audio data that matches the device's optimal
+sample rate and buffer size. For more information, see
+<a href="https://source.android.com/devices/audio/latency_design.html" class="external-link">
+Design For Reduced Latency</a>.</p>
+
+<h3>Obtain the optimal sample rate</h3>
+
+<p>In Java, you can obtain the optimal sample rate from AudioManager as shown in the following
+code example:</p>
+
+<pre>
+AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+String frameRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
+int frameRateInt = Integer.parseInt(frameRate);
+if (frameRateInt == 0) frameRateInt = 44100; // Use a default value if property not found
+</pre>
+
+<p class="note">
+ <strong>Note:</strong> The sample rate refers to the rate of each stream. If your source audio
+ has two channels (stereo), then you will have one stream playing a pair of samples (frame) at
+ <a href="{@docRoot}reference/android/media/AudioManager.html#PROPERTY_OUTPUT_SAMPLE_RATE">
+ PROPERTY_OUTPUT_SAMPLE_RATE</a>.
+</p>
+
+<h3>Use the optimal sample rate when creating your audio player</h3>
+
+<p>Once you have the optimal sample output rate, you can supply it when creating your player
+using OpenSL ES:</p>
+
+<pre>
+// create buffer queue audio player
+void Java_com_example_audio_generatetone_MainActivity_createBufferQueueAudioPlayer
+ (JNIEnv* env, jclass clazz, jint sampleRate, jint framesPerBuffer)
+{
+ ...
+ // specify the audio source format
+ SLDataFormat_PCM format_pcm;
+ format_pcm.numChannels = 2;
+ format_pcm.samplesPerSec = (SLuint32) sampleRate * 1000;
+ ...
+}
+</pre>
+
+<p class="note">
+ <strong>Note:</strong> {@code samplesPerSec} refers to the <em>sample rate per channel in
+ millihertz</em> (1 Hz = 1000 mHz).
+</p>
+
+<h3>Avoid adding output interfaces that involve signal processing</h3>
+
+<p>Only these interfaces are supported by the fast mixer:</p>
+
+<ul>
+ <li>SL_IID_ANDROIDSIMPLEBUFFERQUEUE</li>
+ <li>SL_IID_VOLUME</li>
+ <li>SL_IID_MUTESOLO</li>
+</ul>
+
+<p>These interfaces are not allowed because they involve signal processing and will cause
+your request for a fast-track to be rejected:</p>
+
+<ul>
+ <li>SL_IID_BASSBOOST</li>
+ <li>SL_IID_EFFECTSEND</li>
+ <li>SL_IID_ENVIRONMENTALREVERB</li>
+ <li>SL_IID_EQUALIZER</li>
+ <li>SL_IID_PLAYBACKRATE</li>
+ <li>SL_IID_PRESETREVERB</li>
+ <li>SL_IID_VIRTUALIZER</li>
+ <li>SL_IID_ANDROIDEFFECT</li>
+ <li>SL_IID_ANDROIDEFFECTSEND</li>
+</ul>
+
+<p>When you create your player, make sure you only add <em>fast</em> interfaces, as shown in
+the following example:</p>
+
+<pre>
+const SLInterfaceID interface_ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };
+</pre>
+
+<h3>Verify you're using a low-latency track</h3>
+
+<p>Complete these steps to verify that you have successfully obtained a low-latency track:</p>
+
+<ol>
+ <li>Launch your app and then run the following command:</li>
+
+<pre>
+adb shell ps | grep your_app_name
+</pre>
+
+ <li>Make a note of your app's process ID.</li>
+
+ <li>Now, play some audio from your app. You have approximately three seconds to run the
+following command from the terminal:</li>
+
+<pre>
+adb shell dumpsys media.audio_flinger
+</pre>
+
+ <li>Scan for your process ID. If you see an <em>F</em> in the <em>Name</em> column, it's on a
+low-latency track (the F stands for <em>fast track</em>).</li>
+
+</ol>
+
+<h3>Measure round-trip latency</h3>
+
+<p>You can measure round-trip audio latency by creating an app that generates an audio signal,
+listens for that signal, and measures the time between sending it and receiving it.
+Alternatively, you can install this
+<a href="https://play.google.com/store/apps/details?id=org.drrickorang.loopback" class="external-link">
+latency testing app</a>. This performs a round-trip latency test using the
+<a href="https://source.android.com/devices/audio/latency_measure.html#larsenTest" class="external-link">
+Larsen test</a>. You can also
+<a href="https://github.com/gkasten/drrickorang/tree/master/LoopbackApp" class="external-link">
+view the source code</a> for the latency testing app.</p>
+
+<p>Since the lowest latency is achieved over audio paths with minimal signal processing, you may
+also want to use an
+<a href="https://source.android.com/devices/audio/latency_measure.html#loopback" class="external-link">
+Audio Loopback Dongle</a>, which allows the test to be run over the headset connector.</p>
+
+<p>The lowest possible round-trip audio latency varies greatly depending on device model and
+Android build. You can measure it yourself using the latency testing app and loopback
+dongle. When creating apps for <em>Nexus devices</em>, you can also use the
+<a href="https://source.android.com/devices/audio/latency_measurements.html" class="external-link">
+published measurements</a>.</p>
+
+<p>You can also get a rough idea of audio performance by testing whether the device reports
+support for the
+<a href="http://developer.android.com/reference/android/content/pm/PackageManager.html#FEATURE_AUDIO_LOW_LATENCY">
+low_latency</a> and
+<a href="http://developer.android.com/reference/android/content/pm/PackageManager.html#FEATURE_AUDIO_PRO">
+pro</a> hardware features.</p>
+
+<h3>Review the CDD and audio latency</h3>
+
+<p>The Android Compatibility Definition Document (CDD) enumerates the hardware and software
+requirements of a compatible Android device.
+See <a href="https://source.android.com/compatibility/" class="external-link">
+Android Compatibility</a> for more information on the overall compatibility program, and
+<a href="https://static.googleusercontent.com/media/source.android.com/en//compatibility/android-cdd.pdf" class="external-link">
+CDD</a> for the actual CDD document.</p>
+
+<p>In the CDD, round-trip latency is specified as 20 ms or lower (even though musicians
+generally require 10 ms). This is because there are important use cases that are enabled by
+20 ms.</p>
+
+<p>There is currently no API to determine audio latency over any path on an Android device at
+runtime. You can, however, use the following hardware feature flags to find out whether the
+device makes any guarantees for latency:</p>
+
+<ul>
+ <li><a href="http://developer.android.com/reference/android/content/pm/PackageManager.html#FEATURE_AUDIO_LOW_LATENCY">
+android.hardware.audio.low_latency</a> indicates a continuous output latency of 45 ms or
+less.</li>
+
+ <li><a href="http://developer.android.com/reference/android/content/pm/PackageManager.html#FEATURE_AUDIO_PRO">
+android.hardware.audio.pro</a> indicates a continuous round-trip latency of 20 ms or
+less.</li>
+</ul>
+
+<p>The criteria for reporting these flags is defined in the CDD in sections <em>5.6 Audio
+Latency</em> and <em>5.10 Professional Audio</em>.</p>
+
+<p>Here’s how to check for these features in Java:</p>
+
+<pre>
+boolean hasLowLatencyFeature =
+ getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);
+
+boolean hasProFeature =
+ getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_PRO);
+</pre>
+
+<p>Regarding the relationship of audio features, the {@code android.hardware.audio.low_latency}
+feature is a prerequisite for {@code android.hardware.audio.pro}. A device can implement
+{@code android.hardware.audio.low_latency} and not {@code android.hardware.audio.pro}, but not
+vice-versa.</p>
+
+<h2 id="buffer-size">Use the Optimal Buffer Size When Enqueuing Audio Data</h2>
+
+<p>You can obtain the optimal buffer size in a similar way to the optimal frame rate, using the
+AudioManager API:</p>
+
+<pre>
+AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
+int framesPerBufferInt = Integer.parseInt(framesPerBuffer);
+if (framesPerBufferInt == 0) framesPerBufferInt = 256; // Use default
+</pre>
+
+<p>The
+<a href="{@docRoot}reference/android/media/AudioManager.html#PROPERTY_OUTPUT_FRAMES_PER_BUFFER">
+PROPERTY_OUTPUT_FRAMES_PER_BUFFER</a> property indicates the number of audio frames
+that the HAL (Hardware Abstraction Layer) buffer can hold. You should construct your audio
+buffers so that they contain an exact multiple of this number. If you use the correct number
+of audio frames, your callbacks occur at regular intervals, which reduces jitter.</p>
+
+<p>It is important to use the API to determine buffer size rather than using a hardcoded value,
+ because HAL buffer sizes differ across devices and across Android builds.</p>
+
+<h2 id="warmup-lat">Avoid Warmup Latency</h2>
+
+<p>When you enqueue audio data for the first time, it takes a small, but still significant,
+amount of time for the device audio circuit to warm up. To avoid this warmup latency, you should
+enqueue buffers of audio data containing silence, as shown in the following code example:</p>
+
+<pre>
+#define CHANNELS 1
+static short* silenceBuffer;
+int numSamples = frames * CHANNELS;
+silenceBuffer = malloc(sizeof(*silenceBuffer) * numSamples);
+ for (i = 0; i < numSamples; i++) {
+ silenceBuffer[i] = 0;
+ }
+</pre>
+
+<p>At the point when audio should be produced, you can switch to enqueuing buffers containing
+real audio data.</p>
+
diff --git a/docs/html/ndk/guides/audio/sample-rates.jd b/docs/html/ndk/guides/audio/sample-rates.jd
new file mode 100644
index 0000000..da68597
--- /dev/null
+++ b/docs/html/ndk/guides/audio/sample-rates.jd
@@ -0,0 +1,151 @@
+page.title=Sample Rates
+@jd:body
+
+<div id="qv-wrapper">
+ <div id="qv">
+ <h2>On this page</h2>
+
+ <ol>
+ <li><a href="#best">Best Practices for Sampling and Resampling</a></li>
+ <li><a href="#info">For More Information</a></li>
+ </ol>
+ </div>
+ </div>
+
+<a class="notice-developers-video" href="https://www.youtube.com/watch?v=6Dl6BdrA-sQ">
+<div>
+ <h3>Video</h3>
+ <p>Sample Rates: Why Can't We All Just Agree?</p>
+</div>
+</a>
+
+<p>As of Android 5.0 (Lollipop), the audio resamplers are now entirely based
+on FIR filters derived from a Kaiser windowed-sinc function. The Kaiser windowed-sinc
+offers the following properties:
+<ul>
+ <li>It is straightforward to calculate for its design parameters (stopband
+ ripple, transition bandwidth, cutoff frequency, filter length).</li>
+<li>It is nearly optimal for reduction of stopband energy compared to overall
+energy.</li>
+</ul>
+See P.P. Vaidyanathan, <a class="external-link"
+href="https://drive.google.com/file/d/0B7tBh7YQV0DGak9peDhwaUhqY2c/view">
+<i>Multirate Systems and Filter Banks</i></a>, p. 50 for discussions of the
+Kaiser Window and its optimality and relationship to Prolate Spheroidal
+Windows.</p>
+
+<p>The design parameters are automatically computed based on internal
+quality determination and the sampling ratios desired. Based on the
+design parameters, the windowed-sinc filter is generated. For music use,
+the resampler for 44.1 to 48 kHz and vice versa is generated at a higher
+quality than for arbitrary frequency conversion.</p>
+
+<p>The audio resamplers provide increased quality, as well as speed
+to achieve that quality. But resamplers can introduce small amounts
+of passband ripple and aliasing harmonic noise, and they can cause some high
+frequency loss in the transition band, so avoid using them unnecessarily.</p>
+
+<h2 id="best">Best Practices for Sampling and Resampling</h2>
+<p>This section describes some best practices to help you avoid problems with sampling rates.</p>
+<h3>Choose the sampling rate to fit the device</h3>
+
+<p>In general, it is best to choose the sampling rate to fit the device,
+typically 44.1 kHz or 48 kHz. Use of a sample rate greater than
+48 kHz will typically result in decreased quality because a resampler must be
+used to play back the file.</p>
+
+<h3>Use simple resampling ratios (fixed versus interpolated polyphases)</h3>
+
+<p>The resampler operates in one of the following modes:</p>
+<ul>
+ <li>Fixed polyphase mode. The filter coefficients for each polyphase are precomputed.</li>
+ <li>Interpolated polyphase mode. The filter coefficients for each polyphase must
+be interpolated from the nearest two precomputed polyphases.</li>
+</ul>
+<p>The resampler is fastest in fixed polyphase mode, when the ratio of input
+rate over output rate L/M (taking out the greatest common divisor)
+has M less than 256. For example, for 44,100 to 48,000 conversion, L = 147,
+M = 160.</p>
+
+<p>In fixed polyphase mode, the sampling rate is locked for as
+many samples converted and does not change. In interpolated polyphase
+mode, the sampling rate is approximate. The drift is generally on the
+order of one sample over a few hours of playback on a 48-kHz device.
+This is not usually a concern because approximation error is much less than
+frequency error of internal quartz oscillators, thermal drift, or jitter
+ (typically tens of ppm).</p>
+
+<p>Choose simple-ratio sampling rates such as 24 kHz (1:2) and 32 kHz (2:3) when playing back
+ on a 48-kHz device, even though other sampling
+rates and ratios may be permitted through AudioTrack.</p>
+
+<h3>Use upsampling rather than downsampling when changing sample rates</h3>
+
+<p>Sampling rates can be changed on the fly. The granularity of
+such change is based on the internal buffering (typically a few hundred
+samples), not on a sample-by-sample basis. This can be used for effects.</p>
+
+<p>Do not dynamically change sampling rates when
+downsampling. When changing sample rates after an audio track is
+created, differences of around 5 to 10 percent from the original rate may
+trigger a filter recomputation when downsampling (to properly suppress
+aliasing). This can consume computing resources and may cause an audible click
+if the filter is replaced in real time.</p>
+
+<h3>Limit downsampling to no more than 6:1</h3>
+
+<p>Downsampling is typically triggered by hardware device requirements. When the
+ Sample Rate converter is used for downsampling,
+try to limit the downsampling ratio to no more than 6:1 for good aliasing
+suppression (for example, no greater downsample than 48,000 to 8,000). The filter
+lengths adjust to match the downsampling ratio, but you sacrifice more
+transition bandwidth at higher downsampling ratios to avoid excessively
+increasing the filter length. There are no similar aliasing concerns for
+upsampling. Note that some parts of the audio pipeline
+may prevent downsampling greater than 2:1.</p>
+
+<h3 id="latency">If you are concerned about latency, do not resample</h3>
+
+<p>Resampling prevents the track from being placed in the FastMixer
+path, which means that significantly higher latency occurs due to the additional,
+ larger buffer in the ordinary Mixer path. Furthermore,
+ there is an implicit delay from the filter length of the resampler,
+ though this is typically on the order of one millisecond or less,
+ which is not as large as the additional buffering for the ordinary Mixer path
+ (typically 20 milliseconds).</p>
+
+<h2 id="info">For More Information</h2>
+<p>This section lists some additional resources about sampling and resampling.</p>
+
+<h3>Sample rates</h3>
+
+<p>
+<a href="http://en.wikipedia.org/wiki/Sampling_%28signal_processing%29" class="external-link" >
+Sampling (signal processing)</a> at Wikipedia.</p>
+
+<h3>Resampling</h3>
+
+<p><a href="http://en.wikipedia.org/wiki/Sample_rate_conversion" class="external-link" >
+Sample rate conversion</a> at Wikipedia.</p>
+
+<p><a href="http://source.android.com/devices/audio/src.html" class="external-link" >
+Sample Rate Conversion</a> at source.android.com.</p>
+
+<h3>The high bit-depth and high kHz controversy</h3>
+
+<p><a href="http://people.xiph.org/~xiphmont/demo/neil-young.html" class="external-link" >
+24/192 Music Downloads ... and why they make no sense</a>
+by Christopher "Monty" Montgomery of Xiph.Org.</p>
+
+<p><a href="https://www.youtube.com/watch?v=cIQ9IXSUzuM" class="external-link" >
+D/A and A/D | Digital Show and Tell</a>
+video by Christopher "Monty" Montgomery of Xiph.Org.</p>
+
+<p><a href="http://www.trustmeimascientist.com/2013/02/04/the-science-of-sample-rates-when-higher-is-better-and-when-it-isnt/" class="external-link">
+The Science of Sample Rates (When Higher Is Better - And When It Isn't)</a>.</p>
+
+<p><a href="http://www.image-line.com/support/FLHelp/html/app_audio.htm" class="external-link" >
+Audio Myths & DAW Wars</a></p>
+
+<p><a href="http://forums.stevehoffman.tv/threads/192khz-24bit-vs-96khz-24bit-debate-interesting-revelation.317660/" class="external-link">
+192kHz/24bit vs. 96kHz/24bit "debate"- Interesting revelation</a></p>
diff --git a/docs/html/ndk/guides/guides_toc.cs b/docs/html/ndk/guides/guides_toc.cs
index 98fc54d..09b2a12 100644
--- a/docs/html/ndk/guides/guides_toc.cs
+++ b/docs/html/ndk/guides/guides_toc.cs
@@ -63,13 +63,23 @@
</ul>
</li>
- <li class="nav-section">
+ <li class="nav-section">
<div class="nav-section-header"><a href="<?cs var:toroot ?>ndk/guides/audio/index.html">
<span class="en">Audio</span></a></div>
<ul>
<li><a href="<?cs var:toroot ?>ndk/guides/audio/basics.html">Basics</a></li>
<li><a href="<?cs var:toroot ?>ndk/guides/audio/opensl-for-android.html">OpenSL ES for
Android</a></li>
+ <li><a href="<?cs var:toroot ?>ndk/guides/audio/input-latency.html">Audio Input
+ Latency</a></li>
+ <li><a href="<?cs var:toroot ?>ndk/guides/audio/output-latency.html">Audio Output
+ Latency</a></li>
+ <li><a href="<?cs var:toroot ?>ndk/guides/audio/floating-point.html">Floating-Point
+ Audio</a></li>
+ <li><a href="<?cs var:toroot ?>ndk/guides/audio/sample-rates.html">Sample Rates
+ </a></li>
+ <li><a href="<?cs var:toroot ?>ndk/guides/audio/opensl-prog-notes.html">OpenSL ES Programming Notes
+ </a></li>
</ul>
</li>
diff --git a/docs/html/preview/_book.yaml b/docs/html/preview/_book.yaml
index cac6d13..0d4b81b 100644
--- a/docs/html/preview/_book.yaml
+++ b/docs/html/preview/_book.yaml
@@ -24,7 +24,7 @@
- title: Support and Release Notes
path: /preview/support.html
-- title: Set Up to Develop
+- title: Set Up the Preview
path: /preview/setup-sdk.html
path_attributes:
- name: es-lang
diff --git a/docs/html/preview/api-overview.jd b/docs/html/preview/api-overview.jd
index 4fbb160..09227f2 100644
--- a/docs/html/preview/api-overview.jd
+++ b/docs/html/preview/api-overview.jd
@@ -24,6 +24,7 @@
<li><a href="#number-blocking">Number-blocking</a></li>
<li><a href="#call_screening">Call screening</a></li>
<li><a href="#multi-locale_languages">Locales and languages</a></li>
+ <li><a href="#emoji">New Emojis</a></li>
<li><a href="#icu4">ICU4J APIs in Android</a></li>
<li><a href="#gles_32">OpenGL ES 3.2 API</a></li>
<li><a href="#android_tv_recording">Android TV recording</a></li>
@@ -35,8 +36,11 @@
<li><a href="#default_trusted_ca">Default Trusted CA</a></li>
<li><a href="#apk_signature_v2">APK signature scheme v2</a></li>
<li><a href="#scoped_directory_access">Scoped directory access</a></li>
+ <li><a href="#keyboard_shortcuts_helper">Keyboard Shortcuts Helper</a></li>
+ <li><a href="#sustained_performance_api">Sustained Performance API</a></li>
<li><a href="#print_svc">Print service enhancements</a></li>
<li><a href="#virtual_files">Virtual Files</a></li>
+ <li><a href="#framemetrics_api">FrameMetricsListener API</a></li>
</ol>
</div>
</div>
@@ -113,6 +117,13 @@
</li>
<li>
+ <strong>Message style customization</strong>: You can customize more of the
+ user interface labels associated with your notifications using the
+ <code>MessageStyle</code> class. You can configure the message, conversation
+ title, and content view.
+ </li>
+
+ <li>
<strong>Bundled notifications</strong>: The system can group messages
together, for example by message topic, and display the group. A user can
take actions, such as Dismiss or Archive, on them in place. If you’ve
@@ -346,7 +357,7 @@
<p>
Vulkan is only available to apps on devices with Vulkan-capable hardware,
- such as Nexus 5X and Nexus 6P. We're working closely with our
+ such as Nexus 5X, Nexus 6P, and Nexus Player. We're working closely with our
partners to bring Vulkan to more devices as soon as possible.
</p>
@@ -471,6 +482,49 @@
should follow, see <a href="{@docRoot}preview/features/multilingual-support.html"
>Multilingual Support</a>.</p>
+
+<h2 id="emoji">New Emojis</h2>
+
+<p>
+ Android N introduces additional emojis and emoji-related features including
+ skin tone emojis and support for variation
+ selectors. If your app supports emojis,
+ follow the guidelines below to take advantage of these emoji-related features.
+</p>
+
+<ul>
+ <li>
+ <strong>Check that a device contains an emoji before inserting it.</strong>
+ To check which emojis are present in the
+ system font, use the {@link android.graphics.Paint#hasGlyph(String)} method.
+ </li>
+ <li>
+ <strong>Check that an emoji supports variation selectors.</strong>
+ Variation selectors allow you to
+ present certain emojis in color or in black-and-white.
+ On mobile devices, apps should represent emojis in color rather than black-and-white. However,
+ if your app displays emojis inline with text, then it should use the black-and-white variation.
+ To determine whether an emoji has a variation, use the variation selector.
+ For a complete list of characters with variations, review the
+ <em>emoji variation sequences</em> section of the
+ <a class="external-link"
+ href="http://www.unicode.org/Public/9.0.0/ucd/StandardizedVariants-9.0.0d1.txt">
+ Unicode documentation on variations</a>.
+ </li>
+ <li>
+ <strong>Check that an emoji supports skin tone.</strong> Android N allows users to modify the
+ rendered skin tone of emojis to their preference. Keyboard apps should provide visual
+ indications for emojis that have multiple skin tones and should allow users to
+ select the skin tone that they prefer. To determine which system emojis have
+ skin tone modifiers, use the {@link android.graphics.Paint#hasGlyph(String)}
+ method. You can determine which emojis use skin tones by reading the
+ <a class="external-link"
+ href="http://unicode.org/emoji/charts/full-emoji-list.html">
+ Unicode documentation</a>.
+ </li>
+</ul>
+
+
<h2 id="icu4">ICU4J APIs in Android</h2>
<p>
@@ -613,7 +667,7 @@
meet the needs of those users.</p>
<p>For more information, see <code>android.accessibilityservice.GestureDescription</code>
- in the downloadable <a href="{@docRoot}preview/setup-sdk.html#docs-dl">API Reference</a>.</p>
+ in the downloadable <a href="{@docRoot}preview/setup-sdk.html#docs-dl">API Reference</a>.</p>
<h2 id="direct_boot">Direct boot</h2>
@@ -621,7 +675,7 @@
<p>Direct boot improves device startup times and lets registered
apps have limited functionality even after an unexpected reboot.
For example, if an encrypted device reboots while the user is sleeping,
-registered alarms, messages and incoming calls can now continue notify
+registered alarms, messages and incoming calls can now continue to notify
the user as normal. This also means accessibility services can also be
available immediately after a restart.</p>
@@ -723,18 +777,54 @@
<h2 id="apk_signature_v2">APK signature scheme v2</h2>
-<p>The PackageManager class now supports verifying apps using the APK
-signature scheme v2. The APK signature scheme v2 is a whole-file signature scheme
-that significantly improves verification speed and strengthens integrity
- guarantees by detecting any unauthorized changes to APK files.</p>
+<p>
+ Android N introduces APK Signature Scheme v2, a new app-signing scheme that
+ offers faster app install times and more protection against unauthorized
+ alterations to APK files. By default, Android Studio 2.2 and the Android
+ Plugin for Gradle 2.2 sign your app using both APK Signature Scheme v2 and
+ the traditional signing scheme, which uses JAR signing.
+</p>
-<p>To maintain backward-compatibility, an APK must be signed with the v1 signature
-scheme (JAR signature scheme) before being signed with the v2 signature scheme.
-With the v2 signature scheme, verification fails if you sign the APK with an
- additional certificate after signing with the v2 scheme. </p>
+<p>
+ Although we recommend applying APK Signature Scheme v2 to your app, this new
+ scheme is not mandatory. If your app doesn't build properly when using APK
+ Signature Scheme v2, you can disable the new scheme. The disabling process
+ causes Android Studio 2.2 and the Android Plugin for Gradle 2.2 to sign your
+ app using only the traditional signing scheme. To sign with only the
+ traditional scheme, open the module-level <code>build.gradle</code> file, then
+ add the line <code>v2SigningEnabled false</code> to your release signing
+ configuration:
+</p>
-<p>APK signature scheme v2 support will be available later in the N Developer
-Preview.</p>
+<pre>
+ android {
+ ...
+ defaultConfig { ... }
+ signingConfigs {
+ release {
+ storeFile file("myreleasekey.keystore")
+ storePassword "password"
+ keyAlias "MyReleaseKey"
+ keyPassword "password"
+ <strong>v2SigningEnabled false</strong>
+ }
+ }
+ }
+</pre>
+
+<p class="caution"><strong>Caution: </strong> If you sign your app using APK
+ Signature Scheme v2 and make further changes to the app, the app's signature
+ is invalidated. For this reason, use tools such as <code>zipalign</code>
+ before signing your app using APK Signature Scheme v2, not after.
+</p>
+
+<p>
+ For more information, read the Android Studio documents that describe how to
+ <a href="{@docRoot}studio/tools/publishing/app-signing.html#release-mode">
+ sign an app</a> in Android Studio and how to <a href=
+ "{@docRoot}studio/tools/building/configuring-gradle.html#signing"> configure
+ the build file for signing apps</a> using the Android Plugin for Gradle.
+</p>
<h2 id="scoped_directory_access">Scoped directory access</h2>
@@ -757,6 +847,53 @@
<a href="{@docRoot}preview/features/scoped-folder-access.html">Scoped
Directory Access</a> developer documentation.</p>
+<h2 id="keyboard_shortcuts_helper">Keyboard Shortcuts Helper</h2>
+
+<p>
+In Android N, the user can press Meta+/ to trigger a Keyboard Shortcuts
+screen that displays all shortcuts available both from the system and from
+the app in focus. These are retrieved automatically from the app’s menu if
+available, but developers can provide their own fine-tuned shortcuts lists
+for the screen. This is done simply by overriding the following method
+in {@code Activity.java}:
+</p>
+
+<pre>
+public void onProvideKeyboardShortcuts(
+ List<KeyboardShortcutGroup> data, Menu menu, int deviceId)
+</pre>
+
+<p>
+To trigger the Keyboard Shortcuts Helper from anywhere in your app,
+call {@code requestKeyboardShortcutsHelper} for the relevant activity.
+</p>
+
+<h2 id="sustained_performance_api">Sustained Performance API</h2>
+
+<p>
+Performance can fluctuate dramatically for long-running apps, because the
+system throttles system-on-chip engines as device components reach their
+temperature limits. This fluctuation presents a moving target for app
+developers creating high-performance, long-running apps.
+</p>
+
+<p>
+To address these limitations, Android N includes support for
+<em>sustained performance mode</em>, enabling OEMs to provide hints on
+device-performance capabilities for long-running applications. App developers
+can use these hints to tune applications for a predictable,
+consistent level of device performance over long periods of time.
+</p>
+
+<p>
+App developers can try out this new API in the N Developer Preview on
+Nexus 6P devices only. To use this feature,
+set the sustained performance window flag for the window
+you want to run in sustained performance mode. Set this flag using the
+{@code setSustainedPerformanceMode(boolean)} method. The system automatically
+disables this mode when the window is no longer in focus.
+</p>
+
<h2 id="print_svc">Print Service Enhancements</h2>
<p>
@@ -798,6 +935,51 @@
"{@docRoot}preview/setup-sdk.html#docs-dl">API Reference</a>.
</p>
+<h2 id="framemetrics_api">FrameMetricsListener API</h2>
+
+<p>
+The FrameMetricsListener API allows an app to monitor its UI rendering
+performance by exposing a streaming pubsub API to transfer frame
+timing info for the app's current window. The data returned is
+equivalent to that displayed by {@code adb shell dumpsys gfxinfo framestats},
+but is not limited to the past 120 frames.
+</p>
+
+<p>
+You can use FrameMetricsListener to measure interaction-level UI
+performance in production, without a USB connection. This API
+allows collection of data at a much higher granularity than does
+{@code adb shell dumpsys gfxinfo}. This higher granularity is possible because
+the system can collect data for particular interactions in the app; the system
+need not capture a global summary of the entire app’s
+performance, or clear any global state. You can use this
+capability to gather performance data and catch regressions in UI performance
+for real use cases within an app.
+</p>
+
+<p>
+The API provides a callback interface to be implemented and
+registered on the window that you wish to monitor:
+</p>
+
+<pre>
+ public interface FrameMetricsListener {
+ void onMetricsAvailable(Window window, FrameMetrics frameMetrics,
+ int dropCountSinceLastInvocation);
+ }
+</pre>
+
+<p>
+The API returns a FrameMetrics object, which contains timing data that
+the rendering subsystem reports for various milestones in a frame lifecycle.
+The supported metrics are: {@code UNKNOWN_DELAY_DURATION},
+{@code INPUT_HANDLING_DURATION}, {@code ANIMATION_DURATION},
+{@code LAYOUT_MEASURE_DURATION}, {@code DRAW_DURATION}, {@code SYNC_DURATION},
+{@code COMMAND_ISSUE_DURATION}, {@code SWAP_BUFFERS_DURATION},
+{@code TOTAL_DURATION}, and {@code FIRST_DRAW_FRAME}
+</p>
+
+
<h2 id="virtual_files">Virtual Files</h2>
<p>
@@ -879,4 +1061,4 @@
For more information about accessing user files, see the
<a href="{@docRoot}guide/topics/providers/document-provider.html">Storage
Access Frameworks guide</a>.
-</p>
\ No newline at end of file
+</p>
diff --git a/docs/html/preview/features/data-saver.jd b/docs/html/preview/features/data-saver.jd
index ba8f165..c4cab18 100644
--- a/docs/html/preview/features/data-saver.jd
+++ b/docs/html/preview/features/data-saver.jd
@@ -12,10 +12,16 @@
<ol>
<li>
<a href="#status">Checking Data Saver Preferences</a>
+ <ol>
+ <li>
+ <a href="#request-whitelist">Requesting whitelist permissions</a>
+ </li>
+ </ol>
</li>
<li>
- <a href="#monitor-changes">Monitoring Changes to Data Saver Preferences</a>
+ <a href="#monitor-changes">Monitoring Changes to Data Saver
+ Preferences</a>
</li>
<li>
@@ -124,6 +130,27 @@
}
</pre>
+<h3 id="request-whitelist">
+ Requesting whitelist permissions
+</h3>
+
+<p>
+ If your app needs to use data in the background, it can request whitelist
+ permissions by sending a
+ <code>Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS</code>
+ intent containing a URI of your app's package name: for example,
+ <code>package:MY_APP_ID</code>.
+</p>
+
+<p>
+ Sending the intent and URI launches the <strong>Settings</strong> app and
+ displays data usage settings for your app. The user can then decide whether
+ to enable background data for your app. Before you send this intent, it is
+ good practice to first ask the user if they want to launch the
+ <strong>Settings</strong> app for the purpose of enabling background data
+ usage.
+</p>
+
<h2 id="monitor-changes">
Monitoring Changes to Data Saver Preferences
</h2>
@@ -131,9 +158,8 @@
<p>
Apps can monitor changes to Data Saver preferences by creating a {@link
android.content.BroadcastReceiver} to listen for {@code
- ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED} ({@code
- "android.net.conn.RESTRICT_BACKGROUND_CHANGED"}) and dynamically registering
- the receiver with {@link android.content.Context#registerReceiver
+ ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED} and dynamically
+ registering the receiver with {@link android.content.Context#registerReceiver
Context.registerReceiver()}. When an app receives this broadcast, it should
<a href="#status">check if the new Data Saver preferences affect its
permissions</a> by calling {@code
diff --git a/docs/html/preview/features/notification-updates.jd b/docs/html/preview/features/notification-updates.jd
index 7ee0954..c405360 100644
--- a/docs/html/preview/features/notification-updates.jd
+++ b/docs/html/preview/features/notification-updates.jd
@@ -16,6 +16,7 @@
<li><a href="#direct">Direct Reply</a></li>
<li><a href="#bundle">Bundled Notifications</a></li>
<li><a href="#custom">Custom Views</a></li>
+ <li><a href="#style">Message Style</a></li>
</ol>
</div>
@@ -203,6 +204,16 @@
android.support.v4.app.RemoteInput}, you can update the reply history
using the {@code setRemoteInputHistory()} method.
</p>
+
+<p>
+ The notification must be either updated or cancelled after the app has
+ received remote input. When the user replies to a remote update
+ using Direct Reply,
+ do not cancel the notification. Instead, update the notification to display the user's reply. You can update the notification using a
+ <code>MessagingStyle</code>, or you can append the user's reply to the remote
+ input history.
+</p>
+
<h2 id="bundle">Bundled Notifications</h2>
<p>Android N provides developers with a new way to represent
@@ -233,6 +244,12 @@
group.
</p>
+<p class="note">
+ <strong>Note:</strong> If the same app sends four or more notifications
+ and does not specify a grouping, the
+ system automatically groups them together.
+</p>
+
<p>To learn how to add notifications to a group, see
<a href="{@docRoot}training/wearables/notifications/stacks.html#AddGroup">Add
Each Notification to a Group</a>.</p>
@@ -348,7 +365,7 @@
{@code DecoratedCustomViewStyle()} method.</p>
<pre>
-Notification noti = new Notification.Builder()
+Notification notification = new Notification.Builder()
.setSmallIcon(R.drawable.ic_stat_player)
.setLargeIcon(albumArtBitmap))
.setCustomContentView(contentView);
@@ -356,3 +373,26 @@
.build();
</pre>
+
+<h2 id="style">Messaging Style</h2>
+<p>
+ Android N introduces a new API for customizing the style of a notification.
+ Using the <code>MessageStyle</code> class, you can change several of the
+ labels displayed on the notification, including the conversation title,
+ additional messages, and the content view for the notification.
+</p>
+
+<p>
+ The following code snippet demonstrates how to customize a notification's
+ style using the <code>MessageStyle</code> class.
+</p>
+
+<pre>
+ Notification notification = new Notification.Builder()
+ .setStyle(new Notification.MessagingStyle("Me")
+ .setConversationTitle("Team lunch")
+ .addMessage("Hi", timestamp1, null) // Pass in null for user.
+ .addMessage("What's up?", timestamp2, "Coworker")
+ .addMessage("Not much", timestamp3, null)
+ .addMessage("How about lunch?", timestamp4, "Coworker"));
+</pre>
\ No newline at end of file
diff --git a/docs/html/preview/features/scoped-folder-access.jd b/docs/html/preview/features/scoped-folder-access.jd
index 814692d..06dd665 100644
--- a/docs/html/preview/features/scoped-folder-access.jd
+++ b/docs/html/preview/features/scoped-folder-access.jd
@@ -44,6 +44,12 @@
all available volumes, including removable media volumes, use
<code>StorageManager.getVolumesList()</code>.</p>
+<p>If you have information about a specific file, use
+<code>StorageManager.getStorageVolume(File)</code> to get the
+<code>StorageVolume</code> that contains the file. Call
+<code>createAccessIntent()</code> on this <code>StorageVolume</code> to access
+the external storage directory for the file.</p>
+
<p>
On secondary volumes, such as external SD cards, pass in null when calling
<code>StorageVolume.createAccessIntent()</code> to request access to the entire
diff --git a/docs/html/preview/features/tv-recording-api.jd b/docs/html/preview/features/tv-recording-api.jd
index b62dd46..65ab340 100644
--- a/docs/html/preview/features/tv-recording-api.jd
+++ b/docs/html/preview/features/tv-recording-api.jd
@@ -37,7 +37,23 @@
<h2 id="supporting">Indicating Support for Recording</h2>
-<p>To tell the system that your TV input service supports recording, follow
+<p>To tell the system that your TV input service supports recording, set
+the <code>android:canRecord</code> attribute in your service metadata XML file
+to <code>true</code>:
+</p>
+
+<pre>
+<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
+ <b>android:canRecord="true"</b>
+ android:setupActivity="com.example.sampletvinput.SampleTvInputSetupActivity" />
+</pre>
+
+<p>For more information on the service metadata file, see
+<a href="{@docRoot}training/tv/tif/tvinput.html#manifest">Declare Your TV Input
+Service in the Manifest</a>.
+</p>
+
+<p>Alternatively, you can indicate recording support in your code using
these steps:</p>
<ol>
@@ -48,7 +64,7 @@
<code>setCanRecord(true)</code> before calling <code>build()</code> to
indicate your service supports recording.</li>
<li>Register your <code>TvInputInfo</code> object with the system by calling
-<code>TvInputService.updateTvInputInfo()</code>.</li>
+<code>TvInputManager.updateTvInputInfo()</code>.</li>
</ol>
<h2 id="recording">Recording a Session</h2>
diff --git a/docs/html/preview/preview_toc.cs b/docs/html/preview/preview_toc.cs
index ad8d45f..34d00a4 100644
--- a/docs/html/preview/preview_toc.cs
+++ b/docs/html/preview/preview_toc.cs
@@ -31,7 +31,7 @@
vi-lang="Kiểm thử trên Thiết bị"
zh-cn-lang="设置预览版 SDK"
zh-tw-lang="設定預覽版 SDK">
- Set Up to Develop</a></div>
+ Set Up the Preview</a></div>
</li>
<li class="nav-section">
diff --git a/docs/html/preview/support.jd b/docs/html/preview/support.jd
index ea870a8..55f95d6 100644
--- a/docs/html/preview/support.jd
+++ b/docs/html/preview/support.jd
@@ -8,7 +8,8 @@
<p>
Two primary support channels are available to you when developing and testing
with the Android N Developer Preview: Please file bugs at <a href=
- "https://developer.android.com/preview/bug">https://developer.android.com/preview/bug</a> for
+ "https://developer.android.com/preview/bug"
+ >https://developer.android.com/preview/bug</a> for
device-specific, system, and Google App bugs. For issues in other apps,
please contact the developer directly.
</p>
@@ -33,9 +34,25 @@
<h3 id="new">New in DP3</h3>
-<ul>
- <li>TODO</li>
-</ul>
+
+<h4 id="api-changes">API changes</h4>
+
+<dl>
+ <dt><a href="{@docRoot}preview/api-overview.html#number-blocking"
+ >Number-blocking</a></dt>
+ <dd>If an unauthorized user attempts to block or unblock a number, the operation
+ now fails with {@link java.lang.SecurityException}. (Previously, the
+ operation threw {@link java.lang.UnsupportedOperationException}.)</dd>
+
+ <dt><a href="{@docRoot}preview/api-overview.html#tile_api">Quick
+ Settings Tile API</a></dt>
+ <dd>Tile mode is now controlled by the activity's metadata. (Previously, tile
+ mode was controlled by the return value of
+ <code>TileService.onTileAdded()</code>.) For more information, see
+ <code>TileService.META_DATA_ACTIVE_TILE</code> in the downloadable
+ <a href="{@docRoot}preview/setup-sdk.html#docs-dl">API Reference</a>.
+ </dd>
+</dl>
<h4 id="dp2-fixes">Fixes for issues reported by developers</h4>
diff --git a/docs/html/reference/android/support/wearable/navtree_data.js b/docs/html/reference/android/support/wearable/navtree_data.js
index 11e04b1..e51f578 100644
--- a/docs/html/reference/android/support/wearable/navtree_data.js
+++ b/docs/html/reference/android/support/wearable/navtree_data.js
@@ -1,5 +1,7 @@
var NAVTREE_DATA =
-[ [ "android.support.wearable.activity", "reference/android/support/wearable/activity/package-summary.html", [ [ "Classes", null, [ [ "ConfirmationActivity", "reference/android/support/wearable/activity/ConfirmationActivity.html", null, null ], [ "WearableActivity", "reference/android/support/wearable/activity/WearableActivity.html", null, null ] ]
+[ [ "android.support.wearable", "reference/android/support/wearable/package-summary.html", [ [ "Classes", null, [ [ "R", "reference/android/support/wearable/R.html", null, null ], [ "R.anim", "reference/android/support/wearable/R.anim.html", null, null ], [ "R.animator", "reference/android/support/wearable/R.animator.html", null, null ], [ "R.array", "reference/android/support/wearable/R.array.html", null, null ], [ "R.attr", "reference/android/support/wearable/R.attr.html", null, null ], [ "R.bool", "reference/android/support/wearable/R.bool.html", null, null ], [ "R.color", "reference/android/support/wearable/R.color.html", null, null ], [ "R.dimen", "reference/android/support/wearable/R.dimen.html", null, null ], [ "R.drawable", "reference/android/support/wearable/R.drawable.html", null, null ], [ "R.id", "reference/android/support/wearable/R.id.html", null, null ], [ "R.integer", "reference/android/support/wearable/R.integer.html", null, null ], [ "R.interpolator", "reference/android/support/wearable/R.interpolator.html", null, null ], [ "R.layout", "reference/android/support/wearable/R.layout.html", null, null ], [ "R.string", "reference/android/support/wearable/R.string.html", null, null ], [ "R.style", "reference/android/support/wearable/R.style.html", null, null ], [ "R.styleable", "reference/android/support/wearable/R.styleable.html", null, null ] ]
+, null ] ]
+, null ], [ "android.support.wearable.activity", "reference/android/support/wearable/activity/package-summary.html", [ [ "Classes", null, [ [ "ConfirmationActivity", "reference/android/support/wearable/activity/ConfirmationActivity.html", null, null ], [ "WearableActivity", "reference/android/support/wearable/activity/WearableActivity.html", null, null ] ]
, null ] ]
, null ], [ "android.support.wearable.companion", "reference/android/support/wearable/companion/package-summary.html", [ [ "Classes", null, [ [ "WatchFaceCompanion", "reference/android/support/wearable/companion/WatchFaceCompanion.html", null, null ] ]
, null ] ]
diff --git a/docs/html/sdk/sdk_vars.cs b/docs/html/sdk/sdk_vars.cs
new file mode 100644
index 0000000..6e58ddd
--- /dev/null
+++ b/docs/html/sdk/sdk_vars.cs
@@ -0,0 +1,64 @@
+<?cs
+set:studio.version='2.1.0.9' ?><?cs
+set:studio.release.date='April 26, 2016' ?><?cs
+
+
+set:studio.linux_bundle_download='android-studio-ide-143.2790544-linux.zip' ?><?cs
+set:studio.linux_bundle_bytes='298122012' ?><?cs
+set:studio.linux_bundle_checksum='45dad9b76ad0506c354483aaa67ea0e2468d03a5' ?><?cs
+
+set:studio.mac_bundle_download='android-studio-ide-143.2790544-mac.dmg' ?><?cs
+set:studio.mac_bundle_bytes='298589307' ?><?cs
+set:studio.mac_bundle_checksum='d667d93ae2e4e0f3fc1b95743329a46222dbf11d' ?><?cs
+
+set:studio.win_bundle_download='android-studio-ide-143.2790544-windows.zip' ?><?cs
+set:studio.win_bundle_bytes='300627540' ?><?cs
+set:studio.win_bundle_checksum='9689ba415e5f09e2dcf5263ea302e7b1d98a8fc6' ?><?cs
+
+set:studio.win_bundle_exe_download='android-studio-bundle-143.2790544-windows.exe' ?><?cs
+set:studio.win_bundle_exe_bytes='1238568304' ?><?cs
+set:studio.win_bundle_exe_checksum='c6abe7980dbb7d1d9887f7341a2942c9e506f891' ?><?cs
+
+set:studio.win_notools_exe_download='android-studio-ide-143.2790544-windows.exe' ?><?cs
+set:studio.win_notools_exe_bytes='283804056' ?><?cs
+set:studio.win_notools_exe_checksum='a2065ba737ddcfb96f4921fee6a038278f46d2a7' ?><?cs
+
+
+
+set:sdk.linux_download='android-sdk_r24.4.1-linux.tgz' ?><?cs
+set:sdk.linux_bytes='326412652' ?><?cs
+set:sdk.linux_checksum='725bb360f0f7d04eaccff5a2d57abdd49061326d' ?><?cs
+
+set:sdk.mac_download='android-sdk_r24.4.1-macosx.zip' ?><?cs
+set:sdk.mac_bytes='102781947' ?><?cs
+set:sdk.mac_checksum='85a9cccb0b1f9e6f1f616335c5f07107553840cd' ?><?cs
+
+set:sdk.win_download='android-sdk_r24.4.1-windows.zip' ?><?cs
+set:sdk.win_bytes='199701062' ?><?cs
+set:sdk.win_checksum='66b6a6433053c152b22bf8cab19c0f3fef4eba49' ?><?cs
+set:sdk.win_installer='installer_r24.4.1-windows.exe' ?><?cs
+set:sdk.win_installer_bytes='151659917' ?><?cs
+set:sdk.win_installer_checksum='f9b59d72413649d31e633207e31f456443e7ea0b' ?><?cs
+
+
+
+set:ndk.mac64_download='android-ndk-r11c-darwin-x86_64.zip' ?><?cs
+set:ndk.mac64_bytes='772428792' ?><?cs
+set:ndk.mac64_checksum='4ce8e7ed8dfe08c5fe58aedf7f46be2a97564696' ?><?cs
+
+set:ndk.linux64_download='android-ndk-r11c-linux-x86_64.zip' ?><?cs
+set:ndk.linux64_bytes='794135138' ?><?cs
+set:ndk.linux64_checksum='de5ce9bddeee16fb6af2b9117e9566352aa7e279' ?><?cs
+
+set:ndk.win64_download='android-ndk-r11c-windows-x86_64.zip' ?><?cs
+set:ndk.win64_bytes='771407642' ?><?cs
+set:ndk.win64_checksum='3d89deb97b3191c7e5555f1313ad35059479f071' ?><?cs
+set:ndk.win32_download='android-ndk-r11c-windows-x86.zip' ?><?cs
+set:ndk.win32_bytes='728899082' ?><?cs
+set:ndk.win32_checksum='ff939bde6cd374eecbd2c3b2ad218697f9a5038c'
+?>
+<?cs
+def:size_in_mb(bytes)
+ ?><?cs set:mb = bytes / 1024 / 1024
+ ?><?cs var:mb ?><?cs
+/def ?>
diff --git a/docs/html/topic/libraries/support-library/index.jd b/docs/html/topic/libraries/support-library/index.jd
index 61f0bc2..70c9cff 100644
--- a/docs/html/topic/libraries/support-library/index.jd
+++ b/docs/html/topic/libraries/support-library/index.jd
@@ -186,6 +186,135 @@
<p>This section provides details about the Support Library package releases.</p>
<div class="toggle-content opened">
+ <p id="rev23-4-0">
+ <a href="#" onclick="return toggleContent(this)"><img src=
+ "{@docRoot}assets/images/styles/disclosure_up.png" class=
+ "toggle-content-img" alt="">Android Support Library, revision 23.4.0</a>
+ <em>(May 2016)</em>
+ </p>
+
+ <div class="toggle-content-toggleme">
+ <dl>
+ <dt>
+ Changes for <a href=
+ "{@docRoot}tools/support-library/features.html#v4">v4 Support
+ Library</a>:
+ </dt>
+
+ <dd>
+ <ul>
+ <li>Fixed issue where fragments were added in the wrong order.
+ (<a class="external-link" href=
+ "https://code.google.com/p/android/issues/detail?id=206901">Issue
+ 206901</a>)
+ </li>
+
+ <li>Fixed issue where app bar wasn't drawn after being scrolled
+ offscreen. (<a class="external-link" href=
+ "https://code.google.com/p/android/issues/detail?id=178037">Issue
+ 178037</a>)
+ </li>
+ </ul>
+ </dd>
+
+ <dt>
+ Changes for <a href=
+ "{@docRoot}tools/support-library/features.html#v7-appcompat">v7
+ appcompat library</a>:
+ </dt>
+
+ <dd>
+ <ul>
+ <li>Added <!-- TODO: Link to method -->
+ <code><a href=
+ "{@docRoot}reference/android/support/v7/app/AppCompatDelegate.html">
+ AppCompatDelegate</a>.setCompatVectorFromResourcesEnabled()</code>
+ method to re-enable usage of vector drawables in {@link
+ android.graphics.drawable.DrawableContainer} objects on devices
+ running Android 4.4 (API level 19) and lower. See <a href=
+ "https://medium.com/@chrisbanes/appcompat-v23-2-age-of-the-vectors-91cbafa87c88#.44uulkfal"
+ class="external-link">AppCompat v23.2 — Age of the vectors</a> for
+ more information.
+ </li>
+
+ <li>Fixed an issue in API 23 with <a href=
+ "{@docRoot}reference/android/support/v7/app/AppCompatDelegate.html#setDefaultNightMode(int)">
+ <code>AppCompatDelegate.setDefaultNightMode()</code></a> not
+ loading correct resources in API level 23. (<a class=
+ "external-link" href=
+ "https://code.google.com/p/android/issues/detail?id=206573">Issue
+ 206573</a>)
+ </li>
+
+ <li>Fixed issue that could cause {@link
+ java.lang.NullPointerException}. (<a class="external-link" href=
+ "https://code.google.com/p/android/issues/detail?id=207638">Issue
+ 207638</a>)
+ </li>
+ </ul>
+ </dd>
+
+ <dt>
+ Changes for <a href=
+ "{@docRoot}tools/support-library/features.html#design">Design Support
+ Library</a>:
+ </dt>
+
+ <dd>
+ <ul>
+ <li>Fixed an issue where {@link
+ android.support.design.widget.TextInputLayout} doesn't clear error
+ tint after {@link
+ android.support.design.widget.TextInputLayout#setErrorEnabled
+ setErrorEnabled(false)} on API level 21 - 22 (<a class=
+ "external-link" href=
+ "https://code.google.com/p/android/issues/detail?id=202829">Issue
+ 202829</a>)
+ </li>
+
+ <li>Fixed an issue where {@link
+ android.support.design.widget.FloatingActionButton} does not return
+ when animations are disabled. (<a class="external-link" href=
+ "https://code.google.com/p/android/issues/detail?id=206416">Issue
+ 206416</a>)
+ </li>
+
+ <li>Fixed issue in {@link android.support.design.widget.AppBarLayout}
+ snap functionality when used with <code>{@link
+ android.support.design.R.id#scroll}|{@link
+ android.support.design.R.id#enterAlways}|{@link
+ android.support.design.R.id#enterAlwaysCollapsed}|{@link
+ android.support.design.R.id#snap}</code> scroll flags.
+ (<a class="external-link" href=
+ "https://code.google.com/p/android/issues/detail?id=207398">Issue
+ 207398</a>)
+ </li>
+ </ul>
+ </dd>
+
+ <dt>
+ Changes for <!-- TODO: Add link -->Vector Drawable library:
+ </dt>
+
+ <dd>
+ <ul>
+ <li>Fixed a bug where <!-- TODO: Javadoc link -->
+ <code>VectorDrawableCompat</code> does not render correctly in
+ {@link android.widget.TextView} on API level 23. (<a class=
+ "external-link" href=
+ "https://code.google.com/p/android/issues/detail?id=206227">Issue
+ 206227</a>)
+ </li>
+ </ul>
+ </dd>
+ </dl>
+ </div>
+</div>
+
+<!-- end of collapsible section: 23.4.0 -->
+
+
+<div class="toggle-content closed">
<p id="rev23-3-0">
<a href="#" onclick="return toggleContent(this)"><img src=
"{@docRoot}assets/images/styles/disclosure_up.png" class="toggle-content-img"
diff --git a/docs/html/wear/design/index.jd b/docs/html/wear/design/index.jd
deleted file mode 100644
index e01d17f..0000000
--- a/docs/html/wear/design/index.jd
+++ /dev/null
@@ -1,173 +0,0 @@
-page.title=Design Principles of Android Wear
-
-@jd:body
-
-<style>
-h3 {
- padding:30px 0 10px;
-}
-</style>
-<p>
-Android wearables provide just the right information at just the right time, allowing you to be connected to the virtual world and present in the real world.</p>
-
-<img src="{@docRoot}wear/images/05_images.png" height="200" width="169" style="float:right;clear:right;margin:0 0 60px 60px" />
-
-<p>Here you’ll find some guidelines for designing great user experiences on the Android Wear
-platform. Designing for Android Wear is substantially different than designing for phones or
-tablets, so we’ll start by describing how your content can work in tandem with the overall
-Android Wear vision. To better understand the user experience on Android Wear, also be sure
-to read the <a href="{@docRoot}wear/design/user-interface.html">UI Overview</a>.</p>
-
-
-<img src="{@docRoot}wear/images/02_notifications.png" height="200" width="169" style="float:right;clear:right;margin:0 0 20px 60px" />
-
-
-
-<p>Android Wear experiences are:</p>
-
-<ul>
- <li><strong>Contextually aware and smart.</strong> These devices bring a new level of awareness to computing. Rather than requiring attention and input from users, Android wearables are aware of their situation and state, and helpfully display the right information at the right time. <em>Timely, relevant, specific</em>.</li>
-
- <li><strong>Glanceable.</strong> Wearable devices are used all throughout the day, even when they sit in our peripheral vision. Effective apps provide the maximum payload of information with a minimum of fuss, optimized to provide tiny snippets of relevant information throughout the day. <em>Short, sharp, immediate.</em></li>
-
- <li><strong>Zero/low interaction.</strong> Staying true to the strengths afforded by a smaller form factor, Android Wear focuses on simple interactions, only requiring input by the user when absolutely necessary. Most inputs are based around touch swipes or voice, and inputs requiring fine-grained motor skills are avoided. <em>Gestural, simple, fast.</em></li>
-
- <li><strong>Helpful.</strong> Android Wear is like a great personal assistant: it knows you and your preferences, it only interrupts you when absolutely necessary, and it’s always on hand to provide a ready answer. <em>Efficient, respectful, responsive.</em></li>
-</ul>
-
-
-<p>
-By providing a smart connection to the rest of the world while respecting the user’s attention, Android Wear feels personal and global, simple and smart, unobtrusive and ever-ready. Notifications that respect these principles will feel most at home in the overall Android Wear experience.
-</p>
-
-
-
-<h2 id="Notifications" style="clear:both">Notification UI Patterns</h2>
-
-<p>Android notifications appear as cards in the main stream and form the core of the Android Wear experience. Many of the main <a href="http://developer.android.com/design/patterns/notifications.html">Android Design guidelines for notifications</a> apply in Android Wear. Be respectful of users' attention and aware of how unnecessary interruptions will reflect on your application’s reputation.</p>
-
-<p>Omit needless text from your notifications. Design for glanceability, not reading. Use words and phrases, not sentences. Show, don't tell: where possible use simple icons, glyphs, and visualizations to convey your message.</p>
-<img src="{@docRoot}wear/images/circle_message2.png" height="200" style="float:right;clear:right;margin:0 0 20px 60px" />
-
-<p>In some cases, particularly with messaging applications, cards will contain dynamic content which may not fit on a single screen. In these cases the content will be automatically truncated to fit on the card and the user may tap to expand, so the full message should be provided.</p>
-
-<p>Notification priority should reflect the urgency of your notification, with only time-sensitive notifications carrying a high priority. Active notifications – that is, those that cause the device to vibrate – should only be used in cases that need the user's urgent attention or action (e.g. a time-based reminder, a message from a friend). Non-urgent notifications (e.g. a transit times card, daily pedometer count, social network updates) should be silently added to the card stream.</p>
-
-
-
-
-<h3 id="NotifictionActions" style="clear:both">Actions</h3>
-
-<img src="{@docRoot}wear/images/circle_message2_reply.png" height="200" style="float:right;clear:right;margin:0 0 20px 40px" />
-
-<p>Actions appear to the right of your notification, allowing the user to act on your notification. Up to three actions are permitted. The most-used action should be placed first, so that it is a single swipe away from your content.</p>
-
-<p>Actions consist of an icon and a caption. Icons should be PNG files, white on transparent
-background, 32 × 32 dp (with 8 dp padding), as specified in the <a
-href="/design/style/iconography.html#action-bar">Iconography</a> design guide for action bar
-icons. Captions should be verb-driven and short, and will be automatically truncated at one line.
-</p>
-
-<p>Actions are optional. Many useful notifications will not need to include actions at all.</p>
-
-<p>For developer details about action buttons, see <a href="{@docRoot}wear/notifications/creating.html">Creating
-Notifications for Android Wear</a>.</p>
-
-
-
-
-
-
-<h3 id="Images" style="clear:both">Images</h3>
-
-<img src="{@docRoot}wear/images/circle_badge_B.png" height="200" style="float:right;clear:right;margin:0 0 20px 40px" />
-
-
-<p>Images appear behind cards in the stream, providing context and additional glanceability. Your image should support the core message of the notification; for example, a card about a sports team could include the team color and logo; a message from a contact should display that person's profile photo.</p>
-
-<p>Bear in mind that the card will partially cover the lower part of the image. Images should
-be sized as appropriate for the notification appearance on handsets, which is 64 x 64 dp. Image backgrounds move when horizontally swiped, so landscape-oriented images work better on notifications that include pages or actions.</p>
-
-<p>To add large images, use {@link android.support.v4.app.NotificationCompat.Builder#setLargeIcon
-setLargeIcon()} with any notification, as
-shown in <a href="{@docRoot}wear/notifications/creating.html">Creating
-Notifications for Android Wear</a>.</p>
-
-
-
-
-
-<h3 id="AppIcons" style="clear:both">Application Icons</h3>
-
-<img src="{@docRoot}wear/images/07_appicons.png" height="200" style="float:right;margin:0 0 20px 60px" />
-
-<p>Your application’s launcher icon will be automatically placed on the card, identifying your notification. Do not use the notification title or background image to identify or brand your application. Instead, allow your icon to identify itself and focus on delivering a clear, succinct message in the card and image. You can choose not to display this icon using
- <a href="/reference/android/preview/support/wearable/notifications/WearableNotifications.Builder.html#setHintHideIcon(boolean)"><code>setHintHideIcon()</code></a>.
-</p>
-
-
-
-
-
-
-
-<h3 id="NotificationPages" style="clear:both">Pages</h3>
-
-<p>Pages are additional cards that can appear to the right of your main card in the stream. If your core message is longer than a short snippet, do not sacrifice glanceability by packing a lot of information into your primary notification. Instead, use pages to provide additional content.</p>
-
-<img src="{@docRoot}wear/images/08_pages.png" height="200" style="float:left;margin:0 0 20px 0px" />
-<img src="{@docRoot}wear/images/09_pages.png" height="200" style="float:left;margin:0 0 20px 60px" />
-<img src="{@docRoot}wear/images/10_pages.png" height="200" style="float:left;margin:0 0 20px 60px" />
-
-<p style="clear:left">Pages appear immediately to the right of the main notification card. They are typically used to provide additional details or alternate views of the main card’s content. For example:</p>
-<ul>
- <li>A current weather card might provide an additional page showing a three-day forecast.</li>
- <li>A next train departure card might provide an additional page showing subsequent departures times.</li>
- <li>A daily step count card might provide an additional page showing the same measurement in calories and distance.</li>
-</ul>
-
-<p>There is no imposed limit on the number of pages you may add. However, notifications that provide actions should show no more than three pages to ensure that the actions remain easily accessible.</p>
-
-<p>Pages are optional. Many useful notifications will not need to include pages at all.</p>
-
-<p>For developer details about pages, see
-described in <a href="{@docRoot}wear/notifications/pages.html">Adding
-Pages to a Notification</a>.</p>
-
-
-
-
-
-<h3 id="NotificationStacks" style="clear:both">Notification Stacks</h3>
-
-<img src="{@docRoot}wear/images/11_bundles_B.png" height="200" style="float:right;margin:0 0 20px 60px" />
-<img src="{@docRoot}wear/images/11_bundles_A.png" height="200" style="float:right;margin:0 0 20px 60px" />
-
-<p>Stacks may be used to collect multiple notifications from the same application into a single stack of cards. Whereas pages are used to provide additional detail on a single notification, stacks are used to collect multiple sibling notifications together. A stack may be expanded by the user to access each individual card contained within.</p>
-
-<p>Stacks are a way of adding multiple useful notifications without overwhelming the user’s stream. If your application may produce multiple concurrent notifications, consider combining them into a stack.</p>
-
-<p>Each notification within a stack can contain separate pages and separate actions that are relevant to that specific notification. The user can access these actions after expanding that notification's card within the stack.</p>
-
-<p>For developer details about stacks, see
-described in <a href="{@docRoot}wear/notifications/stacks.html">Stacking
-Notifications</a>.</p>
-
-
-
-
-
-
-<h3 id="VoiceReplies" style="clear:both">Voice Replies</h3>
-
-
-<img src="{@docRoot}wear/images/circle_voice_B.png" height="200" style="float:right;margin:0 0 20px 40px" />
-<img src="{@docRoot}wear/images/circle_voice_A.png" height="200" style="float:right;margin:0 0 20px 40px" />
-
-<p>Voice replies are primarily used by messaging applications to provide a hands-free way of dictating a short message. You can also provide up to five suggested replies or “canned responses” that are useful in a wide range of cases. These canned responses can be tapped by the user, allowing for a fast method of sending simple replies in cases where speaking may not be desirable.</p>
-
-<p>You should attempt to cover a range of simple, neutral replies in your choices. Longer voice replies may be automatically truncated in the Voice reply UI.</p>
-
-<p>For developer details about enabling voice replies, see
-described in <a href="{@docRoot}wear/notifications/remote-input.html">Receiving Voice Input from
-a Notification</a>.</p>
diff --git a/docs/html/wear/design/user-interface.jd b/docs/html/wear/design/user-interface.jd
deleted file mode 100644
index 2a3e9ef..0000000
--- a/docs/html/wear/design/user-interface.jd
+++ /dev/null
@@ -1,58 +0,0 @@
-page.title=UI Overview
-page.customHeadTag=<link rel="stylesheet" type="text/css" href="/wear/css/wear.css">
-
-@jd:body
-
-<style>
-h3 {
- padding:30px 0 10px;
-}
-</style>
-
-<p>A new form factor deserves a new UI model. At a high level, the Android Wear UI consists of two
-main spaces centered around the core functions of <strong>Suggest</strong> and
-<strong>Demand</strong>. Your application will have an important role to play in both of these
-spaces.</p>
-
-
-
-<h3 id="Stream">Suggest: The Context Stream</h3>
-
-<div class="wear-inset-video-container" style="float:right;margin:0 -22px 60px 40px">
- <img class="wear-bezel-only" src="{@docRoot}wear/images/screens/bezel.png" alt="">
- <img class="gif" src="{@docRoot}wear/images/screens/stream.gif">
-</div>
-
-<p>The context stream is a vertical list of cards, each showing a useful or timely piece of
-information. Much like Google Now on Android phones and tablets, users swipe vertically to navigate
-from card to card for a brief and comprehensive update about what's important to them. Only one card
-is displayed on screen at a time, and background images are used to provide additional visual
-information. Your application can create cards and inject them into the stream when they are most
-likely to be useful.</p>
-
-<p>Cards in the stream are more than simple notifications. They can be swiped horizontally to
-reveal additional pages. Further horizontal swiping may reveal tappable buttons, allowing the user
-to take action on the notification. Cards can also be dismissed by swiping left to right, removing
-them from the stream until the next time they have useful information to display.
-In the emulator, hovering the mouse over the top of the screen illuminates a blue bar at
-the top of the device that takes you home when clicked.</p>
-
-
-
-<h3 id="CueCard">Demand: The Cue Card</h3>
-
-<div class="wear-inset-video-container" style="float:right;margin:0 -22px 60px 40px">
- <img class="wear-bezel-only" src="{@docRoot}wear/images/screens/bezel.png" alt="">
- <img class="gif" src="{@docRoot}wear/images/screens/cuecard.gif">
-</div>
-
-<p>For cases where the context stream can't anticipate what the user would like to do, the cue card
-allows users to speak to their device. The cue card is opened by saying, "Ok Google" or by tapping
-on the "g" icon on the home screen. Swiping up on the cue card shows a list of actions, which can
-also be tapped.</p>
-
-<p>The list of actions includes Android intents for voice actions. The upcoming Android Wear SDK
-will enable developers to match their applications to these intents so users can perform actions
-using these voice commands. Multiple applications may register for a single voice intent, and users
-will have the opportunity to choose which application they prefer to use.</p>
-
diff --git a/docs/html/wear/preview/_book.yaml b/docs/html/wear/preview/_book.yaml
index ddcfc11..285073c 100644
--- a/docs/html/wear/preview/_book.yaml
+++ b/docs/html/wear/preview/_book.yaml
@@ -14,6 +14,8 @@
path: /wear/preview/features/complications.html
- title: Wear Navigation and Actions
path: /wear/preview/features/ui-nav-actions.html
+ - title: Bridging for Notifications
+ path: /wear/preview/features/bridger.html
- title: Downloads
path: /wear/preview/downloads.html
diff --git a/docs/html/wear/preview/images/hero-1x.png b/docs/html/wear/preview/images/hero-1x.png
index cde76c3..5dcc847 100644
--- a/docs/html/wear/preview/images/hero-1x.png
+++ b/docs/html/wear/preview/images/hero-1x.png
Binary files differ
diff --git a/docs/html/wear/preview/images/hero-2x.png b/docs/html/wear/preview/images/hero-2x.png
index 4d542d8..19a60cd 100644
--- a/docs/html/wear/preview/images/hero-2x.png
+++ b/docs/html/wear/preview/images/hero-2x.png
Binary files differ
diff --git a/docs/html/wear/preview/index.jd b/docs/html/wear/preview/index.jd
index ff8d5bb..3f3a30a 100644
--- a/docs/html/wear/preview/index.jd
+++ b/docs/html/wear/preview/index.jd
@@ -17,7 +17,7 @@
})
</script>
-<section class="dac-expand dac-hero dac-light" style="background-color:#E1BEE7">
+<section class="dac-expand dac-hero dac-light" style="background-color:#DCEDC8">
<div class="wrap" style="max-width:1100px;margin-top:0">
<div class="cols dac-hero-content" style="padding-bottom:1em;">
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index ef66971..dd33e98 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -136,13 +136,13 @@
* <dd>Defines path data using exactly same format as "d" attribute
* in the SVG's path data. This is defined in the viewport space.</dd>
* <dt><code>android:fillColor</code></dt>
- * <dd>Specifies the color used to fill the path. May be a color, also may be a color state list or
- * a gradient color for SDK 24+. If this property is animated, any value set by the animation will
+ * <dd>Specifies the color used to fill the path. May be a color or, for SDK 24+, a color state list
+ * or a gradient color. If this property is animated, any value set by the animation will
* override the original value. No path fill is drawn if this property is not specified.</dd>
* <dt><code>android:strokeColor</code></dt>
- * <dd>Specifies the color used to draw the path outline. May be a color or (SDK 24+ only) a color
- * state list. If this property is animated, any value set by the animation will override the
- * original value. No path outline is drawn if this property is not specified.</dd>
+ * <dd>Specifies the color used to draw the path outline. May be a color or, for SDK 24+, a color
+ * state list or a gradient color. If this property is animated, any value set by the animation will
+ * override the original value. No path outline is drawn if this property is not specified.</dd>
* <dt><code>android:strokeWidth</code></dt>
* <dd>The width a path stroke.</dd>
* <dt><code>android:strokeAlpha</code></dt>
@@ -162,6 +162,9 @@
* <dd>Sets the lineJoin for a stroked path: miter,round,bevel.</dd>
* <dt><code>android:strokeMiterLimit</code></dt>
* <dd>Sets the Miter limit for a stroked path.</dd>
+ * <dt><code>android:fillType</code></dt>
+ * <dd>Sets the fillType for a path. It is the same as SVG's "fill-rule" properties.
+ * For more details, see https://www.w3.org/TR/SVG/painting.html#FillRuleProperty</dd>
* </dl></dd>
* </dl>
*
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 831473c..2ec7b75 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -194,8 +194,12 @@
renderer.renderGlop(nullptr, clip, glop);
}
-static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
+static void renderTextShadow(BakedOpRenderer& renderer,
const TextOp& op, const BakedOpState& textOpState) {
+ if (CC_LIKELY(!PaintUtils::hasTextShadow(op.paint))) return;
+
+ FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
+ fontRenderer.setFont(op.paint, SkMatrix::I());
renderer.caches().textureState().activateTexture(0);
PaintUtils::TextShadow textShadow;
@@ -258,15 +262,9 @@
Flush
};
-static void renderTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state,
+static void renderText(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state,
const ClipBase* renderClip, TextRenderType renderType) {
FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
-
- if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
- fontRenderer.setFont(op.paint, SkMatrix::I());
- renderTextShadow(renderer, fontRenderer, op, state);
- }
-
float x = op.x;
float y = op.y;
const Matrix4& transform = state.computedState.transform;
@@ -321,6 +319,12 @@
void BakedOpDispatcher::onMergedTextOps(BakedOpRenderer& renderer,
const MergedBakedOpList& opList) {
+ for (size_t i = 0; i < opList.count; i++) {
+ const BakedOpState& state = *(opList.states[i]);
+ const TextOp& op = *(static_cast<const TextOp*>(state.op));
+ renderTextShadow(renderer, op, state);
+ }
+
ClipRect renderTargetClip(opList.clip);
const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
for (size_t i = 0; i < opList.count; i++) {
@@ -328,7 +332,7 @@
const TextOp& op = *(static_cast<const TextOp*>(state.op));
TextRenderType renderType = (i + 1 == opList.count)
? TextRenderType::Flush : TextRenderType::Defer;
- renderTextOp(renderer, op, state, clip, renderType);
+ renderText(renderer, op, state, clip, renderType);
}
}
@@ -740,7 +744,8 @@
}
void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) {
- renderTextOp(renderer, op, state, state.computedState.getClipIfNeeded(), TextRenderType::Flush);
+ renderTextShadow(renderer, op, state);
+ renderText(renderer, op, state, state.computedState.getClipIfNeeded(), TextRenderType::Flush);
}
void BakedOpDispatcher::onTextOnPathOp(BakedOpRenderer& renderer, const TextOnPathOp& op, const BakedOpState& state) {
diff --git a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
index de57cd1e..5613f9f 100644
--- a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp
@@ -19,6 +19,9 @@
#include <RecordedOp.h>
#include <BakedOpDispatcher.h>
#include <BakedOpRenderer.h>
+#include <FrameBuilder.h>
+#include <SkBlurDrawLooper.h>
+#include <hwui/Paint.h>
#include <tests/common/TestUtils.h>
#include <SkDashPathEffect.h>
@@ -26,6 +29,7 @@
using namespace android::uirenderer;
static BakedOpRenderer::LightInfo sLightInfo;
+const FrameBuilder::LightGeometry sLightGeometry = { {100, 100, 100}, 50};
static Rect sBaseClip(100, 100);
class ValidatingBakedOpRenderer : public BakedOpRenderer {
@@ -45,7 +49,7 @@
std::function<void(const Glop& glop)> mValidator;
};
-typedef void (*BakedOpReceiver)(BakedOpRenderer&, const BakedOpState&);
+typedef void (*TestBakedOpReceiver)(BakedOpRenderer&, const BakedOpState&);
static void testUnmergedGlopDispatch(renderthread::RenderThread& renderThread, RecordedOp* op,
std::function<void(const Glop& glop)> glopVerifier) {
@@ -67,7 +71,7 @@
[](BakedOpRenderer& renderer, const BakedOpState& state) { \
BakedOpDispatcher::on##Type(renderer, static_cast<const Type&>(*(state.op)), state); \
},
- static BakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X);
+ static TestBakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X);
#undef X
unmergedReceivers[op->opId](renderer, *state);
ASSERT_EQ(1, glopCount) << "Exactly one Glop expected";
@@ -154,4 +158,40 @@
LinesOp linesOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4);
EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &linesOp))
<< "Expect an offset for non-AA lines.";
+}
+
+RENDERTHREAD_TEST(BakedOpDispatcher, renderTextWithShadow) {
+ auto node = TestUtils::createNode(0, 0, 100, 100,
+ [](RenderProperties& props, TestCanvas& canvas) {
+
+ android::Paint shadowPaint;
+ shadowPaint.setColor(SK_ColorRED);
+
+ SkScalar sigma = Blur::convertRadiusToSigma(5);
+ shadowPaint.setLooper(SkBlurDrawLooper::Create(SK_ColorWHITE, sigma, 3, 3))->unref();
+
+ TestUtils::drawUtf8ToCanvas(&canvas, "A", shadowPaint, 25, 25);
+ TestUtils::drawUtf8ToCanvas(&canvas, "B", shadowPaint, 50, 50);
+ });
+
+ int glopCount = 0;
+ auto glopReceiver = [&glopCount] (const Glop& glop) {
+ if (glopCount < 2) {
+ // two white shadows
+ EXPECT_EQ(FloatColor({1, 1, 1, 1}), glop.fill.color);
+ } else {
+ // two text draws merged into one, drawn after both shadows
+ EXPECT_EQ(FloatColor({1, 0, 0, 1}), glop.fill.color);
+ }
+ glopCount++;
+ };
+
+ ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver);
+
+ FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+ sLightGeometry, Caches::getInstance());
+ frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node));
+
+ frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
+ ASSERT_EQ(3, glopCount) << "Exactly three glops expected";
}
\ No newline at end of file
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 210e62b..ce4a453 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -401,20 +401,8 @@
* with a different track ID.
*
* @param tracks A list which includes track information.
- * @throws IllegalArgumentException if {@code tracks} contains redundant tracks.
*/
public void notifyTracksChanged(final List<TvTrackInfo> tracks) {
- Set<String> trackIdSet = new HashSet<>();
- for (TvTrackInfo track : tracks) {
- String trackId = track.getId();
- if (trackIdSet.contains(trackId)) {
- throw new IllegalArgumentException("redundant track ID: " + trackId);
- }
- trackIdSet.add(trackId);
- }
- trackIdSet.clear();
-
- // TODO: Validate the track list.
final List<TvTrackInfo> tracksCopy = new ArrayList<>(tracks);
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 2836adb..1342945 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -150,6 +150,9 @@
<!-- Description of printer info icon. [CHAR LIMIT=50] -->
<string name="printer_info_desc">More information about this printer</string>
+ <!-- Notification that we could not create a file name for the printed PDF. [CHAR LIMIT=50] -->
+ <string name="could_not_create_file">Could not create file</string>
+
<!-- Notification that print services as disabled. [CHAR LIMIT=50] -->
<string name="print_services_disabled_toast">Some print services are disabled</string>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index cc0b4ca..8b1fd08 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -84,6 +84,7 @@
import android.widget.Spinner;
import android.widget.TextView;
+import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
import com.android.printspooler.R;
import com.android.printspooler.model.MutexFileProvider;
@@ -658,7 +659,15 @@
intent.setType("application/pdf");
intent.putExtra(Intent.EXTRA_TITLE, info.getName());
intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName);
- startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
+
+ try {
+ startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Could not create file", e);
+ Toast.makeText(this, getString(R.string.could_not_create_file),
+ Toast.LENGTH_SHORT).show();
+ onStartCreateDocumentActivityResult(RESULT_CANCELED, null);
+ }
}
private void onStartCreateDocumentActivityResult(int resultCode, Intent data) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 5ff9c0c..f49594c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -46,6 +46,7 @@
import android.os.DropBoxManager;
import android.os.Environment;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -199,6 +200,12 @@
@GuardedBy("mLock")
private SettingsRegistry mSettingsRegistry;
+ @GuardedBy("mLock")
+ private HandlerThread mHandlerThread;
+
+ @GuardedBy("mLock")
+ private Handler mBackgroundHandler;
+
// We have to call in the user manager with no lock held,
private volatile UserManager mUserManager;
@@ -244,6 +251,10 @@
synchronized (mLock) {
mUserManager = UserManager.get(getContext());
mPackageManager = AppGlobals.getPackageManager();
+ mHandlerThread = new HandlerThread(LOG_TAG,
+ Process.THREAD_PRIORITY_BACKGROUND);
+ mHandlerThread.start();
+ mBackgroundHandler = new Handler(mHandlerThread.getLooper());
mSettingsRegistry = new SettingsRegistry();
}
registerBroadcastReceivers();
@@ -1669,7 +1680,7 @@
if (mSettingsStates.get(key) == null) {
final int maxBytesPerPackage = getMaxBytesPerPackageForType(getTypeFromKey(key));
SettingsState settingsState = new SettingsState(mLock, getSettingsFile(key), key,
- maxBytesPerPackage);
+ maxBytesPerPackage, mBackgroundHandler);
mSettingsStates.put(key, settingsState);
}
}
@@ -1948,6 +1959,8 @@
final int userId = getUserIdFromKey(key);
Uri uri = getNotificationUriFor(key, name);
+ mGenerationRegistry.incrementGeneration(key);
+
mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
userId, 0, uri).sendToTarget();
@@ -1959,8 +1972,6 @@
sSystemCloneToManagedSettings);
}
- mGenerationRegistry.incrementGeneration(key);
-
mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 2de0618c..1e02747 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -95,7 +95,7 @@
private final Object mLock;
- private final Handler mHandler = new MyHandler();
+ private final Handler mHandler;
@GuardedBy("mLock")
private final ArrayMap<String, Setting> mSettings = new ArrayMap<>();
@@ -134,13 +134,15 @@
@GuardedBy("mLock")
private long mNextId;
- public SettingsState(Object lock, File file, int key, int maxBytesPerAppPackage) {
+ public SettingsState(Object lock, File file, int key, int maxBytesPerAppPackage,
+ Handler handler) {
// It is important that we use the same lock as the settings provider
// to ensure multiple mutations on this state are atomicaly persisted
// as the async persistence should be blocked while we make changes.
mLock = lock;
mStatePersistFile = file;
mKey = key;
+ mHandler = handler;
if (maxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_LIMITED) {
mMaxBytesPerAppPackage = maxBytesPerAppPackage;
mPackageToMemoryUsage = new ArrayMap<>();
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 3f9ffa1..53c2958 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -15,6 +15,7 @@
*/
package com.android.providers.settings;
+import android.os.Handler;
import android.test.AndroidTestCase;
import android.util.Xml;
@@ -126,7 +127,7 @@
final Object lock = new Object();
final SettingsState ssWriter = new SettingsState(lock, file, 1,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED);
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, new Handler());
ssWriter.setVersionLocked(SettingsState.SETTINGS_VERSOIN_NEW_ENCODING);
ssWriter.insertSettingLocked("k1", "\u0000", "package");
@@ -138,7 +139,7 @@
}
final SettingsState ssReader = new SettingsState(lock, file, 1,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED);
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, new Handler());
synchronized (lock) {
assertEquals("\u0000", ssReader.getSettingLocked("k1").getValue());
assertEquals("abc", ssReader.getSettingLocked("k2").getValue());
@@ -165,7 +166,7 @@
os.close();
final SettingsState ss = new SettingsState(lock, file, 1,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED);
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, new Handler());
synchronized (lock) {
SettingsState.Setting s;
s = ss.getSettingLocked("k0");
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index a5f3e77..0f356e0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -107,6 +107,7 @@
private boolean mFinishedOnStartup;
private boolean mIgnoreAltTabRelease;
private boolean mIsVisible;
+ private boolean mReceivedNewIntent;
// Top level views
private RecentsView mRecentsView;
@@ -120,6 +121,9 @@
private int mFocusTimerDuration;
private DozeTrigger mIterateTrigger;
private final UserInteractionEvent mUserInteractionEvent = new UserInteractionEvent();
+ private final Runnable mSendEnterWindowAnimationCompleteRunnable = () -> {
+ EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
+ };
/**
* A common Runnable to finish Recents by launching Home with an animation depending on the
@@ -342,6 +346,7 @@
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
+ mReceivedNewIntent = true;
// Reload the stack view
reloadStackView();
@@ -364,7 +369,7 @@
RecentsActivityLaunchState launchState = config.getLaunchState();
if (!loadPlan.hasTasks()) {
loader.preloadTasks(loadPlan, launchState.launchedToTaskId,
- launchState.launchedFromHome);
+ !launchState.launchedFromHome);
}
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
@@ -419,7 +424,16 @@
@Override
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
- EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
+
+ // Workaround for b/28705801, on first docking, we may receive the enter animation callback
+ // before the first layout, so in such cases, send the event on the next frame after all
+ // the views are laid out and attached (and registered to the EventBus).
+ mHandler.removeCallbacks(mSendEnterWindowAnimationCompleteRunnable);
+ if (!mReceivedNewIntent) {
+ mHandler.post(mSendEnterWindowAnimationCompleteRunnable);
+ } else {
+ mSendEnterWindowAnimationCompleteRunnable.run();
+ }
}
@Override
@@ -453,7 +467,8 @@
RecentsActivityLaunchState launchState = config.getLaunchState();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
- loader.preloadTasks(loadPlan, -1 /* runningTaskId */, false /* isHomeStackVisible */);
+ loader.preloadTasks(loadPlan, -1 /* runningTaskId */,
+ false /* includeFrontMostExcludedTask */);
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
@@ -477,6 +492,7 @@
// Notify that recents is now hidden
mIsVisible = false;
+ mReceivedNewIntent = false;
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
MetricsLogger.hidden(this, MetricsEvent.OVERVIEW_ACTIVITY);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 297dec9..611f9f2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -112,7 +112,7 @@
// Load the next task only if we aren't svelte
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
+ loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
// This callback is made when a new activity is launched and the old one is paused
// so ignore the current activity and try and preload the thumbnail for the
@@ -189,7 +189,7 @@
// We can use a new plan since the caches will be the same.
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
+ loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
launchOpts.numVisibleTasks = loader.getIconCacheSize();
launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
@@ -366,8 +366,8 @@
ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
RecentsTaskLoader loader = Recents.getTaskLoader();
sInstanceLoadPlan = loader.createLoadPlan(mContext);
- sInstanceLoadPlan.preloadRawTasks(isHomeStackVisible.value);
- loader.preloadTasks(sInstanceLoadPlan, runningTask.id, isHomeStackVisible.value);
+ sInstanceLoadPlan.preloadRawTasks(!isHomeStackVisible.value);
+ loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value);
TaskStack stack = sInstanceLoadPlan.getTaskStack();
if (stack.getTaskCount() > 0) {
// Only preload the icon (but not the thumbnail since it may not have been taken for
@@ -401,7 +401,7 @@
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
+ loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
@@ -453,7 +453,7 @@
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
+ loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
@@ -818,7 +818,7 @@
sInstanceLoadPlan = loader.createLoadPlan(mContext);
}
if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
- loader.preloadTasks(sInstanceLoadPlan, runningTask.id, isHomeStackVisible);
+ loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible);
}
TaskStack stack = sInstanceLoadPlan.getTaskStack();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 08b52d9..0b74e31 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -253,11 +253,12 @@
/**
* Returns a list of the recents tasks.
*
- * @param isHomeStackVisible whether or not the home stack is currently visible. If it is
- * visible, then we ignore all excluded tasks (even the first one).
+ * @param includeFrontMostExcludedTask if set, will ensure that the front most excluded task
+ * will be visible, otherwise no excluded tasks will be
+ * visible.
*/
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId,
- boolean isHomeStackVisible, ArraySet<Integer> quietProfileIds) {
+ boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) {
if (mAm == null) return null;
// If we are mocking, then create some recent tasks
@@ -295,13 +296,16 @@
// Remove home/recents/excluded tasks
int minNumTasksToQuery = 10;
int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks);
- List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery,
- ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
+ int flags = ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK |
ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS |
ActivityManager.RECENT_IGNORE_UNAVAILABLE |
- ActivityManager.RECENT_INCLUDE_PROFILES |
- ActivityManager.RECENT_WITH_EXCLUDED, userId);
+ ActivityManager.RECENT_INCLUDE_PROFILES;
+ if (includeFrontMostExcludedTask) {
+ flags |= ActivityManager.RECENT_WITH_EXCLUDED;
+ }
+ List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery,
+ flags, userId);
// Break early if we can't get a valid set of tasks
if (tasks == null) {
@@ -316,18 +320,22 @@
// NOTE: The order of these checks happens in the expected order of the traversal of the
// tasks
- // Check the first non-recents task, include this task even if it is marked as excluded
- // from recents if we are currently in the app. In other words, only remove excluded
- // tasks if it is not the first active task, and not in the blacklist.
- boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
- == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
- boolean isBlackListed = sRecentsBlacklist.contains(t.realActivity.getClassName());
- // Filter out recent tasks from managed profiles which are in quiet mode.
- isExcluded |= quietProfileIds.contains(t.userId);
- if (isBlackListed || (isExcluded && (isHomeStackVisible || !isFirstValidTask))) {
+ // Remove the task if it is blacklisted
+ if (sRecentsBlacklist.contains(t.realActivity.getClassName())) {
iter.remove();
continue;
}
+
+ // Remove the task if it is marked as excluded, unless it is the first most task and we
+ // are requested to include it
+ boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+ == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+ isExcluded |= quietProfileIds.contains(t.userId);
+ if (isExcluded && (!isFirstValidTask || !includeFrontMostExcludedTask)) {
+ iter.remove();
+ continue;
+ }
+
isFirstValidTask = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 251ad71..1278b73 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -100,12 +100,12 @@
* An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
* to most-recent order.
*/
- public synchronized void preloadRawTasks(boolean isHomeStackVisible) {
+ public synchronized void preloadRawTasks(boolean includeFrontMostExcludedTask) {
int currentUserId = UserHandle.USER_CURRENT;
updateCurrentQuietProfilesCache(currentUserId);
SystemServicesProxy ssp = Recents.getSystemServices();
mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
- currentUserId, isHomeStackVisible, mCurrentQuietProfiles);
+ currentUserId, includeFrontMostExcludedTask, mCurrentQuietProfiles);
// Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
Collections.reverse(mRawTasks);
@@ -121,11 +121,11 @@
* - least-recent to most-recent freeform tasks
*/
public synchronized void preloadPlan(RecentsTaskLoader loader, int runningTaskId,
- boolean isHomeStackVisible) {
+ boolean includeFrontMostExcludedTask) {
Resources res = mContext.getResources();
ArrayList<Task> allTasks = new ArrayList<>();
if (mRawTasks == null) {
- preloadRawTasks(isHomeStackVisible);
+ preloadRawTasks(includeFrontMostExcludedTask);
}
SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 9460b64..ca59831 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -334,8 +334,8 @@
/** Preloads recents tasks using the specified plan to store the output. */
public void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
- boolean isHomeStackVisible) {
- plan.preloadPlan(this, runningTaskId, isHomeStackVisible);
+ boolean includeFrontMostExcludedTask) {
+ plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask);
}
/** Begins loading the heavy task data according to the specified options. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
index acebf42..3a17d22 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
@@ -180,7 +180,7 @@
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
if (!plan.hasTasks()) {
- loader.preloadTasks(plan, -1, launchState.launchedFromHome);
+ loader.preloadTasks(plan, -1, !launchState.launchedFromHome);
}
mLaunchedFromHome = launchState.launchedFromHome;
TaskStack stack = plan.getTaskStack();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
index 6f7bd41..fca8d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
@@ -63,7 +63,7 @@
sInstanceLoadPlan = loader.createLoadPlan(mContext);
}
if (mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
- loader.preloadTasks(sInstanceLoadPlan, runningTask.id, isHomeStackVisible);
+ loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible);
}
TaskStack stack = sInstanceLoadPlan.getTaskStack();
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 3d0de1c..1a197b6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -1391,8 +1391,9 @@
updateLayoutAlgorithm(true /* boundScroll */);
// Animate all the tasks into place
- relayoutTaskViews(new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
- Interpolators.FAST_OUT_SLOW_IN));
+ relayoutTaskViews(mAwaitingFirstLayout
+ ? AnimationProps.IMMEDIATE
+ : new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN));
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 9ba1cc9..cff4702 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -535,7 +535,7 @@
anim.addUpdateListener(animation -> resizeStack((Integer) animation.getAnimatedValue(),
taskPositionSameAtEnd && animation.getAnimatedFraction() == 1f
? TASK_POSITION_SAME
- : snapTarget.position, snapTarget));
+ : snapTarget.taskPosition, snapTarget));
Runnable endAction = () -> {
commitSnapFlags(snapTarget);
mWindowManagerProxy.setResizing(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index dc5957d..593e170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -2246,9 +2246,12 @@
Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
0,
mCurrentUserId) != 0;
+ final boolean remoteInputDpm = (dpmFlags
+ & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0;
+
setShowLockscreenNotifications(show && allowedByDpm);
- setLockScreenAllowRemoteInput(remoteInput);
+ setLockScreenAllowRemoteInput(remoteInput && remoteInputDpm);
}
protected abstract void setAreThereNotifications();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index d2326d2..270b6ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -459,7 +459,8 @@
@Override
public String toString() {
- String result = " summary:\n " + summary.notification;
+ String result = " summary:\n "
+ + (summary != null ? summary.notification : "null");
result += "\n children size: " + children.size();
for (NotificationData.Entry child : children) {
result += "\n " + child.notification;
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 5256e4f6..9dbec6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -941,6 +941,7 @@
@Override
public void flingTopOverscroll(float velocity, boolean open) {
mLastOverscroll = 0f;
+ mQsExpansionFromOverscroll = false;
setQsExpansion(mQsExpansionHeight);
flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled,
new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index d6deba0..8bb1f24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -30,6 +30,9 @@
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto;
import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
@@ -340,6 +343,8 @@
@Override
public void onClick(View v) {
if (v == mSettingsButton) {
+ MetricsLogger.action(mContext,
+ MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH);
if (mSettingsButton.isTunerClick()) {
if (TunerService.isTunerEnabled(mContext)) {
TunerService.showResetRequest(mContext, new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index f801963..0a3197c 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -154,7 +154,7 @@
getContext().sendBroadcast(intent);
intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_CLOCK);
- intent.putExtra("hhmm", "0600");
+ intent.putExtra("hhmm", "0700");
getContext().sendBroadcast(intent);
intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_NETWORK);
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index b7677c6..46da957 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -2203,6 +2203,9 @@
// The dialog shown by 3P intent to change current webview implementation.
WEBVIEW_IMPLEMENTATION = 405;
+ // Settings launched from expanded quick settings.
+ ACTION_QS_EXPANDED_SETTINGS_LAUNCH = 406;
+
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7f977dd..7da969f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -902,8 +902,11 @@
private void unlockUser(int userId) {
synchronized (mLock) {
- UserState userState = getUserStateLocked(userId);
- onUserStateChangedLocked(userState);
+ int parentUserId = mSecurityPolicy.resolveProfileParentLocked(userId);
+ if (parentUserId == mCurrentUserId) {
+ UserState userState = getUserStateLocked(mCurrentUserId);
+ onUserStateChangedLocked(userState);
+ }
}
}
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
new file mode 100644
index 0000000..d48aeed
--- /dev/null
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -0,0 +1,125 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.util.EventLog;
+import android.util.Slog;
+import android.os.Binder;
+import android.os.Build;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructStat;
+
+import java.util.ArrayList;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * <p>PinnerService pins important files for key processes in memory.</p>
+ * <p>Files to pin are specified in the config_defaultPinnerServiceFiles
+ * overlay. </p>
+ */
+public final class PinnerService extends SystemService {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "PinnerService";
+
+ private final Context mContext;
+ private final ArrayList<String> mPinnedFiles = new ArrayList<String>();
+
+ private BinderService mBinderService;
+
+
+ public PinnerService(Context context) {
+ super(context);
+
+ mContext = context;
+
+ }
+
+ @Override
+ public void onStart() {
+ Slog.e(TAG, "Starting PinnerService");
+
+ mBinderService = new BinderService();
+ publishBinderService("pinner", mBinderService);
+
+ // Files to pin come from the overlay and can be specified per-device config
+ // Continue trying to pin remaining files even if there is a failure
+ String[] filesToPin = mContext.getResources().getStringArray(com.android.internal.R.array.config_defaultPinnerServiceFiles);
+ for (int i = 0; i < filesToPin.length; i++){
+ boolean success = pinFile(filesToPin[i], 0, 0);
+ if (success == true) {
+ mPinnedFiles.add(filesToPin[i]);
+ Slog.i(TAG, "Pinned file = " + filesToPin[i]);
+ } else {
+ Slog.e(TAG, "Failed to pin file = " + filesToPin[i]);
+ }
+ }
+ }
+
+ // mlock length bytes of fileToPin in memory, starting at offset
+ // length == 0 means pin from offset to end of file
+ private boolean pinFile(String fileToPin, long offset, long length) {
+ FileDescriptor fd = new FileDescriptor();
+ try {
+ fd = Os.open(fileToPin, OsConstants.O_RDONLY | OsConstants.O_CLOEXEC | OsConstants.O_NOFOLLOW, OsConstants.O_RDONLY);
+
+ StructStat sb = Os.fstat(fd);
+
+ if (offset + length > sb.st_size) {
+ Os.close(fd);
+ return false;
+ }
+
+ if (length == 0) {
+ length = sb.st_size - offset;
+ }
+
+ long address = Os.mmap(0, length, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, fd, offset);
+ Os.close(fd);
+
+ Os.mlock(address, length);
+
+ return true;
+ } catch (ErrnoException e) {
+ Slog.e(TAG, "Failed to pin file " + fileToPin + " with error " + e.getMessage());
+ if(fd.valid()) {
+ try { Os.close(fd); }
+ catch (ErrnoException eClose) {Slog.e(TAG, "Failed to close fd, error = " + eClose.getMessage());}
+ }
+ return false;
+ }
+ }
+
+
+ private final class BinderService extends Binder {
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+ pw.println("Pinned Files:");
+ for (int i = 0; i < mPinnedFiles.size(); i++) {
+ pw.println(mPinnedFiles.get(i));
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index be53cfc..be021ea 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -73,6 +73,7 @@
"/system/bin/surfaceflinger",
"media.codec", // system/bin/mediacodec
"media.extractor", // system/bin/mediaextractor
+ "com.android.bluetooth", // Bluetooth service
};
static Watchdog sWatchdog;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 243c887..62d114d 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1752,6 +1752,9 @@
}
} else if (r.visible) {
// If this activity is already visible, then there is nothing to do here.
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Skipping: already visible at " + r);
+
if (handleAlreadyVisible(r)) {
resumeNextActivity = false;
}
@@ -1933,8 +1936,6 @@
r.sleeping = false;
r.app.pendingUiClean = true;
r.app.thread.scheduleWindowVisibility(r.appToken, true);
- r.stopFreezingScreenLocked(false);
-
// The activity may be waiting for stop, but that is no longer
// appropriate for it.
mStackSupervisor.mStoppingActivities.remove(r);
@@ -1944,10 +1945,10 @@
// visible when it next restarts.
Slog.w(TAG, "Exception thrown making visibile: " + r.intent.getComponent(), e);
}
+ handleAlreadyVisible(r);
}
private boolean handleAlreadyVisible(ActivityRecord r) {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r);
r.stopFreezingScreenLocked(false);
try {
if (r.returningOptions != null) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index c15effb..64bd14c 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -677,16 +677,17 @@
// app in a locked managed profile from an unlocked parent allow it to resolve
// as user will be sent via confirm credentials to unlock the profile.
UserManager userManager = UserManager.get(mService.mContext);
- UserInfo parent = null;
+ boolean profileLockedAndParentUnlockingOrUnlocked = false;
long token = Binder.clearCallingIdentity();
try {
- parent = userManager.getProfileParent(userId);
+ UserInfo parent = userManager.getProfileParent(userId);
+ profileLockedAndParentUnlockingOrUnlocked = (parent != null)
+ && userManager.isUserUnlockingOrUnlocked(parent.id)
+ && !userManager.isUserUnlockingOrUnlocked(userId);
} finally {
Binder.restoreCallingIdentity(token);
}
- if (parent != null
- && userManager.isUserUnlockingOrUnlocked(parent.getUserHandle())
- && !userManager.isUserUnlockingOrUnlocked(userInfo.getUserHandle())) {
+ if (profileLockedAndParentUnlockingOrUnlocked) {
rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 6deebc0..af5c577 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1448,9 +1448,7 @@
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null) {
bluetoothReceiver = new SynchronousResultReceiver();
- adapter.requestControllerActivityEnergyInfo(
- BluetoothAdapter.ACTIVITY_ENERGY_INFO_REFRESHED,
- bluetoothReceiver);
+ adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
}
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 0165fdf..af7740f 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1508,16 +1508,18 @@
? Configuration.ORIENTATION_PORTRAIT
: Configuration.ORIENTATION_LANDSCAPE;
- // For calculating screen layout and smallest screen width, we need to use the non-decor
- // inset screen area for the calculation for compatibility reasons, i.e. screen area without
- // system bars that could never go away in Honeycomb.
+ // For calculating screen layout, we need to use the non-decor inset screen area for the
+ // calculation for compatibility reasons, i.e. screen area without system bars that could
+ // never go away in Honeycomb.
final int compatScreenWidthDp = (int)(mTmpNonDecorBounds.width() / density);
final int compatScreenHeightDp = (int)(mTmpNonDecorBounds.height() / density);
final int sl = Configuration.resetScreenLayout(serviceConfig.screenLayout);
final int longSize = Math.max(compatScreenHeightDp, compatScreenWidthDp);
- config.smallestScreenWidthDp = Math.min(compatScreenHeightDp, compatScreenWidthDp);
- config.screenLayout = Configuration.reduceScreenLayout(sl, longSize,
- config.smallestScreenWidthDp);
+ final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp);;
+ config.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize);
+
+ config.smallestScreenWidthDp = mService.mWindowManager.getSmallestWidthForTaskBounds(
+ insetBounds != null ? insetBounds : bounds);
return config;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index c89055c..43d8bac 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -826,9 +826,7 @@
@ServiceThreadOnly
void onNewAvrAdded(HdmiDeviceInfo avr) {
assertRunOnServiceThread();
- if (getSystemAudioModeSetting() && !isSystemAudioActivated()) {
- addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
- }
+ addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
if (isArcFeatureEnabled(avr.getPortId())
&& !hasAction(SetArcTransmissionStateAction.class)) {
startArcAction(true);
@@ -1172,12 +1170,13 @@
if (getAvrDeviceInfo() == null) {
// AVR may not have been discovered yet. Delay the message processing.
mDelayedMessageBuffer.add(message);
- return true;
+ } else {
+ HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
+ mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
}
- HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
- mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
return true;
}
+ removeAction(SystemAudioAutoInitiationAction.class);
SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null);
addAndStartAction(action);
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
index 512d537..78b40fd 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
@@ -64,13 +64,13 @@
}
if (cmd.getOpcode() == Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS) {
- handleSystemAudioModeStatusMessage();
+ handleSystemAudioModeStatusMessage(HdmiUtils.parseCommandParamSystemAudioStatus(cmd));
return true;
}
return false;
}
- private void handleSystemAudioModeStatusMessage() {
+ private void handleSystemAudioModeStatusMessage(boolean isSystemAudioModeOn) {
if (!canChangeSystemAudio()) {
HdmiLogger.debug("Cannot change system audio mode in auto initiation action.");
finish();
@@ -78,9 +78,11 @@
}
boolean systemAudioModeSetting = tv().getSystemAudioModeSetting();
- // Update AVR's system audio mode regardless of AVR's status.
- addAndStartAction(new SystemAudioActionFromTv(tv(), mAvrAddress, systemAudioModeSetting,
- null));
+ if (systemAudioModeSetting && !isSystemAudioModeOn) {
+ addAndStartAction(new SystemAudioActionFromTv(tv(), mAvrAddress, systemAudioModeSetting, null));
+ } else {
+ tv().setSystemAudioMode(isSystemAudioModeOn, true);
+ }
finish();
}
diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
index 6722bfb..c1d7c58 100644
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
@@ -337,7 +337,7 @@
} else {
pw.print(",");
}
- pw.print(System.identityHashCode(it.next()));
+ pw.print(System.identityHashCode(js));
}
if (printed) {
pw.println();
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 29e2e44..53c5a6d 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -868,7 +868,7 @@
return false;
}
if (this.userid == UserHandle.USER_ALL) return true;
- if (this.userid == UserHandle.USER_SYSTEM) return true;
+ if (this.isSystem) return true;
if (nid == UserHandle.USER_ALL || nid == this.userid) return true;
return supportsProfiles() && mUserProfiles.isCurrentProfile(nid);
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 43a0b91..ffe8f75 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -745,6 +745,9 @@
@Override
public void onShortcutChanged(@NonNull String packageName,
@UserIdInt int userId) {
+ if (!ShortcutService.FEATURE_ENABLED) {
+ return;
+ }
postToPackageMonitorHandler(() -> onShortcutChangedInner(packageName, userId));
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a5104a6..b6c269e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2695,6 +2695,9 @@
// If this is first boot after an OTA, and a normal boot, then
// we need to clear code cache directories.
+ // Note that we do *not* clear the application profiles. These remain valid
+ // across OTAs and are used to drive profile verification (post OTA) and
+ // profile compilation (without waiting to collect a fresh set of profiles).
if (mIsUpgrade && !onlyCore) {
Slog.i(TAG, "Build fingerprint changed; clearing code caches");
for (int i = 0; i < mSettings.mPackages.size(); i++) {
@@ -2705,7 +2708,6 @@
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE
| Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
}
- clearAppProfilesLIF(ps.pkg);
}
ver.fingerprint = Build.FINGERPRINT;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index ae8d1b7..4de8b57 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -284,7 +284,7 @@
case "--reset":
forceCompilation = true;
clearProfileData = true;
- compilerFilter = "reset";
+ compilationReason = "install";
break;
default:
pw.println("Error: Unknown option: " + opt);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 0f17804..8268776 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -125,6 +125,8 @@
public class ShortcutService extends IShortcutService.Stub {
static final String TAG = "ShortcutService";
+ public static final boolean FEATURE_ENABLED = false;
+
static final boolean DEBUG = false; // STOPSHIP if true
static final boolean DEBUG_LOAD = false; // STOPSHIP if true
static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
@@ -319,6 +321,9 @@
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mUserManager = context.getSystemService(UserManager.class);
+ if (!FEATURE_ENABLED) {
+ return;
+ }
mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE
@@ -419,6 +424,7 @@
/** lifecycle event */
void onBootPhase(int phase) {
+ // We want to call initialize() to initialize the configurations, so we don't disable this.
if (DEBUG) {
Slog.d(TAG, "onBootPhase: " + phase);
}
@@ -431,6 +437,9 @@
/** lifecycle event */
void handleUnlockUser(int userId) {
+ if (!FEATURE_ENABLED) {
+ return;
+ }
synchronized (mLock) {
// Preload
getUserShortcutsLocked(userId);
@@ -441,6 +450,9 @@
/** lifecycle event */
void handleCleanupUser(int userId) {
+ if (!FEATURE_ENABLED) {
+ return;
+ }
synchronized (mLock) {
unloadUserLocked(userId);
}
@@ -1926,6 +1938,9 @@
*/
@Override
public void onSystemLocaleChangedNoLock() {
+ if (!FEATURE_ENABLED) {
+ return;
+ }
// DO NOT HOLD ANY LOCKS HERE.
// We want to reset throttling for all packages for all users. But we can't just do so
@@ -1938,7 +1953,6 @@
//
// This allows ShortcutUser's to detect the system locale change, so they can reset
// counters.
-
mLocaleChangeSequenceNumber.incrementAndGet();
postToHandler(() -> scheduleSaveBaseState());
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e0c56d7..b2cf89b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3392,7 +3392,7 @@
}
if (down) {
- long shortcutCode = (long) keyCode;
+ long shortcutCode = keyCode;
if (event.isCtrlPressed()) {
shortcutCode |= ((long) KeyEvent.META_CTRL_ON) << Integer.SIZE;
}
@@ -3511,6 +3511,7 @@
return false;
}
+ @Override
public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
throws RemoteException {
synchronized (mLock) {
@@ -3956,7 +3957,7 @@
if (!isKeyguardShowing) {
navTranslucent &= areTranslucentBarsAllowed();
}
- boolean statusBarExpandedNotKeyguard = !isKeyguardShowing
+ boolean statusBarExpandedNotKeyguard = !isKeyguardShowing && mStatusBar != null
&& mStatusBar.getAttrs().height == MATCH_PARENT
&& mStatusBar.getAttrs().width == MATCH_PARENT;
@@ -4822,7 +4823,7 @@
mShowingLockscreen = false;
mShowingDream = false;
mWinShowWhenLocked = null;
- mKeyguardSecure = isKeyguardSecure();
+ mKeyguardSecure = isKeyguardSecure(mCurrentUserId);
mKeyguardSecureIncludingHidden = mKeyguardSecure
&& (mKeyguardDelegate != null && mKeyguardDelegate.isShowing());
}
@@ -6299,9 +6300,9 @@
/** {@inheritDoc} */
@Override
- public boolean isKeyguardSecure() {
+ public boolean isKeyguardSecure(int userId) {
if (mKeyguardDelegate == null) return false;
- return mKeyguardDelegate.isSecure();
+ return mKeyguardDelegate.isSecure(userId);
}
/** {@inheritDoc} */
@@ -6331,6 +6332,7 @@
}
}
+ @Override
public void notifyActivityDrawnForKeyguardLw() {
if (mKeyguardDelegate != null) {
mHandler.post(new Runnable() {
@@ -6846,7 +6848,7 @@
private void updateLockScreenTimeout() {
synchronized (mScreenLockTimeout) {
boolean enable = (mAllowLockscreenWhenOn && mAwake &&
- mKeyguardDelegate != null && mKeyguardDelegate.isSecure());
+ mKeyguardDelegate != null && mKeyguardDelegate.isSecure(mCurrentUserId));
if (mLockScreenTimerActive != enable) {
if (enable) {
if (localLOGV) Log.v(TAG, "setting lockscreen timer");
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 52e5880..7e27558 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -226,9 +226,9 @@
}
}
- public boolean isSecure() {
+ public boolean isSecure(int userId) {
if (mKeyguardService != null) {
- mKeyguardState.secure = mKeyguardService.isSecure();
+ mKeyguardState.secure = mKeyguardService.isSecure(userId);
}
return mKeyguardState.secure;
}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index dacdec0..57e8857 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -234,8 +234,8 @@
return mKeyguardStateMonitor.isShowing();
}
- public boolean isSecure() {
- return mKeyguardStateMonitor.isSecure();
+ public boolean isSecure(int userId) {
+ return mKeyguardStateMonitor.isSecure(userId);
}
public boolean isInputRestricted() {
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
index 30cff03..138f068 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
@@ -62,8 +62,8 @@
return mIsShowing;
}
- public boolean isSecure() {
- return mLockPatternUtils.isSecure(getCurrentUser()) || mSimSecure;
+ public boolean isSecure(int userId) {
+ return mLockPatternUtils.isSecure(userId) || mSimSecure;
}
public boolean isInputRestricted() {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4c7f9eb..06b93293 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -432,7 +432,7 @@
f = new FileOutputStream(wallpaper.cropFile);
bos = new BufferedOutputStream(f, 32*1024);
- finalCrop.compress(Bitmap.CompressFormat.PNG, 90, bos);
+ finalCrop.compress(Bitmap.CompressFormat.JPEG, 100, bos);
bos.flush(); // don't rely on the implicit flush-at-close when noting success
success = true;
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index a234241..abbb5f4 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -88,6 +88,11 @@
// case do not clear allDrawn until the animation completes.
boolean deferClearAllDrawn;
+ // These are to track the app's real drawing status if there were no saved surfaces.
+ boolean allDrawnExcludingSaved;
+ int numInterestingWindowsExcludingSaved;
+ int numDrawnWindowsExclusingSaved;
+
// Is this window's surface needed? This is almost like hidden, except
// it will sometimes be true a little earlier: when the token has
// been shown, but is still waiting for its app transition to execute
@@ -411,6 +416,39 @@
}
}
+ /**
+ * Whether the app has some window that is invisible in layout, but
+ * animating with saved surface.
+ */
+ boolean isAnimatingInvisibleWithSavedSurface() {
+ for (int i = allAppWindows.size() - 1; i >= 0; i--) {
+ final WindowState w = allAppWindows.get(i);
+ if (w.isAnimatingInvisibleWithSavedSurface()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Hide all window surfaces that's still invisible in layout but animating
+ * with a saved surface, and mark them destroying.
+ */
+ void stopUsingSavedSurfaceLocked() {
+ for (int i = allAppWindows.size() - 1; i >= 0; i--) {
+ final WindowState w = allAppWindows.get(i);
+ if (w.isAnimatingInvisibleWithSavedSurface()) {
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.d(TAG,
+ "stopUsingSavedSurfaceLocked: " + w);
+ w.clearAnimatingWithSavedSurface();
+ w.mDestroying = true;
+ w.mWinAnimator.hide("stopUsingSavedSurfaceLocked");
+ w.mWinAnimator.mWallpaperControllerLocked.hideWallpapers(w);
+ }
+ }
+ destroySurfaces();
+ }
+
void restoreSavedSurfaces() {
if (!canRestoreSurfaces()) {
clearVisibleBeforeClientHidden();
@@ -456,6 +494,7 @@
void clearAllDrawn() {
allDrawn = false;
deferClearAllDrawn = false;
+ allDrawnExcludingSaved = false;
}
@Override
@@ -582,10 +621,7 @@
w.mSkipEnterAnimationForSeamlessReplacement = !candidate.mAnimateReplacingWindow;
// if we got a replacement window, reset the timeout to give drawing more time
- service.mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
- service.mH.sendMessageDelayed(
- service.mH.obtainMessage(H.WINDOW_REPLACEMENT_TIMEOUT, this),
- WINDOW_REPLACEMENT_TIMEOUT_DURATION);
+ service.scheduleReplacingWindowTimeouts(this);
}
}
allAppWindows.add(w);
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 6142511..5b2ce07 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -19,6 +19,10 @@
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
@@ -30,6 +34,7 @@
import static com.android.server.wm.WindowManagerService.H.NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -41,6 +46,8 @@
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.policy.DockedDividerUtils;
import com.android.server.wm.DimLayer.DimLayerUser;
import com.android.server.wm.WindowManagerService.H;
@@ -96,6 +103,7 @@
private WindowState mWindow;
private final Rect mTmpRect = new Rect();
private final Rect mTmpRect2 = new Rect();
+ private final Rect mTmpRect3 = new Rect();
private final Rect mLastRect = new Rect();
private boolean mLastVisibility = false;
private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners
@@ -115,12 +123,14 @@
private final Rect mTouchRegion = new Rect();
private boolean mAnimatingForIme;
private boolean mAdjustedForIme;
+ private int mImeHeight;
private WindowState mDelayedImeWin;
private boolean mAdjustedForDivider;
private float mDividerAnimationStart;
private float mDividerAnimationTarget;
private float mLastAnimationProgress;
private float mLastDividerProgress;
+ private final DividerSnapAlgorithm[] mSnapAlgorithmForRotation = new DividerSnapAlgorithm[4];
DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
mService = service;
@@ -133,6 +143,77 @@
loadDimens();
}
+ int getSmallestWidthDpForBounds(Rect bounds) {
+ final DisplayInfo di = mDisplayContent.getDisplayInfo();
+
+ // If the bounds are fullscreen, return the value of the fullscreen configuration
+ if (bounds == null || (bounds.left == 0 && bounds.top == 0
+ && bounds.right == di.logicalWidth && bounds.bottom == di.logicalHeight)) {
+ return mService.mCurConfiguration.smallestScreenWidthDp;
+ }
+ final int baseDisplayWidth = mDisplayContent.mBaseDisplayWidth;
+ final int baseDisplayHeight = mDisplayContent.mBaseDisplayHeight;
+ int minWidth = Integer.MAX_VALUE;
+
+ // Go through all screen orientations and find the orientation in which the task has the
+ // smallest width.
+ for (int rotation = 0; rotation < 4; rotation++) {
+ mTmpRect.set(bounds);
+ mDisplayContent.rotateBounds(di.rotation, rotation, mTmpRect);
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ mTmpRect2.set(0, 0,
+ rotated ? baseDisplayHeight : baseDisplayWidth,
+ rotated ? baseDisplayWidth : baseDisplayHeight);
+ final int orientation = mTmpRect2.width() <= mTmpRect2.height()
+ ? ORIENTATION_PORTRAIT
+ : ORIENTATION_LANDSCAPE;
+ final int dockSide = TaskStack.getDockSideUnchecked(mTmpRect, mTmpRect2, orientation);
+ final int position = DockedDividerUtils.calculatePositionForBounds(mTmpRect, dockSide,
+ getContentWidth());
+
+ // Since we only care about feasible states, snap to the closest snap target, like it
+ // would happen when actually rotating the screen.
+ final int snappedPosition = mSnapAlgorithmForRotation[rotation]
+ .calculateNonDismissingSnapTarget(position).position;
+ DockedDividerUtils.calculateBoundsForPosition(snappedPosition, dockSide, mTmpRect,
+ mTmpRect2.width(), mTmpRect2.height(), getContentWidth());
+ mService.mPolicy.getStableInsetsLw(rotation, mTmpRect2.width(), mTmpRect2.height(),
+ mTmpRect3);
+ mService.subtractInsets(mTmpRect2, mTmpRect3, mTmpRect);
+ minWidth = Math.min(mTmpRect.width(), minWidth);
+ }
+ return (int) (minWidth / mDisplayContent.getDisplayMetrics().density);
+ }
+
+ private void initSnapAlgorithmForRotations() {
+ final Configuration baseConfig = mService.mCurConfiguration;
+
+ // Initialize the snap algorithms for all 4 screen orientations.
+ final Configuration config = new Configuration();
+ for (int rotation = 0; rotation < 4; rotation++) {
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ final int dw = rotated
+ ? mDisplayContent.mBaseDisplayHeight
+ : mDisplayContent.mBaseDisplayWidth;
+ final int dh = rotated
+ ? mDisplayContent.mBaseDisplayWidth
+ : mDisplayContent.mBaseDisplayHeight;
+ mService.mPolicy.getStableInsetsLw(rotation, dw, dh, mTmpRect);
+ config.setToDefaults();
+ config.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+ config.screenWidthDp = (int)
+ (mService.mPolicy.getConfigDisplayWidth(dw, dh, rotation, baseConfig.uiMode) /
+ mDisplayContent.getDisplayMetrics().density);
+ config.screenHeightDp = (int)
+ (mService.mPolicy.getConfigDisplayHeight(dw, dh, rotation, baseConfig.uiMode) /
+ mDisplayContent.getDisplayMetrics().density);
+ final Context rotationContext = mService.mContext.createConfigurationContext(config);
+ mSnapAlgorithmForRotation[rotation] = new DividerSnapAlgorithm(
+ rotationContext.getResources(), dw, dh, getContentWidth(),
+ config.orientation == ORIENTATION_PORTRAIT, mTmpRect);
+ }
+ }
+
private void loadDimens() {
final Context context = mService.mContext;
mDividerWindowWidth = context.getResources().getDimensionPixelSize(
@@ -141,6 +222,7 @@
com.android.internal.R.dimen.docked_stack_divider_insets);
mDividerWindowWidthInactive = WindowManagerService.dipToPixel(
DIVIDER_WIDTH_INACTIVE_DP, mDisplayContent.getDisplayMetrics());
+ initSnapAlgorithmForRotations();
}
void onConfigurationChanged() {
@@ -215,8 +297,9 @@
void setAdjustedForIme(
boolean adjustedForIme, boolean adjustedForDivider,
- boolean animate, WindowState imeWin) {
- if (mAdjustedForIme != adjustedForIme || mAdjustedForDivider != adjustedForDivider) {
+ boolean animate, WindowState imeWin, int imeHeight) {
+ if (mAdjustedForIme != adjustedForIme || (adjustedForIme && mImeHeight != imeHeight)
+ || mAdjustedForDivider != adjustedForDivider) {
if (animate) {
startImeAdjustAnimation(adjustedForIme, adjustedForDivider, imeWin);
} else {
@@ -224,10 +307,15 @@
notifyAdjustedForImeChanged(adjustedForIme || adjustedForDivider, 0 /* duration */);
}
mAdjustedForIme = adjustedForIme;
+ mImeHeight = imeHeight;
mAdjustedForDivider = adjustedForDivider;
}
}
+ int getImeHeightAdjustedFor() {
+ return mImeHeight;
+ }
+
void positionDockedStackedDivider(Rect frame) {
TaskStack stack = mDisplayContent.getDockedStackLocked();
if (stack == null) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index b2f3df7..7426329 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -849,10 +849,10 @@
*
* @param imeWin The IME window.
*/
- void setAdjustedForIme(WindowState imeWin) {
+ void setAdjustedForIme(WindowState imeWin, boolean forceUpdate) {
mImeWin = imeWin;
mImeGoingAway = false;
- if (!mAdjustedForIme) {
+ if (!mAdjustedForIme || forceUpdate) {
mAdjustedForIme = true;
mAdjustImeAmount = 0f;
mAdjustDividerAmount = 0f;
@@ -1197,16 +1197,20 @@
}
mDisplayContent.getLogicalDisplayRect(mTmpRect);
final int orientation = mService.mCurConfiguration.orientation;
+ return getDockSideUnchecked(bounds, mTmpRect, orientation);
+ }
+
+ static int getDockSideUnchecked(Rect bounds, Rect displayRect, int orientation) {
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
// Portrait mode, docked either at the top or the bottom.
- if (bounds.top - mTmpRect.top <= mTmpRect.bottom - bounds.bottom) {
+ if (bounds.top - displayRect.top <= displayRect.bottom - bounds.bottom) {
return DOCKED_TOP;
} else {
return DOCKED_BOTTOM;
}
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
// Landscape mode, docked either on the left or on the right.
- if (bounds.left - mTmpRect.left <= mTmpRect.right - bounds.right) {
+ if (bounds.left - displayRect.left <= displayRect.right - bounds.right) {
return DOCKED_LEFT;
} else {
return DOCKED_RIGHT;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index eae7838..be060d2 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -788,6 +788,7 @@
removeReplacedWindowsLocked();
}
+ mService.stopUsingSavedSurfaceLocked();
mService.destroyPreservedSurfaceLocked();
mService.mWindowPlacerLocked.destroyPendingSurfaces();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a0b8c35..8fa9efb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -415,6 +415,19 @@
final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<>();
/**
+ * List of window tokens that have finished drawing their own windows and
+ * no longer need to show any saved surfaces. Windows that's still showing
+ * saved surfaces will be cleaned up after next animation pass.
+ */
+ final ArrayList<AppWindowToken> mFinishedEarlyAnim = new ArrayList<>();
+
+ /**
+ * List of app window tokens that are waiting for replacing windows. If the
+ * replacement doesn't come in time the stale windows needs to be disposed of.
+ */
+ final ArrayList<AppWindowToken> mReplacingWindowTimeouts = new ArrayList<>();
+
+ /**
* The input consumer added to the window manager which consumes input events to windows below
* it.
*/
@@ -501,7 +514,7 @@
final float[] mTmpFloats = new float[9];
final Rect mTmpRect = new Rect();
final Rect mTmpRect2 = new Rect();
- final Region mTmpRegion = new Region();
+ final Rect mTmpRect3 = new Rect();
boolean mDisplayReady;
boolean mSafeMode;
@@ -2324,6 +2337,23 @@
Binder.restoreCallingIdentity(origId);
return;
}
+
+ if (win.isAnimatingWithSavedSurface() && !appToken.allDrawnExcludingSaved) {
+ // We started enter animation early with a saved surface, now the app asks to remove
+ // this window. If we remove it now and the app is not yet drawn, we'll show a
+ // flicker. Delay the removal now until it's really drawn.
+ if (DEBUG_ADD_REMOVE) {
+ Slog.d(TAG_WM, "removeWindowLocked: delay removal of " + win
+ + " due to early animation");
+ }
+ // Do not set mAnimatingExit to true here, it will cause the surface to be hidden
+ // immediately after the enter animation is done. If the app is not yet drawn then
+ // it will show up as a flicker.
+ win.mRemoveOnExit = true;
+ win.mWindowRemovalAllowed = true;
+ Binder.restoreCallingIdentity(origId);
+ return;
+ }
// If we are not currently running the exit animation, we need to see about starting one
wasVisible = win.isWinVisibleLw();
@@ -5344,9 +5374,10 @@
@Override
public boolean isKeyguardSecure() {
+ int userId = UserHandle.getCallingUserId();
long origId = Binder.clearCallingIdentity();
try {
- return mPolicy.isKeyguardSecure();
+ return mPolicy.isKeyguardSecure(userId);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -7535,6 +7566,9 @@
final boolean imeOnTop = (imeDockSide == DOCKED_TOP);
final boolean imeOnBottom = (imeDockSide == DOCKED_BOTTOM);
final boolean dockMinimized = displayContent.mDividerControllerLocked.isMinimizedDock();
+ final int imeHeight = mPolicy.getInputMethodWindowVisibleHeightLw();
+ final boolean imeHeightChanged = imeVisible &&
+ imeHeight != displayContent.mDividerControllerLocked.getImeHeightAdjustedFor();
// The divider could be adjusted for IME position, or be thinner than usual,
// or both. There are three possible cases:
@@ -7548,13 +7582,13 @@
final TaskStack stack = stacks.get(i);
final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM;
if (stack.isVisibleLocked() && (imeOnBottom || isDockedOnBottom)) {
- stack.setAdjustedForIme(imeWin);
+ stack.setAdjustedForIme(imeWin, imeOnBottom && imeHeightChanged);
} else {
stack.resetAdjustedForIme(false);
}
}
displayContent.mDividerControllerLocked.setAdjustedForIme(
- imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin);
+ imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin, imeHeight);
} else {
final ArrayList<TaskStack> stacks = displayContent.getStacks();
for (int i = stacks.size() - 1; i >= 0; --i) {
@@ -7562,7 +7596,7 @@
stack.resetAdjustedForIme(!dockVisible);
}
displayContent.mDividerControllerLocked.setAdjustedForIme(
- false /*ime*/, false /*divider*/, dockVisible /*animate*/, imeWin);
+ false /*ime*/, false /*divider*/, dockVisible /*animate*/, imeWin, imeHeight);
}
}
@@ -8496,9 +8530,12 @@
}
break;
case WINDOW_REPLACEMENT_TIMEOUT: {
- final AppWindowToken token = (AppWindowToken) msg.obj;
synchronized (mWindowMap) {
- token.clearTimedoutReplacesLocked();
+ for (int i = mReplacingWindowTimeouts.size() - 1; i >= 0; i--) {
+ final AppWindowToken token = mReplacingWindowTimeouts.get(i);
+ token.clearTimedoutReplacesLocked();
+ }
+ mReplacingWindowTimeouts.clear();
}
}
case NOTIFY_APP_TRANSITION_STARTING: {
@@ -8547,6 +8584,15 @@
}
mDestroyPreservedSurface.clear();
}
+
+ void stopUsingSavedSurfaceLocked() {
+ for (int i = mFinishedEarlyAnim.size() - 1; i >= 0 ; i--) {
+ final AppWindowToken wtoken = mFinishedEarlyAnim.get(i);
+ wtoken.stopUsingSavedSurfaceLocked();
+ }
+ mFinishedEarlyAnim.clear();
+ }
+
// -------------------------------------------------------------
// IWindowManager API
// -------------------------------------------------------------
@@ -9171,6 +9217,18 @@
void updateResizingWindows(final WindowState w) {
final WindowStateAnimator winAnimator = w.mWinAnimator;
if (w.mHasSurface && w.mLayoutSeq == mLayoutSeq && !w.isGoneForLayoutLw()) {
+ final Task task = w.getTask();
+ // In the case of stack bound animations, the window frames
+ // will update (unlike other animations which just modifiy
+ // various transformation properties). We don't want to
+ // notify the client of frame changes in this case. Not only
+ // is it a lot of churn, but the frame may not correspond
+ // to the surface size or the onscreen area at various
+ // phases in the animation, and the client will become
+ // sad and confused.
+ if (task != null && task.mStack.getBoundsAnimating()) {
+ return;
+ }
w.setInsetsChanged();
boolean configChanged = w.isConfigChanged();
if (DEBUG_CONFIGURATION && configChanged) {
@@ -10756,16 +10814,22 @@
return;
}
if (replacing) {
- mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
- mH.sendMessageDelayed(
- mH.obtainMessage(H.WINDOW_REPLACEMENT_TIMEOUT, appWindowToken),
- WINDOW_REPLACEMENT_TIMEOUT_DURATION);
+ scheduleReplacingWindowTimeouts(appWindowToken);
} else {
appWindowToken.resetReplacingWindows();
}
}
}
+ void scheduleReplacingWindowTimeouts(AppWindowToken appWindowToken) {
+ if (!mReplacingWindowTimeouts.contains(appWindowToken)) {
+ mReplacingWindowTimeouts.add(appWindowToken);
+ }
+ mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
+ mH.sendEmptyMessageDelayed(
+ H.WINDOW_REPLACEMENT_TIMEOUT, WINDOW_REPLACEMENT_TIMEOUT_DURATION);
+ }
+
@Override
public int getDockedStackSide() {
synchronized (mWindowMap) {
@@ -10888,8 +10952,7 @@
getStableInsetsLocked(mTmpRect2);
final DisplayInfo di = getDefaultDisplayInfoLocked();
mTmpRect.set(0, 0, di.logicalWidth, di.logicalHeight);
- mTmpRect.inset(mTmpRect2);
- inOutBounds.intersect(mTmpRect);
+ subtractInsets(mTmpRect, mTmpRect2, inOutBounds);
}
}
@@ -10905,8 +10968,27 @@
getNonDecorInsetsLocked(mTmpRect2);
final DisplayInfo di = getDefaultDisplayInfoLocked();
mTmpRect.set(0, 0, di.logicalWidth, di.logicalHeight);
- mTmpRect.inset(mTmpRect2);
- inOutBounds.intersect(mTmpRect);
+ subtractInsets(mTmpRect, mTmpRect2, inOutBounds);
+ }
+ }
+
+ void subtractInsets(Rect display, Rect insets, Rect inOutBounds) {
+ mTmpRect3.set(display);
+ mTmpRect3.inset(insets);
+ inOutBounds.intersect(mTmpRect3);
+ }
+
+ /**
+ * Calculates the smallest width for a task given the {@param bounds}. It does that by iterating
+ * across all screen orientations, and returns the minimum of the task width taking into account
+ * that the bounds might change because the snap algorithm snaps to a different value.
+ *
+ * @return the smallest width to be used in the Configuration, in dips
+ */
+ public int getSmallestWidthForTaskBounds(Rect bounds) {
+ synchronized (mWindowMap) {
+ return getDefaultDisplayContentLocked().getDockedDividerController()
+ .getSmallestWidthDpForBounds(bounds);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b66de89..c9d945a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -437,7 +437,9 @@
// used to start an entering animation earlier.
private boolean mSurfaceSaved = false;
- // Whether we're performing an entering animation with a saved surface.
+ // Whether we're performing an entering animation with a saved surface. This flag is
+ // true during the time we're showing a window with a previously saved surface. It's
+ // cleared when surface is destroyed, saved, or re-drawn by the app.
private boolean mAnimatingWithSavedSurface;
// Whether the window was visible when we set the app to invisible last time. WM uses
@@ -1256,6 +1258,32 @@
}
/**
+ * Whether this window's drawn state might affect the drawn states of the app token.
+ *
+ * @param visibleOnly Whether we should consider only the windows that's currently
+ * visible in layout. If true, windows that has not relayout to VISIBLE
+ * would always return false.
+ *
+ * @return true if the window should be considered while evaluating allDrawn flags.
+ */
+ boolean mightAffectAllDrawn(boolean visibleOnly) {
+ final boolean isViewVisible = (mViewVisibility == View.VISIBLE)
+ && (mAppToken == null || !mAppToken.clientHidden);
+ return (isOnScreenIgnoringKeyguard() && (!visibleOnly || isViewVisible)
+ || mWinAnimator.mAttrType == TYPE_BASE_APPLICATION)
+ && !mAnimatingExit && !mDestroying;
+ }
+
+ /**
+ * Whether this window is "interesting" when evaluating allDrawn. If it's interesting,
+ * it must be drawn before allDrawn can become true.
+ */
+ boolean isInteresting() {
+ return mAppToken != null && !mAppDied
+ && (!mAppToken.mAppAnimator.freezingScreen || !mAppFreezing);
+ }
+
+ /**
* Like isOnScreen(), but we don't return true if the window is part
* of a transition that has not yet been started.
*/
@@ -1960,6 +1988,11 @@
return mAnimatingWithSavedSurface;
}
+ boolean isAnimatingInvisibleWithSavedSurface() {
+ return mAnimatingWithSavedSurface
+ && (mViewVisibility != View.VISIBLE || mWindowRemovalAllowed);
+ }
+
public void setVisibleBeforeClientHidden() {
mWasVisibleBeforeClientHidden |=
(mViewVisibility == View.VISIBLE || mAnimatingWithSavedSurface);
@@ -2042,6 +2075,7 @@
if (mWinAnimator.mSurfaceController != null) {
mWinAnimator.mSurfaceController.disconnectInTransaction();
}
+ mAnimatingWithSavedSurface = false;
} else {
mWinAnimator.destroySurfaceLocked();
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 9e66c28..5678ad9 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -829,8 +829,8 @@
// Adjust for surface insets.
mTmpSize.left -= scale * attrs.surfaceInsets.left;
mTmpSize.top -= scale * attrs.surfaceInsets.top;
- mTmpSize.right += scale * (attrs.surfaceInsets.left + attrs.surfaceInsets.right);
- mTmpSize.bottom += scale * (attrs.surfaceInsets.top + attrs.surfaceInsets.bottom);
+ mTmpSize.right += scale * attrs.surfaceInsets.right;
+ mTmpSize.bottom += scale * attrs.surfaceInsets.bottom;
}
boolean hasSurface() {
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index e20e245..7e9993d 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -792,15 +792,16 @@
+ " isOnScreen=" + w.isOnScreen() + " allDrawn=" + atoken.allDrawn
+ " freezingScreen=" + atoken.mAppAnimator.freezingScreen);
}
- if (atoken != null && (!atoken.allDrawn || atoken.mAppAnimator.freezingScreen)) {
+ if (atoken != null && (!atoken.allDrawn || !atoken.allDrawnExcludingSaved
+ || atoken.mAppAnimator.freezingScreen)) {
if (atoken.lastTransactionSequence != mService.mTransactionSequence) {
atoken.lastTransactionSequence = mService.mTransactionSequence;
atoken.numInterestingWindows = atoken.numDrawnWindows = 0;
+ atoken.numInterestingWindowsExcludingSaved = 0;
+ atoken.numDrawnWindowsExclusingSaved = 0;
atoken.startingDisplayed = false;
}
- if ((w.isOnScreenIgnoringKeyguard()
- || winAnimator.mAttrType == TYPE_BASE_APPLICATION)
- && !w.mAnimatingExit && !w.mDestroying) {
+ if (!atoken.allDrawn && w.mightAffectAllDrawn(false /* visibleOnly */)) {
if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) {
Slog.v(TAG, "Eval win " + w + ": isDrawn="
+ w.isDrawnLw()
@@ -816,13 +817,14 @@
}
}
if (w != atoken.startingWindow) {
- if (!w.mAppDied &&
- (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing)) {
+ if (w.isInteresting()) {
atoken.numInterestingWindows++;
if (w.isDrawnLw()) {
atoken.numDrawnWindows++;
if (DEBUG_VISIBILITY || DEBUG_ORIENTATION)
Slog.v(TAG, "tokenMayBeDrawn: " + atoken
+ + " w=" + w + " numInteresting="
+ + atoken.numInterestingWindows
+ " freezingScreen="
+ atoken.mAppAnimator.freezingScreen
+ " mAppFreezing=" + w.mAppFreezing);
@@ -834,6 +836,23 @@
atoken.startingDisplayed = true;
}
}
+ if (!atoken.allDrawnExcludingSaved
+ && w.mightAffectAllDrawn(true /* visibleOnly */)) {
+ if (w != atoken.startingWindow && w.isInteresting()) {
+ atoken.numInterestingWindowsExcludingSaved++;
+ if (w.isDrawnLw() && !w.isAnimatingWithSavedSurface()) {
+ atoken.numDrawnWindowsExclusingSaved++;
+ if (DEBUG_VISIBILITY || DEBUG_ORIENTATION)
+ Slog.v(TAG, "tokenMayBeDrawnExcludingSaved: " + atoken
+ + " w=" + w + " numInteresting="
+ + atoken.numInterestingWindowsExcludingSaved
+ + " freezingScreen="
+ + atoken.mAppAnimator.freezingScreen
+ + " mAppFreezing=" + w.mAppFreezing);
+ updateAllDrawn = true;
+ }
+ }
+ }
}
if (isDefaultDisplay && someoneLosingFocus && w == mService.mCurrentFocus
@@ -1539,6 +1558,22 @@
wtoken.token).sendToTarget();
}
}
+ if (!wtoken.allDrawnExcludingSaved) {
+ int numInteresting = wtoken.numInterestingWindowsExcludingSaved;
+ if (numInteresting > 0
+ && wtoken.numDrawnWindowsExclusingSaved >= numInteresting) {
+ if (DEBUG_VISIBILITY)
+ Slog.v(TAG, "allDrawnExcludingSaved: " + wtoken
+ + " interesting=" + numInteresting
+ + " drawn=" + wtoken.numDrawnWindowsExclusingSaved);
+ wtoken.allDrawnExcludingSaved = true;
+ displayContent.layoutNeeded = true;
+ if (wtoken.isAnimatingInvisibleWithSavedSurface()
+ && !mService.mFinishedEarlyAnim.contains(wtoken)) {
+ mService.mFinishedEarlyAnim.add(wtoken);
+ }
+ }
+ }
}
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 00b83841..e306d89 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -643,6 +643,10 @@
traceBeginAndSlog("ConnectivityMetricsLoggerService");
mSystemServiceManager.startService(MetricsLoggerService.class);
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+
+ traceBeginAndSlog("PinnerService");
+ mSystemServiceManager.startService(PinnerService.class);
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
} catch (RuntimeException e) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting core service", e);
diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/services/net/java/android/net/apf/ApfCapabilities.java
index 0ec50c4..b0e0230 100644
--- a/services/net/java/android/net/apf/ApfCapabilities.java
+++ b/services/net/java/android/net/apf/ApfCapabilities.java
@@ -38,7 +38,8 @@
*/
public final int apfPacketFormat;
- ApfCapabilities(int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat) {
+ public ApfCapabilities(int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat)
+ {
this.apfVersionSupported = apfVersionSupported;
this.maximumApfProgramSize = maximumApfProgramSize;
this.apfPacketFormat = apfPacketFormat;
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 5a10275..4cbfd9c 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -31,6 +31,7 @@
import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
@@ -69,7 +70,8 @@
*/
public class ApfFilter {
// Thread to listen for RAs.
- private class ReceiveThread extends Thread {
+ @VisibleForTesting
+ class ReceiveThread extends Thread {
private final byte[] mPacket = new byte[1514];
private final FileDescriptor mSocket;
private volatile boolean mStopped;
@@ -151,8 +153,10 @@
private final ApfCapabilities mApfCapabilities;
private final IpManager.Callback mIpManagerCallback;
private final NetworkInterface mNetworkInterface;
- private byte[] mHardwareAddress;
- private ReceiveThread mReceiveThread;
+ @VisibleForTesting
+ byte[] mHardwareAddress;
+ @VisibleForTesting
+ ReceiveThread mReceiveThread;
@GuardedBy("this")
private long mUniqueCounter;
@GuardedBy("this")
@@ -161,7 +165,8 @@
@GuardedBy("this")
private byte[] mIPv4Address;
- private ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
+ @VisibleForTesting
+ ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
IpManager.Callback ipManagerCallback, boolean multicastFilter) {
mApfCapabilities = apfCapabilities;
mIpManagerCallback = ipManagerCallback;
@@ -184,7 +189,8 @@
* Attempt to start listening for RAs and, if RAs are received, generating and installing
* filters to ignore useless RAs.
*/
- private void maybeStartFilter() {
+ @VisibleForTesting
+ void maybeStartFilter() {
FileDescriptor socket;
try {
mHardwareAddress = mNetworkInterface.getHardwareAddress();
@@ -724,7 +730,8 @@
}
@GuardedBy("this")
- private void installNewProgramLocked() {
+ @VisibleForTesting
+ void installNewProgramLocked() {
purgeExpiredRasLocked();
final byte[] program;
long programMinLifetime = Long.MAX_VALUE;
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 34152cf..d8eab35 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -276,6 +276,16 @@
public static class Builder {
private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
+ public Builder withoutIPv4() {
+ mConfig.mEnableIPv4 = false;
+ return this;
+ }
+
+ public Builder withoutIPv6() {
+ mConfig.mEnableIPv6 = false;
+ return this;
+ }
+
public Builder withoutIpReachabilityMonitor() {
mConfig.mUsingIpReachabilityMonitor = false;
return this;
@@ -311,6 +321,8 @@
}
}
+ /* package */ boolean mEnableIPv4 = true;
+ /* package */ boolean mEnableIPv6 = true;
/* package */ boolean mUsingIpReachabilityMonitor = true;
/* package */ int mRequestedPreDhcpActionMs;
/* package */ StaticIpConfiguration mStaticIpConfig;
@@ -320,6 +332,8 @@
public ProvisioningConfiguration() {}
public ProvisioningConfiguration(ProvisioningConfiguration other) {
+ mEnableIPv4 = other.mEnableIPv4;
+ mEnableIPv6 = other.mEnableIPv6;
mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
mStaticIpConfig = other.mStaticIpConfig;
@@ -330,6 +344,8 @@
@Override
public String toString() {
return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
+ .add("mEnableIPv4: " + mEnableIPv4)
+ .add("mEnableIPv6: " + mEnableIPv6)
.add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
.add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
.add("mStaticIpConfig: " + mStaticIpConfig)
@@ -883,6 +899,51 @@
}
}
+ private boolean startIPv4() {
+ // If we have a StaticIpConfiguration attempt to apply it and
+ // handle the result accordingly.
+ if (mConfiguration.mStaticIpConfig != null) {
+ if (setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
+ handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
+ } else {
+ if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
+ recordMetric(IpManagerEvent.PROVISIONING_FAIL);
+ mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
+ return false;
+ }
+ } else {
+ // Start DHCPv4.
+ mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpManager.this, mInterfaceName);
+ mDhcpClient.registerForPreDhcpNotification();
+ mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
+
+ if (mConfiguration.mProvisioningTimeoutMs > 0) {
+ final long alarmTime = SystemClock.elapsedRealtime() +
+ mConfiguration.mProvisioningTimeoutMs;
+ mProvisioningTimeoutAlarm.schedule(alarmTime);
+ }
+ }
+
+ return true;
+ }
+
+ private boolean startIPv6() {
+ // Set privacy extensions.
+ try {
+ mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
+ mNwService.enableIpv6(mInterfaceName);
+ } catch (RemoteException re) {
+ Log.e(mTag, "Unable to change interface settings: " + re);
+ return false;
+ } catch (IllegalStateException ie) {
+ Log.e(mTag, "Unable to change interface settings: " + ie);
+ return false;
+ }
+
+ return true;
+ }
+
+
class StoppedState extends State {
@Override
public void enter() {
@@ -978,15 +1039,9 @@
mCallback.setFallbackMulticastFilter(mMulticastFiltering);
}
- // Set privacy extensions.
- try {
- mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
- mNwService.enableIpv6(mInterfaceName);
- // TODO: Perhaps clearIPv4Address() as well.
- } catch (RemoteException re) {
- Log.e(mTag, "Unable to change interface settings: " + re);
- } catch (IllegalStateException ie) {
- Log.e(mTag, "Unable to change interface settings: " + ie);
+ if (mConfiguration.mEnableIPv6) {
+ // TODO: Consider transitionTo(mStoppingState) if this fails.
+ startIPv6();
}
if (mConfiguration.mUsingIpReachabilityMonitor) {
@@ -1001,31 +1056,10 @@
});
}
- // If we have a StaticIpConfiguration attempt to apply it and
- // handle the result accordingly.
- if (mConfiguration.mStaticIpConfig != null) {
- if (setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
- handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
- } else {
- if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
- recordMetric(IpManagerEvent.PROVISIONING_FAIL);
- mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
+ if (mConfiguration.mEnableIPv4) {
+ if (!startIPv4()) {
transitionTo(mStoppingState);
}
- } else {
- // Start DHCPv4.
- mDhcpClient = DhcpClient.makeDhcpClient(
- mContext,
- IpManager.this,
- mInterfaceName);
- mDhcpClient.registerForPreDhcpNotification();
- mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
-
- if (mConfiguration.mProvisioningTimeoutMs > 0) {
- final long alarmTime = SystemClock.elapsedRealtime() +
- mConfiguration.mProvisioningTimeoutMs;
- mProvisioningTimeoutAlarm.schedule(alarmTime);
- }
}
}
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 59c6970..0437e1d 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -19,8 +19,7 @@
easymocklib \
guava \
android-support-test \
- mockito-target \
- ShortcutManagerTestUtils
+ mockito-target
LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/services/tests/servicestests/jni/apf_jni.cpp b/services/tests/servicestests/jni/apf_jni.cpp
index 7d142eb..ee43dd4 100644
--- a/services/tests/servicestests/jni/apf_jni.cpp
+++ b/services/tests/servicestests/jni/apf_jni.cpp
@@ -175,7 +175,7 @@
(void*)com_android_server_ApfTest_compareBpfApf },
};
- jniRegisterNativeMethods(env, "com/android/server/ApfTest",
+ jniRegisterNativeMethods(env, "android/net/apf/ApfTest",
gMethods, ARRAY_SIZE(gMethods));
return JNI_VERSION_1_6;
diff --git a/services/tests/servicestests/src/android/net/apf/ApfTest.java b/services/tests/servicestests/src/android/net/apf/ApfTest.java
new file mode 100644
index 0000000..e9c5bdd
--- /dev/null
+++ b/services/tests/servicestests/src/android/net/apf/ApfTest.java
@@ -0,0 +1,952 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.apf;
+
+import static android.system.OsConstants.*;
+
+import com.android.frameworks.servicestests.R;
+
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter;
+import android.net.apf.ApfGenerator;
+import android.net.apf.ApfGenerator.IllegalInstructionException;
+import android.net.apf.ApfGenerator.Register;
+import android.net.ip.IpManager;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.os.ConditionVariable;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.nio.ByteBuffer;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * Tests for APF program generator and interpreter.
+ *
+ * Build, install and run with:
+ * runtest frameworks-services -c com.android.server.ApfTest
+ */
+public class ApfTest extends AndroidTestCase {
+ private static final int TIMEOUT_MS = 500;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ // Load up native shared library containing APF interpreter exposed via JNI.
+ System.loadLibrary("servicestestsjni");
+ }
+
+ // Expected return codes from APF interpreter.
+ private final static int PASS = 1;
+ private final static int DROP = 0;
+ // Interpreter will just accept packets without link layer headers, so pad fake packet to at
+ // least the minimum packet size.
+ private final static int MIN_PKT_SIZE = 15;
+
+ private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) {
+ assertEquals(expected, apfSimulate(program, packet, filterAge));
+ }
+
+ private void assertPass(byte[] program, byte[] packet, int filterAge) {
+ assertVerdict(PASS, program, packet, filterAge);
+ }
+
+ private void assertDrop(byte[] program, byte[] packet, int filterAge) {
+ assertVerdict(DROP, program, packet, filterAge);
+ }
+
+ private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge)
+ throws IllegalInstructionException {
+ assertEquals(expected, apfSimulate(gen.generate(), packet, filterAge));
+ }
+
+ private void assertPass(ApfGenerator gen, byte[] packet, int filterAge)
+ throws IllegalInstructionException {
+ assertVerdict(PASS, gen, packet, filterAge);
+ }
+
+ private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge)
+ throws IllegalInstructionException {
+ assertVerdict(DROP, gen, packet, filterAge);
+ }
+
+ private void assertPass(ApfGenerator gen)
+ throws IllegalInstructionException {
+ assertVerdict(PASS, gen, new byte[MIN_PKT_SIZE], 0);
+ }
+
+ private void assertDrop(ApfGenerator gen)
+ throws IllegalInstructionException {
+ assertVerdict(DROP, gen, new byte[MIN_PKT_SIZE], 0);
+ }
+
+ /**
+ * Test each instruction by generating a program containing the instruction,
+ * generating bytecode for that program and running it through the
+ * interpreter to verify it functions correctly.
+ */
+ @LargeTest
+ public void testApfInstructions() throws IllegalInstructionException {
+ // Empty program should pass because having the program counter reach the
+ // location immediately after the program indicates the packet should be
+ // passed to the AP.
+ ApfGenerator gen = new ApfGenerator();
+ assertPass(gen);
+
+ // Test jumping to pass label.
+ gen = new ApfGenerator();
+ gen.addJump(gen.PASS_LABEL);
+ byte[] program = gen.generate();
+ assertEquals(1, program.length);
+ assertEquals((14 << 3) | (0 << 1) | 0, program[0]);
+ assertPass(program, new byte[MIN_PKT_SIZE], 0);
+
+ // Test jumping to drop label.
+ gen = new ApfGenerator();
+ gen.addJump(gen.DROP_LABEL);
+ program = gen.generate();
+ assertEquals(2, program.length);
+ assertEquals((14 << 3) | (1 << 1) | 0, program[0]);
+ assertEquals(1, program[1]);
+ assertDrop(program, new byte[15], 15);
+
+ // Test jumping if equal to 0.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if not equal to 0.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if registers equal.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0EqualsR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if registers not equal.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test load immediate.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test add.
+ gen = new ApfGenerator();
+ gen.addAdd(1234567890);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test subtract.
+ gen = new ApfGenerator();
+ gen.addAdd(-1234567890);
+ gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test or.
+ gen = new ApfGenerator();
+ gen.addOr(1234567890);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test and.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addAnd(123456789);
+ gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test left shift.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLeftShift(1);
+ gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test right shift.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addRightShift(1);
+ gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test multiply.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addMul(2);
+ gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addDiv(2);
+ gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide by zero.
+ gen = new ApfGenerator();
+ gen.addDiv(0);
+ gen.addJump(gen.DROP_LABEL);
+ assertPass(gen);
+
+ // Test add.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addAddR1();
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test subtract.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, -1234567890);
+ gen.addAddR1();
+ gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test or.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addOrR1();
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test and.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, 123456789);
+ gen.addAndR1();
+ gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test left shift.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLeftShiftR1();
+ gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test right shift.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, -1);
+ gen.addLeftShiftR1();
+ gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test multiply.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, 2);
+ gen.addMulR1();
+ gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, 2);
+ gen.addDivR1();
+ gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide by zero.
+ gen = new ApfGenerator();
+ gen.addDivR1();
+ gen.addJump(gen.DROP_LABEL);
+ assertPass(gen);
+
+ // Test byte load.
+ gen = new ApfGenerator();
+ gen.addLoad8(Register.R0, 1);
+ gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test out of bounds load.
+ gen = new ApfGenerator();
+ gen.addLoad8(Register.R0, 16);
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test half-word load.
+ gen = new ApfGenerator();
+ gen.addLoad16(Register.R0, 1);
+ gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test word load.
+ gen = new ApfGenerator();
+ gen.addLoad32(Register.R0, 1);
+ gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test byte indexed load.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLoad8Indexed(Register.R0, 0);
+ gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test out of bounds indexed load.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 8);
+ gen.addLoad8Indexed(Register.R0, 8);
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test half-word indexed load.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLoad16Indexed(Register.R0, 0);
+ gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test word indexed load.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLoad32Indexed(Register.R0, 0);
+ gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test jumping if greater than.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if less than.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0LessThan(0, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addJumpIfR0LessThan(1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if any bits set.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 3);
+ gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if register greater than.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 2);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if register less than.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if any bits set in register.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 3);
+ gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 3);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 3);
+ gen.addLoadImmediate(Register.R0, 3);
+ gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test load from memory.
+ gen = new ApfGenerator();
+ gen.addLoadFromMemory(Register.R0, 0);
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test store to memory.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addStoreToMemory(Register.R1, 12);
+ gen.addLoadFromMemory(Register.R0, 12);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test filter age pre-filled memory.
+ gen = new ApfGenerator();
+ gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890);
+
+ // Test packet size pre-filled memory.
+ gen = new ApfGenerator();
+ gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
+ gen.addJumpIfR0Equals(MIN_PKT_SIZE, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test IPv4 header size pre-filled memory.
+ gen = new ApfGenerator();
+ gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+ gen.addJumpIfR0Equals(20, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x45}, 0);
+
+ // Test not.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addNot(Register.R0);
+ gen.addJumpIfR0Equals(~1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test negate.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addNeg(Register.R0);
+ gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test move.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addMove(Register.R0);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addMove(Register.R1);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test swap.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addSwap();
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addSwap();
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jump if bytes not equal.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+ program = gen.generate();
+ assertEquals(6, program.length);
+ assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
+ assertEquals(1, program[1]);
+ assertEquals(((20 << 3) | (1 << 1) | 0) - 256, program[2]);
+ assertEquals(1, program[3]);
+ assertEquals(1, program[4]);
+ assertEquals(123, program[5]);
+ assertDrop(program, new byte[MIN_PKT_SIZE], 0);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+ byte[] packet123 = new byte[]{0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
+ assertPass(gen, packet123, 0);
+ gen = new ApfGenerator();
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+ assertDrop(gen, packet123, 0);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
+ byte[] packet12345 = new byte[]{0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
+ assertDrop(gen, packet12345, 0);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
+ assertPass(gen, packet12345, 0);
+ }
+
+ /**
+ * Generate some BPF programs, translate them to APF, then run APF and BPF programs
+ * over packet traces and verify both programs filter out the same packets.
+ */
+ @LargeTest
+ public void testApfAgainstBpf() throws Exception {
+ String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
+ "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
+ "arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000",
+ "tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" };
+ String pcap_filename = stageFile(R.raw.apf);
+ for (String tcpdump_filter : tcpdump_filters) {
+ byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter));
+ assertTrue("Failed to match for filter: " + tcpdump_filter,
+ compareBpfApf(tcpdump_filter, pcap_filename, apf_program));
+ }
+ }
+
+ private class MockIpManagerCallback extends IpManager.Callback {
+ private final ConditionVariable mGotApfProgram = new ConditionVariable();
+ private byte[] mLastApfProgram;
+
+ @Override
+ public void installPacketFilter(byte[] filter) {
+ mLastApfProgram = filter;
+ mGotApfProgram.open();
+ }
+
+ public void resetApfProgramWait() {
+ mGotApfProgram.close();
+ }
+
+ public byte[] getApfProgram() {
+ assertTrue(mGotApfProgram.block(TIMEOUT_MS));
+ return mLastApfProgram;
+ }
+ }
+
+ private static class TestApfFilter extends ApfFilter {
+ public final static byte[] MOCK_MAC_ADDR = new byte[]{1,2,3,4,5,6};
+ private FileDescriptor mWriteSocket;
+
+ public TestApfFilter(IpManager.Callback ipManagerCallback, boolean multicastFilter) throws
+ Exception {
+ super(new ApfCapabilities(2, 1000, ARPHRD_ETHER), NetworkInterface.getByName("lo"),
+ ipManagerCallback, multicastFilter);
+ }
+
+ // Pretend an RA packet has been received and show it to ApfFilter.
+ public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException {
+ // ApfFilter's ReceiveThread will be waiting to read this.
+ Os.write(mWriteSocket, packet, 0, packet.length);
+ }
+
+ @Override
+ void maybeStartFilter() {
+ mHardwareAddress = MOCK_MAC_ADDR;
+ installNewProgramLocked();
+
+ // Create two sockets, "readSocket" and "mWriteSocket" and connect them together.
+ FileDescriptor readSocket = new FileDescriptor();
+ mWriteSocket = new FileDescriptor();
+ try {
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket);
+ } catch (ErrnoException e) {
+ fail();
+ return;
+ }
+ // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs.
+ // This allows us to pretend RA packets have been recieved via pretendPacketReceived().
+ mReceiveThread = new ReceiveThread(readSocket);
+ mReceiveThread.start();
+ }
+
+ @Override
+ public void shutdown() {
+ super.shutdown();
+ IoUtils.closeQuietly(mWriteSocket);
+ }
+ }
+
+ private static final int ETH_HEADER_LEN = 14;
+ private static final int ETH_ETHERTYPE_OFFSET = 12;
+ private static final byte[] ETH_BROADCAST_MAC_ADDRESS = new byte[]{
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
+
+ private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0;
+ private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
+ private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
+
+ private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
+ private static final int IPV6_HEADER_LEN = 40;
+ private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
+ // The IPv6 all nodes address ff02::1
+ private static final byte[] IPV6_ALL_NODES_ADDRESS =
+ new byte[]{ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+
+ private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
+ private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
+ private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
+
+ private static final int ICMP6_RA_HEADER_LEN = 16;
+ private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
+ private static final int ICMP6_RA_CHECKSUM_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
+ private static final int ICMP6_RA_OPTION_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
+
+ private static final int ICMP6_PREFIX_OPTION_TYPE = 3;
+ private static final int ICMP6_PREFIX_OPTION_LEN = 32;
+ private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4;
+ private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
+
+ // From RFC6106: Recursive DNS Server option
+ private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
+ // From RFC6106: DNS Search List option
+ private static final int ICMP6_DNSSL_OPTION_TYPE = 31;
+
+ // From RFC4191: Route Information option
+ private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
+ // Above three options all have the same format:
+ private static final int ICMP6_4_BYTE_OPTION_LEN = 8;
+ private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
+ private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;
+
+ private static final int UDP_HEADER_LEN = 8;
+ private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 22;
+
+ private static final int DHCP_CLIENT_PORT = 68;
+ private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48;
+
+ private static int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
+ private static final byte[] ARP_IPV4_REQUEST_HEADER = new byte[]{
+ 0, 1, // Hardware type: Ethernet (1)
+ 8, 0, // Protocol type: IP (0x0800)
+ 6, // Hardware size: 6
+ 4, // Protocol size: 4
+ 0, 1 // Opcode: request (1)
+ };
+ private static int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24;
+
+ private static byte[] MOCK_IPV4_ADDR = new byte[]{10, 0, 0, 1};
+
+ @LargeTest
+ public void testApfFilterIPv4() throws Exception {
+ MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+ ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+ byte[] program = ipManagerCallback.getApfProgram();
+
+ // Verify empty packet of 100 zero bytes is passed
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ assertPass(program, packet.array(), 0);
+
+ // Verify unicast IPv4 packet is passed
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ assertPass(program, packet.array(), 0);
+
+ // Verify broadcast IPv4, not DHCP to us, is dropped
+ packet.put(ETH_BROADCAST_MAC_ADDRESS);
+ assertDrop(program, packet.array(), 0);
+ packet.put(IPV4_VERSION_IHL_OFFSET, (byte)0x45);
+ assertDrop(program, packet.array(), 0);
+ packet.put(IPV4_PROTOCOL_OFFSET, (byte)IPPROTO_UDP);
+ assertDrop(program, packet.array(), 0);
+ packet.putShort(UDP_DESTINATION_PORT_OFFSET, (short)DHCP_CLIENT_PORT);
+ assertDrop(program, packet.array(), 0);
+
+ // Verify broadcast IPv4 DHCP to us is passed
+ packet.position(DHCP_CLIENT_MAC_OFFSET);
+ packet.put(TestApfFilter.MOCK_MAC_ADDR);
+ assertPass(program, packet.array(), 0);
+
+ apfFilter.shutdown();
+ }
+
+ @LargeTest
+ public void testApfFilterIPv6() throws Exception {
+ MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+ ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+ byte[] program = ipManagerCallback.getApfProgram();
+
+ // Verify empty IPv6 packet is passed
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ assertPass(program, packet.array(), 0);
+
+ // Verify empty ICMPv6 packet is passed
+ packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+ assertPass(program, packet.array(), 0);
+
+ // Verify empty ICMPv6 NA packet is passed
+ packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_NEIGHBOR_ANNOUNCEMENT);
+ assertPass(program, packet.array(), 0);
+
+ // Verify ICMPv6 NA to ff02::1 is dropped
+ packet.position(IPV6_DEST_ADDR_OFFSET);
+ packet.put(IPV6_ALL_NODES_ADDRESS);
+ assertDrop(program, packet.array(), 0);
+
+ apfFilter.shutdown();
+ }
+
+ @LargeTest
+ public void testApfFilterIPv4Multicast() throws Exception {
+ MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+ ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+ byte[] program = ipManagerCallback.getApfProgram();
+
+ // Verify initially disabled multicast filter is off
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ packet.position(IPV4_DEST_ADDR_OFFSET);
+ packet.put(new byte[]{(byte)224,0,0,1});
+ assertPass(program, packet.array(), 0);
+
+ // Turn on multicast filter and verify it works
+ ipManagerCallback.resetApfProgramWait();
+ apfFilter.setMulticastFilter(true);
+ program = ipManagerCallback.getApfProgram();
+ assertDrop(program, packet.array(), 0);
+
+ // Turn off multicast filter and verify it's off
+ ipManagerCallback.resetApfProgramWait();
+ apfFilter.setMulticastFilter(false);
+ program = ipManagerCallback.getApfProgram();
+ assertPass(program, packet.array(), 0);
+
+ // Verify it can be initialized to on
+ ipManagerCallback.resetApfProgramWait();
+ apfFilter.shutdown();
+ apfFilter = new TestApfFilter(ipManagerCallback, true /* multicastFilter */);
+ program = ipManagerCallback.getApfProgram();
+ assertDrop(program, packet.array(), 0);
+
+ apfFilter.shutdown();
+ }
+
+ private void verifyArpFilter(MockIpManagerCallback ipManagerCallback, ApfFilter apfFilter,
+ LinkProperties linkProperties, int filterResult) {
+ ipManagerCallback.resetApfProgramWait();
+ apfFilter.setLinkProperties(linkProperties);
+ byte[] program = ipManagerCallback.getApfProgram();
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+ assertPass(program, packet.array(), 0);
+ packet.position(ARP_HEADER_OFFSET);
+ packet.put(ARP_IPV4_REQUEST_HEADER);
+ assertVerdict(filterResult, program, packet.array(), 0);
+ packet.position(ARP_TARGET_IP_ADDRESS_OFFSET);
+ packet.put(MOCK_IPV4_ADDR);
+ assertPass(program, packet.array(), 0);
+ }
+
+ @LargeTest
+ public void testApfFilterArp() throws Exception {
+ MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+ ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+ byte[] program = ipManagerCallback.getApfProgram();
+
+ // Verify initially ARP filter is off
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+ assertPass(program, packet.array(), 0);
+ packet.position(ARP_HEADER_OFFSET);
+ packet.put(ARP_IPV4_REQUEST_HEADER);
+ assertPass(program, packet.array(), 0);
+ packet.position(ARP_TARGET_IP_ADDRESS_OFFSET);
+ packet.put(MOCK_IPV4_ADDR);
+ assertPass(program, packet.array(), 0);
+
+ // Inform ApfFilter of our address and verify ARP filtering is on
+ LinkProperties lp = new LinkProperties();
+ assertTrue(lp.addLinkAddress(
+ new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24)));
+ verifyArpFilter(ipManagerCallback, apfFilter, lp, DROP);
+
+ // Inform ApfFilter of loss of IP and verify ARP filtering is off
+ verifyArpFilter(ipManagerCallback, apfFilter, new LinkProperties(), PASS);
+
+ apfFilter.shutdown();
+ }
+
+ // Verify that the last program pushed to the IpManager.Callback properly filters the
+ // given packet for the given lifetime.
+ private void verifyRaLifetime(MockIpManagerCallback ipManagerCallback, ByteBuffer packet,
+ int lifetime) {
+ byte[] program = ipManagerCallback.getApfProgram();
+
+ // Verify new program should drop RA for 1/6th its lifetime
+ assertDrop(program, packet.array(), 0);
+ assertDrop(program, packet.array(), lifetime/6);
+ assertPass(program, packet.array(), lifetime/6 + 1);
+ assertPass(program, packet.array(), lifetime);
+
+ // Verify RA checksum is ignored
+ packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)12345);
+ assertDrop(program, packet.array(), 0);
+ packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)-12345);
+ assertDrop(program, packet.array(), 0);
+
+ // Verify other changes to RA make it not match filter
+ packet.put(0, (byte)-1);
+ assertPass(program, packet.array(), 0);
+ packet.put(0, (byte)0);
+ assertDrop(program, packet.array(), 0);
+ }
+
+ // Test that when ApfFilter is shown the given packet, it generates a program to filter it
+ // for the given lifetime.
+ private void testRaLifetime(TestApfFilter apfFilter, MockIpManagerCallback ipManagerCallback,
+ ByteBuffer packet, int lifetime) throws IOException, ErrnoException {
+ // Verify new program generated if ApfFilter witnesses RA
+ ipManagerCallback.resetApfProgramWait();
+ apfFilter.pretendPacketReceived(packet.array());
+ ipManagerCallback.getApfProgram();
+
+ verifyRaLifetime(ipManagerCallback, packet, lifetime);
+ }
+
+ @LargeTest
+ public void testApfFilterRa() throws Exception {
+ MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+ TestApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+ byte[] program = ipManagerCallback.getApfProgram();
+
+ // Verify RA is passed the first time
+ ByteBuffer basePacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
+ basePacket.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ basePacket.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+ basePacket.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_ADVERTISEMENT);
+ basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short)1000);
+ assertPass(program, basePacket.array(), 0);
+
+ testRaLifetime(apfFilter, ipManagerCallback, basePacket, 1000);
+
+ // Generate several RAs with different options and lifetimes, and verify when
+ // ApfFilter is shown these packets, it generates programs to filter them for the
+ // appropriate lifetime.
+ ByteBuffer prefixOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_LEN]);
+ basePacket.clear();
+ prefixOptionPacket.put(basePacket);
+ prefixOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
+ prefixOptionPacket.put((byte)(ICMP6_PREFIX_OPTION_LEN / 8));
+ prefixOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET, 100);
+ prefixOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET, 200);
+ testRaLifetime(apfFilter, ipManagerCallback, prefixOptionPacket, 100);
+
+ ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+ basePacket.clear();
+ rdnssOptionPacket.put(basePacket);
+ rdnssOptionPacket.put((byte)ICMP6_RDNSS_OPTION_TYPE);
+ rdnssOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+ rdnssOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, 300);
+ testRaLifetime(apfFilter, ipManagerCallback, rdnssOptionPacket, 300);
+
+ ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+ basePacket.clear();
+ routeInfoOptionPacket.put(basePacket);
+ routeInfoOptionPacket.put((byte)ICMP6_ROUTE_INFO_OPTION_TYPE);
+ routeInfoOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+ routeInfoOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, 400);
+ testRaLifetime(apfFilter, ipManagerCallback, routeInfoOptionPacket, 400);
+
+ ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+ basePacket.clear();
+ dnsslOptionPacket.put(basePacket);
+ dnsslOptionPacket.put((byte)ICMP6_DNSSL_OPTION_TYPE);
+ dnsslOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+ dnsslOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, 2000);
+ // Note that lifetime of 2000 will be ignored in favor of shorter
+ // route lifetime of 1000.
+ testRaLifetime(apfFilter, ipManagerCallback, dnsslOptionPacket, 1000);
+
+ // Verify that current program filters all five RAs:
+ verifyRaLifetime(ipManagerCallback, basePacket, 1000);
+ verifyRaLifetime(ipManagerCallback, prefixOptionPacket, 100);
+ verifyRaLifetime(ipManagerCallback, rdnssOptionPacket, 300);
+ verifyRaLifetime(ipManagerCallback, routeInfoOptionPacket, 400);
+ verifyRaLifetime(ipManagerCallback, dnsslOptionPacket, 1000);
+
+ apfFilter.shutdown();
+ }
+
+ /**
+ * Stage a file for testing, i.e. make it native accessible. Given a resource ID,
+ * copy that resource into the app's data directory and return the path to it.
+ */
+ private String stageFile(int rawId) throws Exception {
+ File file = new File(getContext().getFilesDir(), "staged_file");
+ new File(file.getParent()).mkdirs();
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = getContext().getResources().openRawResource(rawId);
+ out = new FileOutputStream(file);
+ Streams.copy(in, out);
+ } finally {
+ if (in != null) in.close();
+ if (out != null) out.close();
+ }
+ return file.getAbsolutePath();
+ }
+
+ /**
+ * Call the APF interpreter the run {@code program} on {@code packet} pretending the
+ * filter was installed {@code filter_age} seconds ago.
+ */
+ private native static int apfSimulate(byte[] program, byte[] packet, int filter_age);
+
+ /**
+ * Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF
+ * prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d".
+ */
+ private native static String compileToBpf(String filter);
+
+ /**
+ * Open packet capture file {@code pcap_filename} and filter the packets using tcpdump
+ * human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and
+ * at the same time using APF program {@code apf_program}. Return {@code true} if
+ * both APF and BPF programs filter out exactly the same packets.
+ */
+ private native static boolean compareBpfApf(String filter, String pcap_filename,
+ byte[] apf_program);
+}
diff --git a/services/tests/servicestests/src/com/android/server/Bpf2Apf.java b/services/tests/servicestests/src/android/net/apf/Bpf2Apf.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/Bpf2Apf.java
rename to services/tests/servicestests/src/android/net/apf/Bpf2Apf.java
index 29594a8..220e54d 100644
--- a/services/tests/servicestests/src/com/android/server/Bpf2Apf.java
+++ b/services/tests/servicestests/src/android/net/apf/Bpf2Apf.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server;
+package android.net.apf;
import android.net.apf.ApfGenerator;
import android.net.apf.ApfGenerator.IllegalInstructionException;
@@ -31,9 +31,9 @@
*
* Example usage:
* javac net/java/android/net/apf/ApfGenerator.java \
- * tests/servicestests/src/com/android/server/Bpf2Apf.java
+ * tests/servicestests/src/android/net/apf/Bpf2Apf.java
* sudo tcpdump -i em1 -d icmp | java -classpath tests/servicestests/src:net/java \
- * com.android.server.Bpf2Apf
+ * android.net.apf.Bpf2Apf
*/
public class Bpf2Apf {
private static int parseImm(String line, String arg) {
diff --git a/services/tests/servicestests/src/com/android/server/ApfTest.java b/services/tests/servicestests/src/com/android/server/ApfTest.java
deleted file mode 100644
index 9ba27cb..0000000
--- a/services/tests/servicestests/src/com/android/server/ApfTest.java
+++ /dev/null
@@ -1,560 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-
-import com.android.frameworks.servicestests.R;
-import android.net.apf.ApfGenerator;
-import android.net.apf.ApfGenerator.IllegalInstructionException;
-import android.net.apf.ApfGenerator.Register;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-/**
- * Tests for APF program generator and interpreter.
- *
- * Build, install and run with:
- * runtest frameworks-services -c com.android.server.ApfTest
- */
-public class ApfTest extends AndroidTestCase {
- @Override
- public void setUp() throws Exception {
- super.setUp();
- // Load up native shared library containing APF interpreter exposed via JNI.
- System.loadLibrary("servicestestsjni");
- }
-
- // Expected return codes from APF interpreter.
- private final static int PASS = 1;
- private final static int DROP = 0;
- // Interpreter will just accept packets without link layer headers, so pad fake packet to at
- // least the minimum packet size.
- private final static int MIN_PKT_SIZE = 15;
-
- private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) {
- assertEquals(expected, apfSimulate(program, packet, filterAge));
- }
-
- private void assertPass(byte[] program, byte[] packet, int filterAge) {
- assertVerdict(PASS, program, packet, filterAge);
- }
-
- private void assertDrop(byte[] program, byte[] packet, int filterAge) {
- assertVerdict(DROP, program, packet, filterAge);
- }
-
- private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge)
- throws IllegalInstructionException {
- assertEquals(expected, apfSimulate(gen.generate(), packet, filterAge));
- }
-
- private void assertPass(ApfGenerator gen, byte[] packet, int filterAge)
- throws IllegalInstructionException {
- assertVerdict(PASS, gen, packet, filterAge);
- }
-
- private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge)
- throws IllegalInstructionException {
- assertVerdict(DROP, gen, packet, filterAge);
- }
-
- private void assertPass(ApfGenerator gen)
- throws IllegalInstructionException {
- assertVerdict(PASS, gen, new byte[MIN_PKT_SIZE], 0);
- }
-
- private void assertDrop(ApfGenerator gen)
- throws IllegalInstructionException {
- assertVerdict(DROP, gen, new byte[MIN_PKT_SIZE], 0);
- }
-
- /**
- * Test each instruction by generating a program containing the instruction,
- * generating bytecode for that program and running it through the
- * interpreter to verify it functions correctly.
- */
- @LargeTest
- public void testApfInstructions() throws IllegalInstructionException {
- // Empty program should pass because having the program counter reach the
- // location immediately after the program indicates the packet should be
- // passed to the AP.
- ApfGenerator gen = new ApfGenerator();
- assertPass(gen);
-
- // Test jumping to pass label.
- gen = new ApfGenerator();
- gen.addJump(gen.PASS_LABEL);
- byte[] program = gen.generate();
- assertEquals(1, program.length);
- assertEquals((14 << 3) | (0 << 1) | 0, program[0]);
- assertPass(program, new byte[MIN_PKT_SIZE], 0);
-
- // Test jumping to drop label.
- gen = new ApfGenerator();
- gen.addJump(gen.DROP_LABEL);
- program = gen.generate();
- assertEquals(2, program.length);
- assertEquals((14 << 3) | (1 << 1) | 0, program[0]);
- assertEquals(1, program[1]);
- assertDrop(program, new byte[15], 15);
-
- // Test jumping if equal to 0.
- gen = new ApfGenerator();
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if not equal to 0.
- gen = new ApfGenerator();
- gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if registers equal.
- gen = new ApfGenerator();
- gen.addJumpIfR0EqualsR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if registers not equal.
- gen = new ApfGenerator();
- gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test load immediate.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test add.
- gen = new ApfGenerator();
- gen.addAdd(1234567890);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test subtract.
- gen = new ApfGenerator();
- gen.addAdd(-1234567890);
- gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test or.
- gen = new ApfGenerator();
- gen.addOr(1234567890);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test and.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addAnd(123456789);
- gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test left shift.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLeftShift(1);
- gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test right shift.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addRightShift(1);
- gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test multiply.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addMul(2);
- gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test divide.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addDiv(2);
- gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test divide by zero.
- gen = new ApfGenerator();
- gen.addDiv(0);
- gen.addJump(gen.DROP_LABEL);
- assertPass(gen);
-
- // Test add.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addAddR1();
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test subtract.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, -1234567890);
- gen.addAddR1();
- gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test or.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addOrR1();
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test and.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLoadImmediate(Register.R1, 123456789);
- gen.addAndR1();
- gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test left shift.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLoadImmediate(Register.R1, 1);
- gen.addLeftShiftR1();
- gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test right shift.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLoadImmediate(Register.R1, -1);
- gen.addLeftShiftR1();
- gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test multiply.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLoadImmediate(Register.R1, 2);
- gen.addMulR1();
- gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test divide.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLoadImmediate(Register.R1, 2);
- gen.addDivR1();
- gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test divide by zero.
- gen = new ApfGenerator();
- gen.addDivR1();
- gen.addJump(gen.DROP_LABEL);
- assertPass(gen);
-
- // Test byte load.
- gen = new ApfGenerator();
- gen.addLoad8(Register.R0, 1);
- gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test out of bounds load.
- gen = new ApfGenerator();
- gen.addLoad8(Register.R0, 16);
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test half-word load.
- gen = new ApfGenerator();
- gen.addLoad16(Register.R0, 1);
- gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test word load.
- gen = new ApfGenerator();
- gen.addLoad32(Register.R0, 1);
- gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test byte indexed load.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1);
- gen.addLoad8Indexed(Register.R0, 0);
- gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test out of bounds indexed load.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 8);
- gen.addLoad8Indexed(Register.R0, 8);
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test half-word indexed load.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1);
- gen.addLoad16Indexed(Register.R0, 0);
- gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test word indexed load.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1);
- gen.addLoad32Indexed(Register.R0, 0);
- gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test jumping if greater than.
- gen = new ApfGenerator();
- gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if less than.
- gen = new ApfGenerator();
- gen.addJumpIfR0LessThan(0, gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addJumpIfR0LessThan(1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if any bits set.
- gen = new ApfGenerator();
- gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
- assertDrop(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 3);
- gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if register greater than.
- gen = new ApfGenerator();
- gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 2);
- gen.addLoadImmediate(Register.R1, 1);
- gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if register less than.
- gen = new ApfGenerator();
- gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1);
- gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if any bits set in register.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 3);
- gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 3);
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
- assertDrop(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 3);
- gen.addLoadImmediate(Register.R0, 3);
- gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test load from memory.
- gen = new ApfGenerator();
- gen.addLoadFromMemory(Register.R0, 0);
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test store to memory.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addStoreToMemory(Register.R1, 12);
- gen.addLoadFromMemory(Register.R0, 12);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test filter age pre-filled memory.
- gen = new ApfGenerator();
- gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890);
-
- // Test packet size pre-filled memory.
- gen = new ApfGenerator();
- gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
- gen.addJumpIfR0Equals(MIN_PKT_SIZE, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test IPv4 header size pre-filled memory.
- gen = new ApfGenerator();
- gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
- gen.addJumpIfR0Equals(20, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x45}, 0);
-
- // Test not.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addNot(Register.R0);
- gen.addJumpIfR0Equals(~1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test negate.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addNeg(Register.R0);
- gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test move.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addMove(Register.R0);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addMove(Register.R1);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test swap.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addSwap();
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addSwap();
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jump if bytes not equal.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
- program = gen.generate();
- assertEquals(6, program.length);
- assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
- assertEquals(1, program[1]);
- assertEquals(((20 << 3) | (1 << 1) | 0) - 256, program[2]);
- assertEquals(1, program[3]);
- assertEquals(1, program[4]);
- assertEquals(123, program[5]);
- assertDrop(program, new byte[MIN_PKT_SIZE], 0);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
- byte[] packet123 = new byte[]{0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
- assertPass(gen, packet123, 0);
- gen = new ApfGenerator();
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
- assertDrop(gen, packet123, 0);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
- byte[] packet12345 = new byte[]{0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
- assertDrop(gen, packet12345, 0);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
- assertPass(gen, packet12345, 0);
- }
-
- /**
- * Generate some BPF programs, translate them to APF, then run APF and BPF programs
- * over packet traces and verify both programs filter out the same packets.
- */
- @LargeTest
- public void testApfAgainstBpf() throws Exception {
- String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
- "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
- "arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000",
- "tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" };
- String pcap_filename = stageFile(R.raw.apf);
- for (String tcpdump_filter : tcpdump_filters) {
- byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter));
- assertTrue("Failed to match for filter: " + tcpdump_filter,
- compareBpfApf(tcpdump_filter, pcap_filename, apf_program));
- }
- }
-
- /**
- * Stage a file for testing, i.e. make it native accessible. Given a resource ID,
- * copy that resource into the app's data directory and return the path to it.
- */
- private String stageFile(int rawId) throws Exception {
- File file = new File(getContext().getFilesDir(), "staged_file");
- new File(file.getParent()).mkdirs();
- InputStream in = null;
- OutputStream out = null;
- try {
- in = getContext().getResources().openRawResource(rawId);
- out = new FileOutputStream(file);
- Streams.copy(in, out);
- } finally {
- if (in != null) in.close();
- if (out != null) out.close();
- }
- return file.getAbsolutePath();
- }
-
- /**
- * Call the APF interpreter the run {@code program} on {@code packet} pretending the
- * filter was installed {@code filter_age} seconds ago.
- */
- private native static int apfSimulate(byte[] program, byte[] packet, int filter_age);
-
- /**
- * Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF
- * prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d".
- */
- private native static String compileToBpf(String filter);
-
- /**
- * Open packet capture file {@code pcap_filename} and filter the packets using tcpdump
- * human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and
- * at the same time using APF program {@code apf_program}. Return {@code true} if
- * both APF and BPF programs filter out exactly the same packets.
- */
- private native static boolean compareBpfApf(String filter, String pcap_filename,
- byte[] apf_program);
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
deleted file mode 100644
index ced7cf0..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
+++ /dev/null
@@ -1,6092 +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.
- */
-package com.android.server.pm;
-
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllDynamic;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllDynamicOrPinned;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIcon;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIconFile;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIconResId;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIntents;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveTitle;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllKeyFieldsOnly;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotHaveIntents;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotHaveTitle;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotKeyFieldsOnly;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllPinned;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllUnique;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertBitmapSize;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertBundleEmpty;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCallbackNotReceived;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCallbackReceived;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertDynamicAndPinned;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertDynamicOnly;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertShortcutIds;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.findShortcut;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.hashSet;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.makeBundle;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.pfdToBitmap;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.resetAll;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.Manifest.permission;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.annotation.UserIdInt;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.IUidObserver;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.ILauncherApps;
-import android.content.pm.LauncherApps;
-import android.content.pm.LauncherApps.ShortcutQuery;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.content.pm.ShortcutInfo;
-import android.content.pm.ShortcutManager;
-import android.content.pm.ShortcutServiceInternal;
-import android.content.pm.Signature;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.FileUtils;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Parcel;
-import android.os.PersistableBundle;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.test.InstrumentationTestCase;
-import android.test.MoreAsserts;
-import android.test.mock.MockContext;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-
-import com.android.frameworks.servicestests.R;
-import com.android.internal.util.Preconditions;
-import com.android.server.LocalServices;
-import com.android.server.SystemService;
-import com.android.server.pm.LauncherAppsService.LauncherAppsImpl;
-import com.android.server.pm.ShortcutService.ConfigConstants;
-import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
-
-import org.junit.Assert;
-import org.mockito.ArgumentCaptor;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.BiPredicate;
-import java.util.function.Consumer;
-
-/**
- * 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
-
- * TODO: Add checks with assertAllNotHaveIcon()
- * TODO: Detailed test for hasShortcutPermissionInner().
- * TODO: Add tests for the command line functions too.
- */
-@SmallTest
-public class ShortcutManagerTest extends InstrumentationTestCase {
- 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 = false; // DO NOT SUBMIT WITH true
-
- private static final boolean DUMP_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true
-
- // public for mockito
- public class BaseContext extends MockContext {
- @Override
- public Object getSystemService(String name) {
- switch (name) {
- case Context.USER_SERVICE:
- return mMockUserManager;
- }
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getSystemServiceName(Class<?> serviceClass) {
- return getTestContext().getSystemServiceName(serviceClass);
- }
-
- @Override
- public PackageManager getPackageManager() {
- return mMockPackageManager;
- }
-
- @Override
- public Resources getResources() {
- return getTestContext().getResources();
- }
-
- @Override
- public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
- IntentFilter filter, String broadcastPermission, Handler scheduler) {
- // ignore.
- return null;
- }
- }
-
- /** Context used in the client side */
- public class ClientContext extends BaseContext {
- @Override
- public String getPackageName() {
- return mInjectedClientPackage;
- }
-
- @Override
- public int getUserId() {
- return getCallingUserId();
- }
- }
-
- /** Context used in the service side */
- public class ServiceContext extends BaseContext {
- long injectClearCallingIdentity() {
- final int prevCallingUid = mInjectedCallingUid;
- mInjectedCallingUid = Process.SYSTEM_UID;
- return prevCallingUid;
- }
-
- void injectRestoreCallingIdentity(long token) {
- mInjectedCallingUid = (int) token;
- }
-
- @Override
- public void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options,
- UserHandle userId) {
- }
-
- @Override
- public int getUserId() {
- return UserHandle.USER_SYSTEM;
- }
- }
-
- /** ShortcutService with injection override methods. */
- private final class ShortcutServiceTestable extends ShortcutService {
- final ServiceContext mContext;
- IUidObserver mUidObserver;
-
- public ShortcutServiceTestable(ServiceContext context, Looper looper) {
- super(context, looper);
- mContext = context;
- }
-
- @Override
- String injectShortcutManagerConstants() {
- return ConfigConstants.KEY_RESET_INTERVAL_SEC + "=" + (INTERVAL / 1000) + ","
- + ConfigConstants.KEY_MAX_SHORTCUTS + "=" + MAX_SHORTCUTS + ","
- + ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "="
- + MAX_UPDATES_PER_INTERVAL + ","
- + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=" + MAX_ICON_DIMENSION + ","
- + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "="
- + MAX_ICON_DIMENSION_LOWRAM + ","
- + ConfigConstants.KEY_ICON_FORMAT + "=PNG,"
- + ConfigConstants.KEY_ICON_QUALITY + "=100";
- }
-
- @Override
- long injectClearCallingIdentity() {
- return mContext.injectClearCallingIdentity();
- }
-
- @Override
- void injectRestoreCallingIdentity(long token) {
- mContext.injectRestoreCallingIdentity(token);
- }
-
- @Override
- int injectDipToPixel(int dip) {
- return dip;
- }
-
- @Override
- long injectCurrentTimeMillis() {
- return mInjectedCurrentTimeLillis;
- }
-
- @Override
- long injectElapsedRealtime() {
- // TODO This should be kept separately from mInjectedCurrentTimeLillis, since
- // this should increase even if we rewind mInjectedCurrentTimeLillis in some tests.
- return mInjectedCurrentTimeLillis - START_TIME;
- }
-
- @Override
- int injectBinderCallingUid() {
- return mInjectedCallingUid;
- }
-
- @Override
- int injectGetPackageUid(String packageName, int userId) {
- return getInjectedPackageInfo(packageName, userId, false).applicationInfo.uid;
- }
-
- @Override
- File injectSystemDataPath() {
- return new File(mInjectedFilePathRoot, "system");
- }
-
- @Override
- File injectUserDataPath(@UserIdInt int userId) {
- return new File(mInjectedFilePathRoot, "user-" + userId);
- }
-
- @Override
- void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
- // Can't check
- }
-
- @Override
- boolean injectIsLowRamDevice() {
- return mInjectedIsLowRamDevice;
- }
-
- @Override
- void injectRegisterUidObserver(IUidObserver observer, int which) {
- mUidObserver = observer;
- }
-
- @Override
- PackageManagerInternal injectPackageManagerInternal() {
- return mMockPackageManagerInternal;
- }
-
- @Override
- boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
- return mDefaultLauncherChecker.test(callingPackage, userId);
- }
-
- @Override
- PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
- boolean getSignatures) {
- return getInjectedPackageInfo(packageName, userId, getSignatures);
- }
-
- @Override
- ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
- PackageInfo pi = injectPackageInfo(packageName, userId, /* getSignatures= */ false);
- return pi != null ? pi.applicationInfo : null;
- }
-
- @Override
- void postToHandler(Runnable r) {
- final long token = mContext.injectClearCallingIdentity();
- r.run();
- mContext.injectRestoreCallingIdentity(token);
- }
-
- @Override
- void injectEnforceCallingPermission(String permission, String message) {
- if (!mCallerPermissions.contains(permission)) {
- throw new SecurityException("Missing permission: " + permission);
- }
- }
-
- @Override
- void wtf(String message, Exception e) {
- // During tests, WTF is fatal.
- fail(message + " exception: " + e);
- }
- }
-
- /** ShortcutManager with injection override methods. */
- private class ShortcutManagerTestable extends ShortcutManager {
- public ShortcutManagerTestable(Context context, ShortcutServiceTestable service) {
- super(context, service);
- }
-
- @Override
- protected int injectMyUserId() {
- return UserHandle.getUserId(mInjectedCallingUid);
- }
- }
-
- private class LauncherAppImplTestable extends LauncherAppsImpl {
- final ServiceContext mContext;
-
- public LauncherAppImplTestable(ServiceContext context) {
- super(context);
- mContext = context;
- }
-
- @Override
- public void verifyCallingPackage(String callingPackage) {
- // SKIP
- }
-
- @Override
- void postToPackageMonitorHandler(Runnable r) {
- final long token = mContext.injectClearCallingIdentity();
- r.run();
- mContext.injectRestoreCallingIdentity(token);
- }
-
- @Override
- int injectBinderCallingUid() {
- return mInjectedCallingUid;
- }
-
- @Override
- long injectClearCallingIdentity() {
- final int prevCallingUid = mInjectedCallingUid;
- mInjectedCallingUid = Process.SYSTEM_UID;
- return prevCallingUid;
- }
-
- @Override
- void injectRestoreCallingIdentity(long token) {
- mInjectedCallingUid = (int) token;
- }
- }
-
- private class LauncherAppsTestable extends LauncherApps {
- public LauncherAppsTestable(Context context, ILauncherApps service) {
- super(context, service);
- }
- }
-
- 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 LauncherAppImplTestable mLauncherAppImpl;
-
- // LauncherApps has per-instace state, so we need a differnt instance for each launcher.
- private final Map<Pair<Integer, String>, LauncherAppsTestable>
- mLauncherAppsMap = new HashMap<>();
- private LauncherAppsTestable mLauncherApps; // Current one
-
- private File mInjectedFilePathRoot;
-
- private long mInjectedCurrentTimeLillis;
-
- private boolean mInjectedIsLowRamDevice;
-
- private int mInjectedCallingUid;
- private String mInjectedClientPackage;
-
- private Map<String, PackageInfo> mInjectedPackages;
-
- private Set<PackageWithUser> mUninstalledPackages;
-
- private PackageManager mMockPackageManager;
- private PackageManagerInternal mMockPackageManagerInternal;
- private UserManager mMockUserManager;
-
- 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 CALLING_PACKAGE_4 = "com.android.test.4";
- private static final int CALLING_UID_4 = 10004;
-
- 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 String LAUNCHER_3 = "com.android.launcher.3";
- private static final int LAUNCHER_UID_3 = 10013;
-
- private static final String LAUNCHER_4 = "com.android.launcher.4";
- private static final int LAUNCHER_UID_4 = 10014;
-
- private static final int USER_0 = UserHandle.USER_SYSTEM;
- private static final int USER_10 = 10;
- private static final int USER_11 = 11;
- private static final int USER_P0 = 20; // profile of user 0
-
- private static final UserHandle HANDLE_USER_0 = UserHandle.of(USER_0);
- private static final UserHandle HANDLE_USER_10 = UserHandle.of(USER_10);
- private static final UserHandle HANDLE_USER_11 = UserHandle.of(USER_11);
- private static final UserHandle HANDLE_USER_P0 = UserHandle.of(USER_P0);
-
- private static final UserInfo USER_INFO_0 = withProfileGroupId(
- new UserInfo(USER_0, "user0",
- UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY | UserInfo.FLAG_INITIALIZED), 10);
-
- private static final UserInfo USER_INFO_10 =
- new UserInfo(USER_10, "user10", UserInfo.FLAG_INITIALIZED);
-
- private static final UserInfo USER_INFO_11 =
- new UserInfo(USER_11, "user11", UserInfo.FLAG_INITIALIZED);
-
- private static final UserInfo USER_INFO_P0 = withProfileGroupId(
- new UserInfo(USER_P0, "userP0",
- UserInfo.FLAG_MANAGED_PROFILE), 10);
-
- private BiPredicate<String, Integer> mDefaultLauncherChecker =
- (callingPackage, userId) ->
- LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage)
- || LAUNCHER_3.equals(callingPackage) || LAUNCHER_4.equals(callingPackage);
-
- private static final long START_TIME = 1440000000101L;
-
- private static final long INTERVAL = 10000;
-
- private static final int MAX_SHORTCUTS = 10;
-
- private static final int MAX_UPDATES_PER_INTERVAL = 3;
-
- private static final int MAX_ICON_DIMENSION = 128;
-
- private static final int MAX_ICON_DIMENSION_LOWRAM = 32;
-
- private static final ShortcutQuery QUERY_ALL = new ShortcutQuery();
-
- private final ArrayList<String> mCallerPermissions = new ArrayList<>();
-
- static {
- QUERY_ALL.setQueryFlags(
- ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mServiceContext = spy(new ServiceContext());
- mClientContext = new ClientContext();
-
- mMockPackageManager = mock(PackageManager.class);
- mMockPackageManagerInternal = mock(PackageManagerInternal.class);
- mMockUserManager = mock(UserManager.class);
-
- // Prepare injection values.
-
- mInjectedCurrentTimeLillis = START_TIME;
-
- mInjectedPackages = new HashMap<>();;
- addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1);
- addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 2);
- addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 3);
- addPackage(CALLING_PACKAGE_4, CALLING_UID_4, 10);
- addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4);
- addPackage(LAUNCHER_2, LAUNCHER_UID_2, 5);
- addPackage(LAUNCHER_3, LAUNCHER_UID_3, 6);
- addPackage(LAUNCHER_4, LAUNCHER_UID_4, 10);
-
- // CALLING_PACKAGE_3 / LAUNCHER_3 are not backup target.
- updatePackageInfo(CALLING_PACKAGE_3,
- pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
- updatePackageInfo(LAUNCHER_3,
- pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
-
- mUninstalledPackages = new HashSet<>();
-
- mInjectedFilePathRoot = new File(getTestContext().getCacheDir(), "test-files");
-
- deleteAllSavedFiles();
-
- // Set up users.
- doAnswer(inv -> {
- assertSystem();
- return USER_INFO_0;
- }).when(mMockUserManager).getUserInfo(eq(USER_0));
-
- doAnswer(inv -> {
- assertSystem();
- return USER_INFO_10;
- }).when(mMockUserManager).getUserInfo(eq(USER_10));
-
- doAnswer(inv -> {
- assertSystem();
- return USER_INFO_11;
- }).when(mMockUserManager).getUserInfo(eq(USER_11));
-
- doAnswer(inv -> {
- assertSystem();
- return USER_INFO_P0;
- }).when(mMockUserManager).getUserInfo(eq(USER_P0));
-
- // User 0 is always running.
- when(mMockUserManager.isUserRunning(eq(USER_0))).thenReturn(true);
-
- initService();
- setCaller(CALLING_PACKAGE_1);
-
- // In order to complicate the situation, we set mLocaleChangeSequenceNumber to 1 by
- // calling this. Running test with mLocaleChangeSequenceNumber == 0 might make us miss
- // some edge cases.
- mInternal.onSystemLocaleChangedNoLock();
- }
-
- private static UserInfo withProfileGroupId(UserInfo in, int groupId) {
- in.profileGroupId = groupId;
- return in;
- }
-
- @Override
- protected void tearDown() throws Exception {
- if (DUMP_IN_TEARDOWN) dumpsysOnLogcat("Teardown");
-
- shutdownServices();
-
- super.tearDown();
- }
-
- private Context getTestContext() {
- return getInstrumentation().getContext();
- }
-
- private void deleteAllSavedFiles() {
- // Empty the data directory.
- if (mInjectedFilePathRoot.exists()) {
- Assert.assertTrue("failed to delete dir",
- FileUtils.deleteContents(mInjectedFilePathRoot));
- }
- mInjectedFilePathRoot.mkdirs();
- }
-
- /** (Re-) init the manager and the service. */
- private void initService() {
- shutdownServices();
-
- LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
-
- // Instantiate targets.
- mService = new ShortcutServiceTestable(mServiceContext, Looper.getMainLooper());
- mManager = new ShortcutManagerTestable(mClientContext, mService);
-
- mInternal = LocalServices.getService(ShortcutServiceInternal.class);
-
- mLauncherAppImpl = new LauncherAppImplTestable(mServiceContext);
- mLauncherApps = null;
- mLauncherAppsMap.clear();
-
- // Load the setting file.
- mService.onBootPhase(SystemService.PHASE_LOCK_SETTINGS_READY);
- }
-
- private void shutdownServices() {
- if (mService != null) {
- // Flush all the unsaved data from the previous instance.
- mService.saveDirtyInfo();
- }
- LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
-
- mService = null;
- mManager = null;
- mInternal = null;
- mLauncherAppImpl = null;
- mLauncherApps = null;
- mLauncherAppsMap.clear();
- }
-
- private void addPackage(String packageName, int uid, int version) {
- addPackage(packageName, uid, version, packageName);
- }
-
- private Signature[] genSignatures(String... signatures) {
- final Signature[] sigs = new Signature[signatures.length];
- for (int i = 0; i < signatures.length; i++){
- sigs[i] = new Signature(signatures[i].getBytes());
- }
- return sigs;
- }
-
- private PackageInfo genPackage(String packageName, int uid, int version, String... signatures) {
- final PackageInfo pi = new PackageInfo();
- pi.packageName = packageName;
- pi.applicationInfo = new ApplicationInfo();
- pi.applicationInfo.uid = uid;
- pi.applicationInfo.flags = ApplicationInfo.FLAG_INSTALLED
- | ApplicationInfo.FLAG_ALLOW_BACKUP;
- pi.versionCode = version;
- pi.applicationInfo.versionCode = version;
- pi.signatures = genSignatures(signatures);
-
- return pi;
- }
-
- private void addPackage(String packageName, int uid, int version, String... signatures) {
- mInjectedPackages.put(packageName, genPackage(packageName, uid, version, signatures));
- }
-
- private void updatePackageInfo(String packageName, Consumer<PackageInfo> c) {
- c.accept(mInjectedPackages.get(packageName));
- }
-
- private void updatePackageVersion(String packageName, int increment) {
- updatePackageInfo(packageName, pi -> {
- pi.versionCode += increment;
- pi.applicationInfo.versionCode += increment;
- });
- }
-
- private void uninstallPackage(int userId, String packageName) {
- if (ENABLE_DUMP) {
- Log.i(TAG, "Unnstall package " + packageName + " / " + userId);
- }
- mUninstalledPackages.add(PackageWithUser.of(userId, packageName));
- }
-
- private void installPackage(int userId, String packageName) {
- if (ENABLE_DUMP) {
- Log.i(TAG, "Install package " + packageName + " / " + userId);
- }
- mUninstalledPackages.remove(PackageWithUser.of(userId, packageName));
- }
-
- PackageInfo getInjectedPackageInfo(String packageName, @UserIdInt int userId,
- boolean getSignatures) {
- final PackageInfo pi = mInjectedPackages.get(packageName);
- if (pi == null) return null;
-
- final PackageInfo ret = new PackageInfo();
- ret.packageName = pi.packageName;
- ret.versionCode = pi.versionCode;
- ret.applicationInfo = new ApplicationInfo(pi.applicationInfo);
- ret.applicationInfo.uid = UserHandle.getUid(userId, pi.applicationInfo.uid);
- if (mUninstalledPackages.contains(PackageWithUser.of(userId, packageName))) {
- ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
- }
-
- if (getSignatures) {
- ret.signatures = pi.signatures;
- }
-
- return ret;
- }
-
- /** Replace the current calling package */
- private void setCaller(String packageName, int userId) {
- mInjectedClientPackage = packageName;
- mInjectedCallingUid =
- Preconditions.checkNotNull(getInjectedPackageInfo(packageName, userId, false),
- "Unknown package").applicationInfo.uid;
-
- // Set up LauncherApps for this caller.
- final Pair<Integer, String> key = Pair.create(userId, packageName);
- if (!mLauncherAppsMap.containsKey(key)) {
- mLauncherAppsMap.put(key, new LauncherAppsTestable(mClientContext, mLauncherAppImpl));
- }
- mLauncherApps = mLauncherAppsMap.get(key);
- }
-
- private void setCaller(String packageName) {
- setCaller(packageName, UserHandle.USER_SYSTEM);
- }
-
- private String getCallingPackage() {
- return mInjectedClientPackage;
- }
-
- private void setDefaultLauncherChecker(BiPredicate<String, Integer> p) {
- mDefaultLauncherChecker = p;
- }
-
- private void runWithCaller(String packageName, int userId, Runnable r) {
- final String previousPackage = mInjectedClientPackage;
- final int previousUserId = UserHandle.getUserId(mInjectedCallingUid);
-
- setCaller(packageName, userId);
-
- r.run();
-
- setCaller(previousPackage, previousUserId);
- }
-
- private int getCallingUserId() {
- return UserHandle.getUserId(mInjectedCallingUid);
- }
-
- private UserHandle getCallingUser() {
- return UserHandle.of(getCallingUserId());
- }
-
- /** For debugging */
- private void dumpsysOnLogcat() {
- dumpsysOnLogcat("");
- }
-
- private void dumpsysOnLogcat(String message) {
- dumpsysOnLogcat(message, false);
- }
-
- private void dumpsysOnLogcat(String message, boolean force) {
- if (force || !ENABLE_DUMP) return;
-
- final ByteArrayOutputStream out = new ByteArrayOutputStream();
- final PrintWriter pw = new PrintWriter(out);
- mService.dumpInner(pw, null);
- pw.close();
-
- Log.e(TAG, "Dumping ShortcutService: " + message);
- for (String line : out.toString().split("\n")) {
- Log.e(TAG, line);
- }
- }
-
- /**
- * For debugging, dump arbitrary file on logcat.
- */
- private void dumpFileOnLogcat(String path) {
- dumpFileOnLogcat(path, "");
- }
-
- private void dumpFileOnLogcat(String path, String message) {
- if (!ENABLE_DUMP) return;
-
- Log.i(TAG, "Dumping file: " + path + " " + message);
- 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() {
- mService.saveDirtyInfo();
- dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath()
- + "/system/" + ShortcutService.FILENAME_BASE_STATE);
- }
-
- /**
- * For debugging, dump per-user state file on logcat.
- */
- private void dumpUserFile(int userId) {
- dumpUserFile(userId, "");
- }
-
- private void dumpUserFile(int userId, String message) {
- mService.saveDirtyInfo();
- dumpFileOnLogcat(mInjectedFilePathRoot.getAbsolutePath()
- + "/user-" + userId
- + "/" + ShortcutService.FILENAME_USER_PACKAGES, message);
- }
-
- private void waitOnMainThread() throws Throwable {
- runTestOnUiThread(() -> {});
- }
-
- /**
- * 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 a shortcut with an ID and icon.
- */
- private ShortcutInfo makeShortcutWithIcon(String id, Icon icon) {
- return makeShortcut(
- id, "Title-" + id, /* activity =*/ null, icon,
- makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0);
- }
-
- private ShortcutInfo makePackageShortcut(String packageName, String id) {
- String origCaller = getCallingPackage();
-
- setCaller(packageName);
- ShortcutInfo s = makeShortcut(
- id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
- makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0);
- setCaller(origCaller); // restore the caller
-
- 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;
- }
-
- private ShortcutInfo.Builder makeShortcutBuilder() {
- return new ShortcutInfo.Builder(mClientContext);
- }
-
- /**
- * 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 assertSystem() {
- assertEquals("Caller must be system", Process.SYSTEM_UID, mInjectedCallingUid);
- }
-
- private void assertResetTimes(long expectedLastResetTime, long expectedNextResetTime) {
- assertEquals(expectedLastResetTime, mService.getLastResetTimeLocked());
- assertEquals(expectedNextResetTime, mService.getNextResetTimeLocked());
- }
-
- public static List<ShortcutInfo> assertAllNotHaveIcon(
- List<ShortcutInfo> actualShortcuts) {
- for (ShortcutInfo s : actualShortcuts) {
- assertNull("ID " + s.getId(), s.getIcon());
- }
- return actualShortcuts;
- }
-
- @NonNull
- private List<ShortcutInfo> assertAllHaveFlags(@NonNull List<ShortcutInfo> actualShortcuts,
- int shortcutFlags) {
- for (ShortcutInfo s : actualShortcuts) {
- assertTrue("ID " + s.getId() + " doesn't have flags " + shortcutFlags,
- s.hasFlags(shortcutFlags));
- }
- return actualShortcuts;
- }
-
- private ShortcutInfo getPackageShortcut(String packageName, String shortcutId, int userId) {
- return mService.getPackageShortcutForTest(packageName, shortcutId, userId);
- }
-
- private void assertShortcutExists(String packageName, String shortcutId, int userId) {
- assertTrue(getPackageShortcut(packageName, shortcutId, userId) != null);
- }
-
- private void assertShortcutNotExists(String packageName, String shortcutId, int userId) {
- assertTrue(getPackageShortcut(packageName, shortcutId, userId) == null);
- }
-
- private Intent launchShortcutAndGetIntent(
- @NonNull String packageName, @NonNull String shortcutId, int userId) {
- reset(mServiceContext);
- assertTrue(mLauncherApps.startShortcut(packageName, shortcutId, null, null,
- UserHandle.of(userId)));
-
- final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mServiceContext).startActivityAsUser(
- intentCaptor.capture(),
- any(Bundle.class),
- eq(UserHandle.of(userId)));
- return intentCaptor.getValue();
- }
-
- private Intent launchShortcutAndGetIntent_withShortcutInfo(
- @NonNull String packageName, @NonNull String shortcutId, int userId) {
- reset(mServiceContext);
-
- assertTrue(mLauncherApps.startShortcut(
- getShortcutInfoAsLauncher(packageName, shortcutId, userId), null, null));
-
- final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mServiceContext).startActivityAsUser(
- intentCaptor.capture(),
- any(Bundle.class),
- eq(UserHandle.of(userId)));
- return intentCaptor.getValue();
- }
-
- private void assertShortcutLaunchable(@NonNull String packageName, @NonNull String shortcutId,
- int userId) {
- assertNotNull(launchShortcutAndGetIntent(packageName, shortcutId, userId));
- assertNotNull(launchShortcutAndGetIntent_withShortcutInfo(packageName, shortcutId, userId));
- }
-
- private void assertShortcutNotLaunchable(@NonNull String packageName,
- @NonNull String shortcutId, int userId) {
- try {
- final boolean ok = mLauncherApps.startShortcut(packageName, shortcutId, null, null,
- UserHandle.of(userId));
- if (!ok) {
- return; // didn't launch, okay.
- }
- fail();
- } catch (SecurityException expected) {
- // security exception is okay too.
- }
- }
-
- private ShortcutInfo getPackageShortcut(String packageName, String shortcutId) {
- return getPackageShortcut(packageName, shortcutId, getCallingUserId());
- }
-
- private ShortcutInfo getCallerShortcut(String shortcutId) {
- return getPackageShortcut(getCallingPackage(), shortcutId, getCallingUserId());
- }
-
- private List<ShortcutInfo> getLauncherShortcuts(String launcher, int userId, int queryFlags) {
- final List<ShortcutInfo>[] ret = new List[1];
- runWithCaller(launcher, userId, () -> {
- final ShortcutQuery q = new ShortcutQuery();
- q.setQueryFlags(queryFlags);
- ret[0] = mLauncherApps.getShortcuts(q, UserHandle.of(userId));
- });
- return ret[0];
- }
-
- private List<ShortcutInfo> getLauncherPinnedShortcuts(String launcher, int userId) {
- return getLauncherShortcuts(launcher, userId, ShortcutQuery.FLAG_GET_PINNED);
- }
-
- private ShortcutInfo getShortcutInfoAsLauncher(String packageName, String shortcutId,
- int userId) {
- final List<ShortcutInfo> infoList =
- mLauncherApps.getShortcutInfo(packageName, list(shortcutId),
- UserHandle.of(userId));
- assertEquals("No shortcutInfo found (or too many of them)", 1, infoList.size());
- return infoList.get(0);
- }
-
- private Intent genPackageDeleteIntent(String pakcageName, int userId) {
- Intent i = new Intent(Intent.ACTION_PACKAGE_REMOVED);
- i.setData(Uri.parse("package:" + pakcageName));
- i.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- return i;
- }
-
- private Intent genPackageUpdateIntent(String pakcageName, int userId) {
- Intent i = new Intent(Intent.ACTION_PACKAGE_ADDED);
- i.setData(Uri.parse("package:" + pakcageName));
- i.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- i.putExtra(Intent.EXTRA_REPLACING, true);
- return i;
- }
- private Intent genPackageDataClear(String packageName, int userId) {
- Intent i = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED);
- i.setData(Uri.parse("package:" + packageName));
- i.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- return i;
- }
-
- private ShortcutInfo parceled(ShortcutInfo si) {
- Parcel p = Parcel.obtain();
- p.writeParcelable(si, 0);
- p.setDataPosition(0);
- ShortcutInfo si2 = p.readParcelable(getClass().getClassLoader());
- p.recycle();
- return si2;
- }
-
- /**
- * Test for the first launch path, no settings file available.
- */
- public void testFirstInitialize() {
- assertResetTimes(START_TIME, START_TIME + INTERVAL);
- }
-
- /**
- * Test for {@link ShortcutService#getLastResetTimeLocked()} and
- * {@link ShortcutService#getNextResetTimeLocked()}.
- */
- 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 hours 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();
-
- mService.saveDirtyInfo();
-
- // 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.
- }
-
- public void testLoadConfig() {
- mService.updateConfigurationLocked(
- ConfigConstants.KEY_RESET_INTERVAL_SEC + "=123,"
- + ConfigConstants.KEY_MAX_SHORTCUTS + "=4,"
- + ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=5,"
- + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=100,"
- + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "=50,"
- + ConfigConstants.KEY_ICON_FORMAT + "=WEBP,"
- + ConfigConstants.KEY_ICON_QUALITY + "=75");
- assertEquals(123000, mService.getResetIntervalForTest());
- assertEquals(4, mService.getMaxDynamicShortcutsForTest());
- assertEquals(5, mService.getMaxUpdatesPerIntervalForTest());
- assertEquals(100, mService.getMaxIconDimensionForTest());
- assertEquals(CompressFormat.WEBP, mService.getIconPersistFormatForTest());
- assertEquals(75, mService.getIconPersistQualityForTest());
-
- mInjectedIsLowRamDevice = true;
- mService.updateConfigurationLocked(
- ConfigConstants.KEY_MAX_ICON_DIMENSION_DP + "=100,"
- + ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM + "=50,"
- + ConfigConstants.KEY_ICON_FORMAT + "=JPEG");
- assertEquals(ShortcutService.DEFAULT_RESET_INTERVAL_SEC * 1000,
- mService.getResetIntervalForTest());
-
- assertEquals(ShortcutService.DEFAULT_MAX_SHORTCUTS_PER_APP,
- mService.getMaxDynamicShortcutsForTest());
-
- assertEquals(ShortcutService.DEFAULT_MAX_UPDATES_PER_INTERVAL,
- mService.getMaxUpdatesPerIntervalForTest());
-
- assertEquals(50, mService.getMaxIconDimensionForTest());
-
- assertEquals(CompressFormat.JPEG, mService.getIconPersistFormatForTest());
-
- assertEquals(ShortcutService.DEFAULT_ICON_PERSIST_QUALITY,
- mService.getIconPersistQualityForTest());
- }
-
- // === 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_UPDATES_PER_INTERVAL, 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() {
- setCaller(CALLING_PACKAGE_1, USER_0);
-
- final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.icon1);
- final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
- getTestContext().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(list(si1, si2)));
- assertShortcutIds(assertAllNotKeyFieldsOnly(
- mManager.getDynamicShortcuts()),
- "shortcut1", "shortcut2");
- assertEquals(2, mManager.getRemainingCallCount());
-
- // TODO: Check fields
-
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertShortcutIds(assertAllNotKeyFieldsOnly(
- mManager.getDynamicShortcuts()),
- "shortcut1");
- assertEquals(1, mManager.getRemainingCallCount());
-
- assertTrue(mManager.setDynamicShortcuts(list()));
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertEquals(0, mManager.getRemainingCallCount());
-
- dumpsysOnLogcat();
-
- mInjectedCurrentTimeLillis++; // Need to advance the clock for reset to work.
- mService.resetThrottlingInner(UserHandle.USER_SYSTEM);
-
- dumpsysOnLogcat();
-
- assertTrue(mManager.setDynamicShortcuts(list(si2, si3)));
- assertEquals(2, mManager.getDynamicShortcuts().size());
-
- // TODO Check max number
-
- runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
- });
- }
-
- public void testAddDynamicShortcuts() {
- setCaller(CALLING_PACKAGE_1, USER_0);
-
- final ShortcutInfo si1 = makeShortcut("shortcut1");
- final ShortcutInfo si2 = makeShortcut("shortcut2");
- final ShortcutInfo si3 = makeShortcut("shortcut3");
-
- assertEquals(3, mManager.getRemainingCallCount());
-
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(2, mManager.getRemainingCallCount());
- assertShortcutIds(assertAllNotKeyFieldsOnly(
- mManager.getDynamicShortcuts()),
- "shortcut1");
-
- assertTrue(mManager.addDynamicShortcuts(list(si2, si3)));
- assertEquals(1, mManager.getRemainingCallCount());
- assertShortcutIds(assertAllNotKeyFieldsOnly(
- mManager.getDynamicShortcuts()),
- "shortcut1", "shortcut2", "shortcut3");
-
- // This should not crash. It'll still consume the quota.
- assertTrue(mManager.addDynamicShortcuts(list()));
- assertEquals(0, mManager.getRemainingCallCount());
- assertShortcutIds(assertAllNotKeyFieldsOnly(
- mManager.getDynamicShortcuts()),
- "shortcut1", "shortcut2", "shortcut3");
-
- mInjectedCurrentTimeLillis += INTERVAL; // reset
-
- // Add with the same ID
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("shortcut1"))));
- assertEquals(2, mManager.getRemainingCallCount());
- assertShortcutIds(assertAllNotKeyFieldsOnly(
- mManager.getDynamicShortcuts()),
- "shortcut1", "shortcut2", "shortcut3");
-
- // TODO Check max number
-
- // TODO Check fields.
-
- runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s1"))));
- });
- }
-
- public void testDeleteDynamicShortcuts() {
- final ShortcutInfo si1 = makeShortcut("shortcut1");
- final ShortcutInfo si2 = makeShortcut("shortcut2");
- final ShortcutInfo si3 = makeShortcut("shortcut3");
- final ShortcutInfo si4 = makeShortcut("shortcut4");
-
- assertTrue(mManager.setDynamicShortcuts(list(si1, si2, si3, si4)));
- assertShortcutIds(assertAllNotKeyFieldsOnly(
- mManager.getDynamicShortcuts()),
- "shortcut1", "shortcut2", "shortcut3", "shortcut4");
-
- assertEquals(2, mManager.getRemainingCallCount());
-
- mManager.removeDynamicShortcuts(list("shortcut1"));
- assertShortcutIds(assertAllNotKeyFieldsOnly(
- mManager.getDynamicShortcuts()),
- "shortcut2", "shortcut3", "shortcut4");
-
- mManager.removeDynamicShortcuts(list("shortcut1"));
- assertShortcutIds(assertAllNotKeyFieldsOnly(
- mManager.getDynamicShortcuts()),
- "shortcut2", "shortcut3", "shortcut4");
-
- mManager.removeDynamicShortcuts(list("shortcutXXX"));
- assertShortcutIds(assertAllNotKeyFieldsOnly(
- mManager.getDynamicShortcuts()),
- "shortcut2", "shortcut3", "shortcut4");
-
- mManager.removeDynamicShortcuts(list("shortcut2", "shortcut4"));
- assertShortcutIds(assertAllNotKeyFieldsOnly(
- mManager.getDynamicShortcuts()),
- "shortcut3");
-
- mManager.removeDynamicShortcuts(list("shortcut3"));
- assertShortcutIds(assertAllNotKeyFieldsOnly(
- mManager.getDynamicShortcuts()));
-
- // Still 2 calls left.
- assertEquals(2, mManager.getRemainingCallCount());
- }
-
- public void testDeleteAllDynamicShortcuts() {
- final ShortcutInfo si1 = makeShortcut("shortcut1");
- final ShortcutInfo si2 = makeShortcut("shortcut2");
- final ShortcutInfo si3 = makeShortcut("shortcut3");
-
- assertTrue(mManager.setDynamicShortcuts(list(si1, si2, si3)));
- assertShortcutIds(assertAllNotKeyFieldsOnly(
- mManager.getDynamicShortcuts()),
- "shortcut1", "shortcut2", "shortcut3");
-
- assertEquals(2, mManager.getRemainingCallCount());
-
- mManager.removeAllDynamicShortcuts();
- 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(list(si1, si2, si3)));
- assertEquals(3, mManager.getDynamicShortcuts().size());
-
- // Still 1 call left
- assertEquals(1, mManager.getRemainingCallCount());
- }
-
- public void testThrottling() {
- final ShortcutInfo si1 = makeShortcut("shortcut1");
-
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(2, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
-
- mInjectedCurrentTimeLillis++;
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(1, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
-
- mInjectedCurrentTimeLillis++;
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(0, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
-
- // Reached the max
-
- mInjectedCurrentTimeLillis++;
- assertFalse(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(0, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
-
- // Still throttled
- mInjectedCurrentTimeLillis = START_TIME + INTERVAL - 1;
- assertFalse(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(0, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
-
- // Now it should work.
- mInjectedCurrentTimeLillis++;
- assertTrue(mManager.setDynamicShortcuts(list(si1))); // fail
- assertEquals(2, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());
-
- mInjectedCurrentTimeLillis++;
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(1, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());
-
- mInjectedCurrentTimeLillis++;
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(0, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());
-
- mInjectedCurrentTimeLillis++;
- assertFalse(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(0, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL * 2, mManager.getRateLimitResetTime());
-
- // 4 hours later...
- mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL;
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(2, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL * 5, mManager.getRateLimitResetTime());
-
- mInjectedCurrentTimeLillis++;
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(1, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL * 5, mManager.getRateLimitResetTime());
-
- // Make sure getRemainingCallCount() itself gets reset without calling setDynamicShortcuts().
- mInjectedCurrentTimeLillis = START_TIME + 8 * INTERVAL;
- assertEquals(3, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL * 9, mManager.getRateLimitResetTime());
-
- mInjectedCurrentTimeLillis++;
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(2, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL * 9, mManager.getRateLimitResetTime());
- }
-
- public void testThrottling_rewind() {
- final ShortcutInfo si1 = makeShortcut("shortcut1");
-
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(2, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
-
- mInjectedCurrentTimeLillis = 12345; // Clock reset!
-
- // Since the clock looks invalid, the counter shouldn't have reset.
- assertEquals(2, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
-
- // Forward again. Still haven't reset yet.
- mInjectedCurrentTimeLillis = START_TIME + INTERVAL - 1;
- assertEquals(2, mManager.getRemainingCallCount());
- assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
-
- // Now rewind -- this will reset the counters.
- mInjectedCurrentTimeLillis = START_TIME - 100000;
- assertEquals(3, mManager.getRemainingCallCount());
-
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(2, mManager.getRemainingCallCount());
-
- // Forward again, should be reset now.
- mInjectedCurrentTimeLillis += INTERVAL;
- assertEquals(3, mManager.getRemainingCallCount());
- }
-
- public void testThrottling_perPackage() {
- final ShortcutInfo si1 = makeShortcut("shortcut1");
-
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(2, mManager.getRemainingCallCount());
-
- mInjectedCurrentTimeLillis++;
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(1, mManager.getRemainingCallCount());
-
- mInjectedCurrentTimeLillis++;
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertEquals(0, mManager.getRemainingCallCount());
-
- // Reached the max
-
- mInjectedCurrentTimeLillis++;
- assertFalse(mManager.setDynamicShortcuts(list(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(list(si2)));
- assertEquals(2, mManager.getRemainingCallCount());
-
- mInjectedCurrentTimeLillis++;
- assertTrue(mManager.setDynamicShortcuts(list(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(list(si1)));
- assertEquals(0, mManager.getRemainingCallCount());
-
- // Now it should work.
- mInjectedCurrentTimeLillis++;
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
-
- mInjectedCurrentTimeLillis++;
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
-
- mInjectedCurrentTimeLillis++;
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
-
- mInjectedCurrentTimeLillis++;
- assertFalse(mManager.setDynamicShortcuts(list(si1)));
-
- mInjectedCurrentTimeLillis = START_TIME + 4 * INTERVAL;
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertTrue(mManager.setDynamicShortcuts(list(si1)));
- assertFalse(mManager.setDynamicShortcuts(list(si1)));
-
- mInjectedClientPackage = CALLING_PACKAGE_2;
- mInjectedCallingUid = CALLING_UID_2;
-
- assertEquals(3, mManager.getRemainingCallCount());
-
- assertTrue(mManager.setDynamicShortcuts(list(si2)));
- assertTrue(mManager.setDynamicShortcuts(list(si2)));
- assertTrue(mManager.setDynamicShortcuts(list(si2)));
- assertFalse(mManager.setDynamicShortcuts(list(si2)));
- }
-
- public void testIcons() throws IOException {
- final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
- final Icon res64x64 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64);
- final Icon res512x512 = Icon.createWithResource(getTestContext(), R.drawable.black_512x512);
-
- final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
- getTestContext().getResources(), R.drawable.black_32x32));
- final Icon bmp64x64 = Icon.createWithBitmap(BitmapFactory.decodeResource(
- getTestContext().getResources(), R.drawable.black_64x64));
- final Icon bmp512x512 = Icon.createWithBitmap(BitmapFactory.decodeResource(
- getTestContext().getResources(), R.drawable.black_512x512));
-
- // Set from package 1
- setCaller(CALLING_PACKAGE_1);
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcutWithIcon("res32x32", res32x32),
- makeShortcutWithIcon("res64x64", res64x64),
- makeShortcutWithIcon("bmp32x32", bmp32x32),
- makeShortcutWithIcon("bmp64x64", bmp64x64),
- makeShortcutWithIcon("bmp512x512", bmp512x512),
- makeShortcut("none")
- )));
-
- // getDynamicShortcuts() shouldn't return icons, thus assertAllNotHaveIcon().
- assertShortcutIds(assertAllNotHaveIcon(mManager.getDynamicShortcuts()),
- "res32x32",
- "res64x64",
- "bmp32x32",
- "bmp64x64",
- "bmp512x512",
- "none");
-
- // Call from another caller with the same ID, just to make sure storage is per-package.
- setCaller(CALLING_PACKAGE_2);
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcutWithIcon("res32x32", res512x512),
- makeShortcutWithIcon("res64x64", res512x512),
- makeShortcutWithIcon("none", res512x512)
- )));
- assertShortcutIds(assertAllNotHaveIcon(mManager.getDynamicShortcuts()),
- "res32x32",
- "res64x64",
- "none");
-
- // Different profile. Note the names and the contents don't match.
- setCaller(CALLING_PACKAGE_1, USER_P0);
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcutWithIcon("res32x32", res512x512),
- makeShortcutWithIcon("bmp32x32", bmp512x512)
- )));
- assertShortcutIds(assertAllNotHaveIcon(mManager.getDynamicShortcuts()),
- "res32x32",
- "bmp32x32");
-
- // Re-initialize and load from the files.
- mService.saveDirtyInfo();
- initService();
-
- // Load from launcher.
- Bitmap bmp;
-
- setCaller(LAUNCHER_1);
- // Check hasIconResource()/hasIconFile().
- assertShortcutIds(assertAllHaveIconResId(
- list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "res32x32", USER_0))),
- "res32x32");
-
- assertShortcutIds(assertAllHaveIconResId(
- list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "res64x64", USER_0))),
- "res64x64");
-
- assertShortcutIds(assertAllHaveIconFile(
- list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp32x32", USER_0))),
- "bmp32x32");
-
- assertShortcutIds(assertAllHaveIconFile(
- list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp64x64", USER_0))),
- "bmp64x64");
-
- assertShortcutIds(assertAllHaveIconFile(
- list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp512x512", USER_0))),
- "bmp512x512");
-
- assertShortcutIds(assertAllHaveIconResId(
- list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "res32x32", USER_P0))),
- "res32x32");
- assertShortcutIds(assertAllHaveIconFile(
- list(getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp32x32", USER_P0))),
- "bmp32x32");
-
- // Check
- assertEquals(
- R.drawable.black_32x32,
- mLauncherApps.getShortcutIconResId(
- getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "res32x32", USER_0)));
-
- assertEquals(
- R.drawable.black_64x64,
- mLauncherApps.getShortcutIconResId(
- getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "res64x64", USER_0)));
-
- assertEquals(
- 0, // because it's not a resource
- mLauncherApps.getShortcutIconResId(
- getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp32x32", USER_0)));
- assertEquals(
- 0, // because it's not a resource
- mLauncherApps.getShortcutIconResId(
- getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp64x64", USER_0)));
- assertEquals(
- 0, // because it's not a resource
- mLauncherApps.getShortcutIconResId(
- getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp512x512", USER_0)));
-
- bmp = pfdToBitmap(mLauncherApps.getShortcutIconFd(
- getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp32x32", USER_0)));
- assertBitmapSize(32, 32, bmp);
-
- bmp = pfdToBitmap(mLauncherApps.getShortcutIconFd(
- getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp64x64", USER_0)));
- assertBitmapSize(64, 64, bmp);
-
- bmp = pfdToBitmap(mLauncherApps.getShortcutIconFd(
- getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp512x512", USER_0)));
- assertBitmapSize(128, 128, bmp);
-
- assertEquals(
- R.drawable.black_512x512,
- mLauncherApps.getShortcutIconResId(
- getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "res32x32", USER_P0)));
- // Should be 512x512, so shrunk.
- bmp = pfdToBitmap(mLauncherApps.getShortcutIconFd(
- getShortcutInfoAsLauncher(CALLING_PACKAGE_1, "bmp32x32", USER_P0)));
- assertBitmapSize(128, 128, bmp);
-
- // Also check the overload APIs too.
- assertEquals(
- R.drawable.black_32x32,
- mLauncherApps.getShortcutIconResId(CALLING_PACKAGE_1, "res32x32", HANDLE_USER_0));
- assertEquals(
- R.drawable.black_64x64,
- mLauncherApps.getShortcutIconResId(CALLING_PACKAGE_1, "res64x64", HANDLE_USER_0));
- assertEquals(
- R.drawable.black_512x512,
- mLauncherApps.getShortcutIconResId(CALLING_PACKAGE_1, "res32x32", HANDLE_USER_P0));
- bmp = pfdToBitmap(
- mLauncherApps.getShortcutIconFd(CALLING_PACKAGE_1, "bmp32x32", HANDLE_USER_P0));
- assertBitmapSize(128, 128, bmp);
-
- // TODO Test the content URI case too.
- }
-
- private void checkShrinkBitmap(int expectedWidth, int expectedHeight, int resId, int maxSize) {
- assertBitmapSize(expectedWidth, expectedHeight,
- ShortcutService.shrinkBitmap(BitmapFactory.decodeResource(
- getTestContext().getResources(), resId),
- maxSize));
- }
-
- public void testShrinkBitmap() {
- checkShrinkBitmap(32, 32, R.drawable.black_512x512, 32);
- checkShrinkBitmap(511, 511, R.drawable.black_512x512, 511);
- checkShrinkBitmap(512, 512, R.drawable.black_512x512, 512);
-
- checkShrinkBitmap(1024, 4096, R.drawable.black_1024x4096, 4096);
- checkShrinkBitmap(1024, 4096, R.drawable.black_1024x4096, 4100);
- checkShrinkBitmap(512, 2048, R.drawable.black_1024x4096, 2048);
-
- checkShrinkBitmap(4096, 1024, R.drawable.black_4096x1024, 4096);
- checkShrinkBitmap(4096, 1024, R.drawable.black_4096x1024, 4100);
- checkShrinkBitmap(2048, 512, R.drawable.black_4096x1024, 2048);
- }
-
- private File openIconFileForWriteAndGetPath(int userId, String packageName)
- throws IOException {
- // Shortcut IDs aren't used in the path, so just pass the same ID.
- final FileOutputStreamWithPath out =
- mService.openIconFileForWrite(userId, makePackageShortcut(packageName, "id"));
- out.close();
- return out.getFile();
- }
-
- public void testOpenIconFileForWrite() throws IOException {
- mInjectedCurrentTimeLillis = 1000;
-
- final File p10_1_1 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_1);
- final File p10_1_2 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_1);
-
- final File p10_2_1 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_2);
- final File p10_2_2 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_2);
-
- final File p11_1_1 = openIconFileForWriteAndGetPath(11, CALLING_PACKAGE_1);
- final File p11_1_2 = openIconFileForWriteAndGetPath(11, CALLING_PACKAGE_1);
-
- mInjectedCurrentTimeLillis++;
-
- final File p10_1_3 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_1);
- final File p10_1_4 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_1);
- final File p10_1_5 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_1);
-
- final File p10_2_3 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_2);
- final File p11_1_3 = openIconFileForWriteAndGetPath(11, CALLING_PACKAGE_1);
-
- // Make sure their paths are all unique
- assertAllUnique(list(
- p10_1_1,
- p10_1_2,
- p10_1_3,
- p10_1_4,
- p10_1_5,
-
- p10_2_1,
- p10_2_2,
- p10_2_3,
-
- p11_1_1,
- p11_1_2,
- p11_1_3
- ));
-
- // Check each set has the same parent.
- assertEquals(p10_1_1.getParent(), p10_1_2.getParent());
- assertEquals(p10_1_1.getParent(), p10_1_3.getParent());
- assertEquals(p10_1_1.getParent(), p10_1_4.getParent());
- assertEquals(p10_1_1.getParent(), p10_1_5.getParent());
-
- assertEquals(p10_2_1.getParent(), p10_2_2.getParent());
- assertEquals(p10_2_1.getParent(), p10_2_3.getParent());
-
- assertEquals(p11_1_1.getParent(), p11_1_2.getParent());
- assertEquals(p11_1_1.getParent(), p11_1_3.getParent());
-
- // Check the parents are still unique.
- assertAllUnique(list(
- p10_1_1.getParent(),
- p10_2_1.getParent(),
- p11_1_1.getParent()
- ));
-
- // All files created at the same time for the same package/user, expcet for the first ones,
- // will have "_" in the path.
- assertFalse(p10_1_1.getName().contains("_"));
- assertTrue(p10_1_2.getName().contains("_"));
- assertFalse(p10_1_3.getName().contains("_"));
- assertTrue(p10_1_4.getName().contains("_"));
- assertTrue(p10_1_5.getName().contains("_"));
-
- assertFalse(p10_2_1.getName().contains("_"));
- assertTrue(p10_2_2.getName().contains("_"));
- assertFalse(p10_2_3.getName().contains("_"));
-
- assertFalse(p11_1_1.getName().contains("_"));
- assertTrue(p11_1_2.getName().contains("_"));
- assertFalse(p11_1_3.getName().contains("_"));
- }
-
- public void testUpdateShortcuts() {
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"),
- makeShortcut("s2"),
- makeShortcut("s3"),
- makeShortcut("s4"),
- makeShortcut("s5")
- )));
- });
- runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"),
- makeShortcut("s2"),
- makeShortcut("s3"),
- makeShortcut("s4"),
- makeShortcut("s5")
- )));
- });
- runWithCaller(LAUNCHER_1, UserHandle.USER_SYSTEM, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2", "s3"),
- getCallingUser());
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s4", "s5"),
- getCallingUser());
- });
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- mManager.removeDynamicShortcuts(list("s1"));
- mManager.removeDynamicShortcuts(list("s2"));
- });
- runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
- mManager.removeDynamicShortcuts(list("s1"));
- mManager.removeDynamicShortcuts(list("s3"));
- mManager.removeDynamicShortcuts(list("s5"));
- });
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- assertShortcutIds(assertAllDynamic(
- mManager.getDynamicShortcuts()),
- "s3", "s4", "s5");
- assertShortcutIds(assertAllPinned(
- mManager.getPinnedShortcuts()),
- "s2", "s3");
- });
- runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
- assertShortcutIds(assertAllDynamic(
- mManager.getDynamicShortcuts()),
- "s2", "s4");
- assertShortcutIds(assertAllPinned(
- mManager.getPinnedShortcuts()),
- "s4", "s5");
- });
-
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- ShortcutInfo s2 = makeShortcutBuilder()
- .setId("s2")
- .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32))
- .build();
-
- ShortcutInfo s4 = makeShortcutBuilder()
- .setId("s4")
- .setTitle("new title")
- .build();
-
- mManager.updateShortcuts(list(s2, s4));
- });
- runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
- ShortcutInfo s2 = makeShortcutBuilder()
- .setId("s2")
- .setIntent(makeIntent(Intent.ACTION_ANSWER, ShortcutActivity.class,
- "key1", "val1"))
- .build();
-
- ShortcutInfo s4 = makeShortcutBuilder()
- .setId("s4")
- .setIntent(new Intent(Intent.ACTION_ALL_APPS))
- .build();
-
- mManager.updateShortcuts(list(s2, s4));
- });
-
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- assertShortcutIds(assertAllDynamic(
- mManager.getDynamicShortcuts()),
- "s3", "s4", "s5");
- assertShortcutIds(assertAllPinned(
- mManager.getPinnedShortcuts()),
- "s2", "s3");
-
- ShortcutInfo s = getCallerShortcut("s2");
- assertTrue(s.hasIconResource());
- assertEquals(R.drawable.black_32x32, s.getIconResourceId());
- assertEquals("Title-s2", s.getTitle());
-
- s = getCallerShortcut("s4");
- assertFalse(s.hasIconResource());
- assertEquals(0, s.getIconResourceId());
- assertEquals("new title", s.getTitle());
- });
- runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
- assertShortcutIds(assertAllDynamic(
- mManager.getDynamicShortcuts()),
- "s2", "s4");
- assertShortcutIds(assertAllPinned(
- mManager.getPinnedShortcuts()),
- "s4", "s5");
-
- ShortcutInfo s = getCallerShortcut("s2");
- assertFalse(s.hasIconResource());
- assertEquals(0, s.getIconResourceId());
- assertEquals("Title-s2", s.getTitle());
- assertEquals(Intent.ACTION_ANSWER, s.getIntent().getAction());
- assertEquals(1, s.getIntent().getExtras().size());
-
- s = getCallerShortcut("s4");
- assertFalse(s.hasIconResource());
- assertEquals(0, s.getIconResourceId());
- assertEquals("Title-s4", s.getTitle());
- assertEquals(Intent.ACTION_ALL_APPS, s.getIntent().getAction());
- assertBundleEmpty(s.getIntent().getExtras());
- });
- // TODO Check with other fields too.
-
- // TODO Check bitmap removal too.
-
- runWithCaller(CALLING_PACKAGE_2, USER_11, () -> {
- mManager.updateShortcuts(list());
- });
- }
-
- // === Test for launcher side APIs ===
-
- private static ShortcutQuery buildQuery(long changedSince,
- String packageName, ComponentName componentName,
- /* @ShortcutQuery.QueryFlags */ int flags) {
- return buildQuery(changedSince, packageName, null, componentName, flags);
- }
-
- private static ShortcutQuery buildQuery(long changedSince,
- String packageName, List<String> shortcutIds, ComponentName componentName,
- /* @ShortcutQuery.QueryFlags */ int flags) {
- final ShortcutQuery q = new ShortcutQuery();
- q.setChangedSince(changedSince);
- q.setPackage(packageName);
- q.setShortcutIds(shortcutIds);
- q.setActivity(componentName);
- q.setQueryFlags(flags);
- return q;
- }
-
- private static ShortcutQuery buildAllQuery(String packageName) {
- final ShortcutQuery q = new ShortcutQuery();
- q.setPackage(packageName);
- q.setQueryFlags(ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED);
- return q;
- }
-
- private static ShortcutQuery buildPinnedQuery(String packageName) {
- final ShortcutQuery q = new ShortcutQuery();
- q.setPackage(packageName);
- q.setQueryFlags(ShortcutQuery.FLAG_GET_PINNED);
- return q;
- }
-
- 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(list(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(list(s2_2, s2_3, s2_4)));
-
- setCaller(CALLING_PACKAGE_3);
- final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s3", START_TIME + 5000);
- assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
-
- setCaller(LAUNCHER_1);
-
- // Get dynamic
- assertAllDynamic(assertAllHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
- assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
- "s1", "s2"))));
-
- // Get pinned
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null,
- ShortcutQuery.FLAG_GET_PINNED), getCallingUser())
- /* none */);
-
- // Get both, with timestamp
- assertAllDynamic(assertAllHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
- assertAllNotKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
- /* time =*/ 1000, CALLING_PACKAGE_2,
- /* activity =*/ null,
- ShortcutQuery.FLAG_GET_PINNED | ShortcutQuery.FLAG_GET_DYNAMIC),
- getCallingUser())),
- "s2", "s3"))));
-
- // FLAG_GET_KEY_FIELDS_ONLY
- assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
- assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
- /* time =*/ 1000, CALLING_PACKAGE_2,
- /* activity =*/ null,
- ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
- getCallingUser())),
- "s2", "s3"))));
-
- // With ID.
- assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
- assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
- /* time =*/ 1000, CALLING_PACKAGE_2, list("s3"),
- /* activity =*/ null,
- ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
- getCallingUser())),
- "s3"))));
- assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
- assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
- /* time =*/ 1000, CALLING_PACKAGE_2, list("s3", "s2", "ss"),
- /* activity =*/ null,
- ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
- getCallingUser())),
- "s2", "s3"))));
- assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
- assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
- /* time =*/ 1000, CALLING_PACKAGE_2, list("s3x", "s2x"),
- /* activity =*/ null,
- ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
- getCallingUser()))
- /* empty */))));
- assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
- assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
- /* time =*/ 1000, CALLING_PACKAGE_2, list(),
- /* activity =*/ null,
- ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
- getCallingUser()))
- /* empty */))));
-
- // Pin some shortcuts.
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
- list("s3", "s4"), getCallingUser());
-
- // Pinned ones only
- assertAllPinned(assertAllHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
- assertAllNotKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
- /* time =*/ 1000, CALLING_PACKAGE_2,
- /* activity =*/ null,
- ShortcutQuery.FLAG_GET_PINNED),
- getCallingUser())),
- "s3"))));
-
- // All packages.
- assertShortcutIds(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(
- /* time =*/ 5000, /* package= */ null,
- /* activity =*/ null,
- ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED),
- getCallingUser())),
- "s1", "s3");
-
- assertExpectException(
- IllegalArgumentException.class, "package name must also be set", () -> {
- mLauncherApps.getShortcuts(buildQuery(
- /* time =*/ 0, /* package= */ null, list("id"),
- /* activity =*/ null, /* flags */ 0), getCallingUser());
- });
-
- // 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(list(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(list(s2_1)));
- dumpsysOnLogcat();
-
- // Pin some.
- setCaller(LAUNCHER_1);
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s2"), getCallingUser());
-
- dumpsysOnLogcat();
-
- // Delete some.
- setCaller(CALLING_PACKAGE_1);
- assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
- mManager.removeDynamicShortcuts(list("s2"));
- assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
-
- dumpsysOnLogcat();
-
- setCaller(LAUNCHER_1);
- List<ShortcutInfo> list;
-
- // Note we don't guarantee the orders.
- list = assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents(
- assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcutInfo(CALLING_PACKAGE_1,
- list("s2", "s1", "s3", null), getCallingUser())))),
- "s1", "s2");
- assertEquals("Title 1", findById(list, "s1").getTitle());
- assertEquals("Title 2", findById(list, "s2").getTitle());
-
- assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents(
- mLauncherApps.getShortcutInfo(CALLING_PACKAGE_1,
- list("s3"), getCallingUser())))
- /* none */);
-
- list = assertShortcutIds(assertAllHaveTitle(assertAllNotHaveIntents(
- mLauncherApps.getShortcutInfo(CALLING_PACKAGE_2,
- list("s1", "s2", "s3"), getCallingUser()))),
- "s1");
- assertEquals("ABC", findById(list, "s1").getTitle());
- }
-
- public void testPinShortcutAndGetPinnedShortcuts() {
- // Create some shortcuts.
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000);
- final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000);
-
- assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
- });
-
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- final ShortcutInfo s2_2 = makeShortcutWithTimestamp("s2", 1500);
- final ShortcutInfo s2_3 = makeShortcutWithTimestamp("s3", 3000);
- final ShortcutInfo s2_4 = makeShortcutWithTimestamp("s4", 500);
- assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4)));
- });
-
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s2", 1000);
- assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
- });
-
- // Pin some.
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s2", "s3"), getCallingUser());
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
- list("s3", "s4", "s5"), getCallingUser());
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_3,
- list("s3"), getCallingUser()); // Note ID doesn't exist
- });
-
- // Delete some.
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
- mManager.removeDynamicShortcuts(list("s2"));
- assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
- });
-
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4");
- mManager.removeDynamicShortcuts(list("s3"));
- assertShortcutIds(mManager.getPinnedShortcuts(), "s3", "s4");
- });
-
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- assertShortcutIds(mManager.getPinnedShortcuts() /* none */);
- mManager.removeDynamicShortcuts(list("s2"));
- assertShortcutIds(mManager.getPinnedShortcuts() /* none */);
- });
-
- // Get pinned shortcuts from launcher
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- // CALLING_PACKAGE_1 deleted s2, but it's pinned, so it still exists.
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
- "s2");
-
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
- "s3", "s4");
-
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_3,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))
- /* none */);
- });
- }
-
- public void testPinShortcutAndGetPinnedShortcuts_multi() {
- // Create some shortcuts.
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
-
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
-
- dumpsysOnLogcat();
-
- // Pin some.
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s3", "s4"), getCallingUser());
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
- list("s1", "s2", "s4"), getCallingUser());
- });
-
- dumpsysOnLogcat();
-
- // Delete some.
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertShortcutIds(mManager.getPinnedShortcuts(), "s3");
- mManager.removeDynamicShortcuts(list("s3"));
- assertShortcutIds(mManager.getPinnedShortcuts(), "s3");
- });
-
- dumpsysOnLogcat();
-
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertShortcutIds(mManager.getPinnedShortcuts(), "s1", "s2");
- mManager.removeDynamicShortcuts(list("s1"));
- mManager.removeDynamicShortcuts(list("s3"));
- assertShortcutIds(mManager.getPinnedShortcuts(), "s1", "s2");
- });
-
- dumpsysOnLogcat();
-
- // Get pinned shortcuts from launcher
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
- "s3");
-
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
- "s1", "s2");
-
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
- | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
- "s1", "s2", "s3");
-
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
- | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
- "s1", "s2");
- });
-
- dumpsysOnLogcat();
-
- runWithCaller(LAUNCHER_2, USER_0, () -> {
- // Launcher2 still has no pinned ones.
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))
- /* none */);
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))
- /* none */);
-
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
- | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
- "s1", "s2");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
- | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
- "s2");
-
- // Now pin some.
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s1", "s2"), getCallingUser());
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
- list("s1", "s2"), getCallingUser());
-
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
- | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
- "s1", "s2");
-
- // S1 was not visible to it, so shouldn't be pinned.
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
- | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
- "s2");
- });
-
- // Re-initialize and load from the files.
- mService.saveDirtyInfo();
- initService();
-
- // Load from file.
- mService.handleUnlockUser(USER_0);
-
- // Make sure package info is restored too.
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
- "s3");
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
- "s1", "s2");
- });
- runWithCaller(LAUNCHER_2, USER_0, () -> {
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
- | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
- "s1", "s2");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
- | ShortcutQuery.FLAG_GET_DYNAMIC), getCallingUser())),
- "s2");
- });
-
- // Delete all dynamic.
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- mManager.removeAllDynamicShortcuts();
-
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), "s1", "s2", "s3");
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- mManager.removeAllDynamicShortcuts();
-
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), "s2", "s1");
- });
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
- "s3");
-
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
- "s1", "s2");
-
- // from all packages.
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, null,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
- "s1", "s2", "s3");
-
- // Update pined. Note s2 and s3 are actually available, but not visible to this
- // launcher, so still can't be pinned.
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3", "s4"),
- getCallingUser());
-
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
- "s3");
- });
- // Re-publish s1.
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s1"))));
-
- assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()), "s1");
- assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), "s1", "s2", "s3");
- });
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
- "s3");
-
- // Now "s1" is visible, so can be pinned.
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3", "s4"),
- getCallingUser());
-
- assertShortcutIds(assertAllPinned(assertAllNotKeyFieldsOnly(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))),
- "s1", "s3");
- });
-
- // Now clear pinned shortcuts. First, from launcher 1.
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list(), getCallingUser());
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list(), getCallingUser());
-
- assertEquals(0,
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()).size());
- assertEquals(0,
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()).size());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()), "s1");
- assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), "s1", "s2");
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()), "s2");
- });
-
- // Clear all pins from launcher 2.
- runWithCaller(LAUNCHER_2, USER_0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list(), getCallingUser());
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list(), getCallingUser());
-
- assertEquals(0,
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()).size());
- assertEquals(0,
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()).size());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()), "s1");
- assertEquals(0, mManager.getPinnedShortcuts().size());
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertEquals(0, mManager.getPinnedShortcuts().size());
- });
- }
-
- public void testPinShortcutAndGetPinnedShortcuts_crossProfile_plusLaunch() {
- // Create some shortcuts.
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
- makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
- });
-
- // Pin some shortcuts and see the result.
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s1"), HANDLE_USER_0);
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
- list("s1", "s2", "s3"), HANDLE_USER_0);
- });
-
- runWithCaller(LAUNCHER_1, USER_P0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s2"), HANDLE_USER_0);
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
- list("s2", "s3"), HANDLE_USER_0);
- });
-
- runWithCaller(LAUNCHER_2, USER_P0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s3"), HANDLE_USER_0);
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
- list("s3"), HANDLE_USER_0);
- });
-
- runWithCaller(LAUNCHER_2, USER_10, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s1", "s2", "s3"), HANDLE_USER_10);
- });
-
- // Cross profile pinning.
- final int PIN_AND_DYNAMIC = ShortcutQuery.FLAG_GET_PINNED | ShortcutQuery.FLAG_GET_DYNAMIC;
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
-
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
-
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
-
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
-
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
- });
- runWithCaller(LAUNCHER_1, USER_P0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s2");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
-
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s2", "s3");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
-
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
-
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
-
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
- });
- runWithCaller(LAUNCHER_2, USER_P0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s3");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
-
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s3");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
-
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
-
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
-
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
- });
- runWithCaller(LAUNCHER_2, USER_10, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_10)),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_10)),
- "s1", "s2", "s3", "s4", "s5", "s6");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_10)),
- "s1", "s2", "s3", "s4", "s5", "s6");
- });
-
- // Remove some dynamic shortcuts.
-
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"))));
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"))));
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"))));
- });
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1");
-
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
-
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
-
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
-
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
- });
- runWithCaller(LAUNCHER_1, USER_P0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s2");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2");
-
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s2", "s3");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
-
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
-
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
-
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
- });
- runWithCaller(LAUNCHER_2, USER_P0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s3");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s3");
-
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s3");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s3");
-
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
-
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
-
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
- });
- runWithCaller(LAUNCHER_2, USER_10, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_10)),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_10)),
- "s1");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_10)),
- "s1", "s2", "s3");
-
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
-
- assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
-
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
- });
-
- // Save & load and make sure we still have the same information.
- mService.saveDirtyInfo();
- initService();
- mService.handleUnlockUser(USER_0);
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1");
-
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
-
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
-
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
-
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
- });
- runWithCaller(LAUNCHER_1, USER_P0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s2");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2");
-
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s2", "s3");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s2", "s3");
-
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
-
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
-
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
- });
- runWithCaller(LAUNCHER_2, USER_P0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s3");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s3");
-
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), HANDLE_USER_0)),
- "s3");
- assertShortcutIds(assertAllDynamic(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, ShortcutQuery.FLAG_GET_DYNAMIC), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllDynamicOrPinned(
- mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
- /* activity =*/ null, PIN_AND_DYNAMIC), HANDLE_USER_0)),
- "s1", "s3");
-
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s1", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_1, "s3", USER_0);
-
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s1", USER_0);
- assertShortcutNotLaunchable(CALLING_PACKAGE_2, "s2", USER_0);
- assertShortcutLaunchable(CALLING_PACKAGE_2, "s3", USER_0);
-
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s1", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s2", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s3", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s4", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s5", USER_10);
- assertShortcutNotLaunchable(CALLING_PACKAGE_1, "s6", USER_10);
- });
- }
-
- public void testStartShortcut() {
- // 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(list(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(list(s2_1)));
-
- // Pin all.
- setCaller(LAUNCHER_1);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s1", "s2"), getCallingUser());
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
- list("s1"), getCallingUser());
-
- // Just to make it complicated, delete some.
- setCaller(CALLING_PACKAGE_1);
- mManager.removeDynamicShortcuts(list("s2"));
-
- // intent and check.
- setCaller(LAUNCHER_1);
-
- Intent intent;
- intent = launchShortcutAndGetIntent(CALLING_PACKAGE_1, "s1", USER_0);
- assertEquals(ShortcutActivity2.class.getName(), intent.getComponent().getClassName());
-
-
- intent = launchShortcutAndGetIntent(CALLING_PACKAGE_1, "s2", USER_0);
- assertEquals(ShortcutActivity3.class.getName(), intent.getComponent().getClassName());
-
- intent = launchShortcutAndGetIntent(CALLING_PACKAGE_2, "s1", USER_0);
- assertEquals(ShortcutActivity.class.getName(), intent.getComponent().getClassName());
-
- // TODO Check extra, etc
- }
-
- public void testLauncherCallback() throws Throwable {
- LauncherApps.Callback c0 = mock(LauncherApps.Callback.class);
-
- // Set listeners
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.registerCallback(c0, new Handler(Looper.getMainLooper()));
- });
-
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
-
- waitOnMainThread();
- ArgumentCaptor<List> shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
- "s1", "s2", "s3");
-
- // From different package.
- reset(c0);
- runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- waitOnMainThread();
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_2),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
- "s1", "s2", "s3");
-
- // Different user, callback shouldn't be called.
- reset(c0);
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- waitOnMainThread();
- verify(c0, times(0)).onShortcutsChanged(
- anyString(),
- any(List.class),
- any(UserHandle.class)
- );
-
- // Test for addDynamicShortcuts.
- reset(c0);
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- dumpsysOnLogcat("before addDynamicShortcuts");
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s4"))));
- });
-
- waitOnMainThread();
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
- "s1", "s2", "s3", "s4");
-
- // Test for remove
- reset(c0);
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- mManager.removeDynamicShortcuts(list("s1"));
- });
-
- waitOnMainThread();
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
- "s2", "s3", "s4");
-
- // Test for update
- reset(c0);
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- assertTrue(mManager.updateShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"))));
- });
-
- waitOnMainThread();
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertShortcutIds(assertAllDynamic(shortcuts.getValue()),
- "s2", "s3", "s4");
-
- // Test for deleteAll
- reset(c0);
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- mManager.removeAllDynamicShortcuts();
- });
-
- waitOnMainThread();
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertEquals(0, shortcuts.getValue().size());
-
- // Remove CALLING_PACKAGE_2
- reset(c0);
- uninstallPackage(USER_0, CALLING_PACKAGE_2);
- mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0);
-
- // Should get a callback with an empty list.
- waitOnMainThread();
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_2),
- shortcuts.capture(),
- eq(HANDLE_USER_0)
- );
- assertEquals(0, shortcuts.getValue().size());
- }
-
- public void testLauncherCallback_crossProfile() throws Throwable {
- prepareCrossProfileDataSet();
-
- final Handler h = new Handler(Looper.getMainLooper());
-
- final LauncherApps.Callback c0_1 = mock(LauncherApps.Callback.class);
- final LauncherApps.Callback c0_2 = mock(LauncherApps.Callback.class);
- final LauncherApps.Callback c0_3 = mock(LauncherApps.Callback.class);
- final LauncherApps.Callback c0_4 = mock(LauncherApps.Callback.class);
-
- final LauncherApps.Callback cP0_1 = mock(LauncherApps.Callback.class);
- final LauncherApps.Callback c10_1 = mock(LauncherApps.Callback.class);
- final LauncherApps.Callback c10_2 = mock(LauncherApps.Callback.class);
- final LauncherApps.Callback c11_1 = mock(LauncherApps.Callback.class);
-
- final List<LauncherApps.Callback> all =
- list(c0_1, c0_2, c0_3, c0_4, cP0_1, c10_1, c11_1);
-
- setDefaultLauncherChecker((pkg, userId) -> {
- switch (userId) {
- case USER_0:
- return LAUNCHER_2.equals(pkg);
- case USER_P0:
- return LAUNCHER_1.equals(pkg);
- case USER_10:
- return LAUNCHER_1.equals(pkg);
- case USER_11:
- return LAUNCHER_1.equals(pkg);
- default:
- return false;
- }
- });
-
- runWithCaller(LAUNCHER_1, USER_0, () -> mLauncherApps.registerCallback(c0_1, h));
- runWithCaller(LAUNCHER_2, USER_0, () -> mLauncherApps.registerCallback(c0_2, h));
- runWithCaller(LAUNCHER_3, USER_0, () -> mLauncherApps.registerCallback(c0_3, h));
- runWithCaller(LAUNCHER_4, USER_0, () -> mLauncherApps.registerCallback(c0_4, h));
- runWithCaller(LAUNCHER_1, USER_P0, () -> mLauncherApps.registerCallback(cP0_1, h));
- runWithCaller(LAUNCHER_1, USER_10, () -> mLauncherApps.registerCallback(c10_1, h));
- runWithCaller(LAUNCHER_2, USER_10, () -> mLauncherApps.registerCallback(c10_2, h));
- runWithCaller(LAUNCHER_1, USER_11, () -> mLauncherApps.registerCallback(c11_1, h));
-
- // User 0.
-
- resetAll(all);
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- mManager.removeDynamicShortcuts(list());
- });
- waitOnMainThread();
-
- assertCallbackNotReceived(c0_1);
- assertCallbackNotReceived(c0_3);
- assertCallbackNotReceived(c0_4);
- assertCallbackNotReceived(c10_1);
- assertCallbackNotReceived(c10_2);
- assertCallbackNotReceived(c11_1);
- assertCallbackReceived(c0_2, HANDLE_USER_0, CALLING_PACKAGE_1, "s1", "s2", "s3");
- assertCallbackReceived(cP0_1, HANDLE_USER_0, CALLING_PACKAGE_1, "s1", "s2", "s3", "s4");
-
- // User 0, different package.
-
- resetAll(all);
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- mManager.removeDynamicShortcuts(list());
- });
- waitOnMainThread();
-
- assertCallbackNotReceived(c0_1);
- assertCallbackNotReceived(c0_3);
- assertCallbackNotReceived(c0_4);
- assertCallbackNotReceived(c10_1);
- assertCallbackNotReceived(c10_2);
- assertCallbackNotReceived(c11_1);
- assertCallbackReceived(c0_2, HANDLE_USER_0, CALLING_PACKAGE_3, "s1", "s2", "s3", "s4");
- assertCallbackReceived(cP0_1, HANDLE_USER_0, CALLING_PACKAGE_3,
- "s1", "s2", "s3", "s4", "s5", "s6");
-
- // Work profile, but not running, so don't send notifications.
-
- resetAll(all);
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- mManager.removeDynamicShortcuts(list());
- });
- waitOnMainThread();
-
- assertCallbackNotReceived(c0_1);
- assertCallbackNotReceived(c0_2);
- assertCallbackNotReceived(c0_3);
- assertCallbackNotReceived(c0_4);
- assertCallbackNotReceived(cP0_1);
- assertCallbackNotReceived(c10_1);
- assertCallbackNotReceived(c10_2);
- assertCallbackNotReceived(c11_1);
-
- // Work profile, now running.
-
- when(mMockUserManager.isUserRunning(anyInt())).thenReturn(false);
- when(mMockUserManager.isUserRunning(eq(USER_P0))).thenReturn(true);
-
- resetAll(all);
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- mManager.removeDynamicShortcuts(list());
- });
- waitOnMainThread();
-
- assertCallbackNotReceived(c0_1);
- assertCallbackNotReceived(c0_3);
- assertCallbackNotReceived(c0_4);
- assertCallbackNotReceived(c10_1);
- assertCallbackNotReceived(c10_2);
- assertCallbackNotReceived(c11_1);
- assertCallbackReceived(c0_2, HANDLE_USER_P0, CALLING_PACKAGE_1, "s1", "s2", "s3", "s5");
- assertCallbackReceived(cP0_1, HANDLE_USER_P0, CALLING_PACKAGE_1, "s1", "s2", "s3", "s4");
-
- // Normal secondary user.
-
- when(mMockUserManager.isUserRunning(anyInt())).thenReturn(false);
- when(mMockUserManager.isUserRunning(eq(USER_10))).thenReturn(true);
-
- resetAll(all);
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- mManager.removeDynamicShortcuts(list());
- });
- waitOnMainThread();
-
- assertCallbackNotReceived(c0_1);
- assertCallbackNotReceived(c0_2);
- assertCallbackNotReceived(c0_3);
- assertCallbackNotReceived(c0_4);
- assertCallbackNotReceived(cP0_1);
- assertCallbackNotReceived(c10_2);
- assertCallbackNotReceived(c11_1);
- assertCallbackReceived(c10_1, HANDLE_USER_10, CALLING_PACKAGE_1,
- "x1", "x2", "x3", "x4", "x5");
- }
-
- // === Test for persisting ===
-
- public void testSaveAndLoadUser_empty() {
- assertTrue(mManager.setDynamicShortcuts(list()));
-
- Log.i(TAG, "Saved state");
- dumpsysOnLogcat();
- dumpUserFile(0);
-
- // Restore.
- mService.saveDirtyInfo();
- initService();
-
- assertEquals(0, mManager.getDynamicShortcuts().size());
- }
-
- /**
- * Try save and load, also stop/start the user.
- */
- public void testSaveAndLoadUser() {
- // First, create some shortcuts and save.
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16);
- final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
- getTestContext().getResources(), R.drawable.icon2));
-
- final ShortcutInfo si1 = makeShortcut(
- "s1",
- "title1-1",
- makeComponent(ShortcutActivity.class),
- icon1,
- makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
- "key1", "val1", "nest", makeBundle("key", 123)),
- /* weight */ 10);
-
- final ShortcutInfo si2 = makeShortcut(
- "s2",
- "title1-2",
- /* activity */ null,
- icon2,
- makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
- /* weight */ 12);
-
- assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
-
- assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
- assertEquals(2, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
- final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_16x64);
- final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
- getTestContext().getResources(), R.drawable.icon2));
-
- final ShortcutInfo si1 = makeShortcut(
- "s1",
- "title2-1",
- makeComponent(ShortcutActivity.class),
- icon1,
- makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
- "key1", "val1", "nest", makeBundle("key", 123)),
- /* weight */ 10);
-
- final ShortcutInfo si2 = makeShortcut(
- "s2",
- "title2-2",
- /* activity */ null,
- icon2,
- makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
- /* weight */ 12);
-
- assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
-
- assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
- assertEquals(2, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64);
- final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
- getTestContext().getResources(), R.drawable.icon2));
-
- final ShortcutInfo si1 = makeShortcut(
- "s1",
- "title10-1-1",
- makeComponent(ShortcutActivity.class),
- icon1,
- makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
- "key1", "val1", "nest", makeBundle("key", 123)),
- /* weight */ 10);
-
- final ShortcutInfo si2 = makeShortcut(
- "s2",
- "title10-1-2",
- /* activity */ null,
- icon2,
- makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
- /* weight */ 12);
-
- assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
-
- assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
- assertEquals(2, mManager.getRemainingCallCount());
- });
-
- mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM).setLauncherComponent(
- mService, new ComponentName("pkg1", "class"));
-
- // Restore.
- mService.saveDirtyInfo();
- initService();
-
- // Before the load, the map should be empty.
- assertEquals(0, mService.getShortcutsForTest().size());
-
- // this will pre-load the per-user info.
- mService.handleUnlockUser(UserHandle.USER_SYSTEM);
-
- // Now it's loaded.
- assertEquals(1, mService.getShortcutsForTest().size());
-
- runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
- assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
- mManager.getDynamicShortcuts()))), "s1", "s2");
- assertEquals(2, mManager.getRemainingCallCount());
-
- assertEquals("title1-1", getCallerShortcut("s1").getTitle());
- assertEquals("title1-2", getCallerShortcut("s2").getTitle());
- });
- runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
- assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
- mManager.getDynamicShortcuts()))), "s1", "s2");
- assertEquals(2, mManager.getRemainingCallCount());
-
- assertEquals("title2-1", getCallerShortcut("s1").getTitle());
- assertEquals("title2-2", getCallerShortcut("s2").getTitle());
- });
-
- assertEquals("pkg1", mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM)
- .getLauncherComponent().getPackageName());
-
- // Start another user
- mService.handleUnlockUser(USER_10);
-
- // Now the size is 2.
- assertEquals(2, mService.getShortcutsForTest().size());
-
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
- mManager.getDynamicShortcuts()))), "s1", "s2");
- assertEquals(2, mManager.getRemainingCallCount());
-
- assertEquals("title10-1-1", getCallerShortcut("s1").getTitle());
- assertEquals("title10-1-2", getCallerShortcut("s2").getTitle());
- });
- assertNull(mService.getShortcutsForTest().get(USER_10).getLauncherComponent());
-
- // Try stopping the user
- mService.handleCleanupUser(USER_10);
-
- // Now it's unloaded.
- assertEquals(1, mService.getShortcutsForTest().size());
-
- // TODO Check all other fields
- }
-
- public void testCleanupPackage() {
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s0_1"))));
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s0_2"))));
- });
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s0_1"),
- HANDLE_USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s0_2"),
- HANDLE_USER_0);
- });
- runWithCaller(LAUNCHER_2, USER_0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s0_1"),
- HANDLE_USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s0_2"),
- HANDLE_USER_0);
- });
-
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s10_1"))));
- });
- runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s10_2"))));
- });
- runWithCaller(LAUNCHER_1, USER_10, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s10_1"),
- HANDLE_USER_10);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s10_2"),
- HANDLE_USER_10);
- });
- runWithCaller(LAUNCHER_2, USER_10, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s10_1"),
- HANDLE_USER_10);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s10_2"),
- HANDLE_USER_10);
- });
-
- // Remove all dynamic shortcuts; now all shortcuts are just pinned.
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- mManager.removeAllDynamicShortcuts();
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- mManager.removeAllDynamicShortcuts();
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- mManager.removeAllDynamicShortcuts();
- });
- runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
- mManager.removeAllDynamicShortcuts();
- });
-
-
- final SparseArray<ShortcutUser> users = mService.getShortcutsForTest();
- assertEquals(2, users.size());
- assertEquals(USER_0, users.keyAt(0));
- assertEquals(USER_10, users.keyAt(1));
-
- final ShortcutUser user0 = users.get(USER_0);
- final ShortcutUser user10 = users.get(USER_10);
-
-
- // Check the registered packages.
- dumpsysOnLogcat();
- assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- hashSet(user0.getAllPackagesForTest().keySet()));
- assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- hashSet(user10.getAllPackagesForTest().keySet()));
- assertEquals(
- set(PackageWithUser.of(USER_0, LAUNCHER_1),
- PackageWithUser.of(USER_0, LAUNCHER_2)),
- hashSet(user0.getAllLaunchersForTest().keySet()));
- assertEquals(
- set(PackageWithUser.of(USER_10, LAUNCHER_1),
- PackageWithUser.of(USER_10, LAUNCHER_2)),
- hashSet(user10.getAllLaunchersForTest().keySet()));
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
- "s0_1", "s0_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
- "s0_1", "s0_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_10),
- "s10_1", "s10_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
- "s10_1", "s10_2");
- assertShortcutExists(CALLING_PACKAGE_1, "s0_1", USER_0);
- assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
- assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
- assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
-
- mService.saveDirtyInfo();
-
- // Nonexistent package.
- uninstallPackage(USER_0, "abc");
- mService.cleanUpPackageLocked("abc", USER_0, USER_0);
-
- // No changes.
- assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- hashSet(user0.getAllPackagesForTest().keySet()));
- assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- hashSet(user10.getAllPackagesForTest().keySet()));
- assertEquals(
- set(PackageWithUser.of(USER_0, LAUNCHER_1),
- PackageWithUser.of(USER_0, LAUNCHER_2)),
- hashSet(user0.getAllLaunchersForTest().keySet()));
- assertEquals(
- set(PackageWithUser.of(USER_10, LAUNCHER_1),
- PackageWithUser.of(USER_10, LAUNCHER_2)),
- hashSet(user10.getAllLaunchersForTest().keySet()));
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
- "s0_1", "s0_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
- "s0_1", "s0_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_10),
- "s10_1", "s10_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
- "s10_1", "s10_2");
- assertShortcutExists(CALLING_PACKAGE_1, "s0_1", USER_0);
- assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
- assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
- assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
-
- mService.saveDirtyInfo();
-
- // Remove a package.
- uninstallPackage(USER_0, CALLING_PACKAGE_1);
- mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0);
-
- assertEquals(set(CALLING_PACKAGE_2),
- hashSet(user0.getAllPackagesForTest().keySet()));
- assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- hashSet(user10.getAllPackagesForTest().keySet()));
- assertEquals(
- set(PackageWithUser.of(USER_0, LAUNCHER_1),
- PackageWithUser.of(USER_0, LAUNCHER_2)),
- hashSet(user0.getAllLaunchersForTest().keySet()));
- assertEquals(
- set(PackageWithUser.of(USER_10, LAUNCHER_1),
- PackageWithUser.of(USER_10, LAUNCHER_2)),
- hashSet(user10.getAllLaunchersForTest().keySet()));
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
- "s0_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
- "s0_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_10),
- "s10_1", "s10_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
- "s10_1", "s10_2");
- assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
- assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
- assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
- assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
-
- mService.saveDirtyInfo();
-
- // Remove a launcher.
- uninstallPackage(USER_10, LAUNCHER_1);
- mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10);
-
- assertEquals(set(CALLING_PACKAGE_2),
- hashSet(user0.getAllPackagesForTest().keySet()));
- assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
- hashSet(user10.getAllPackagesForTest().keySet()));
- assertEquals(
- set(PackageWithUser.of(USER_0, LAUNCHER_1),
- PackageWithUser.of(USER_0, LAUNCHER_2)),
- hashSet(user0.getAllLaunchersForTest().keySet()));
- assertEquals(
- set(PackageWithUser.of(USER_10, LAUNCHER_2)),
- hashSet(user10.getAllLaunchersForTest().keySet()));
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
- "s0_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
- "s0_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
- "s10_1", "s10_2");
- assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
- assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
- assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
- assertShortcutExists(CALLING_PACKAGE_2, "s10_2", USER_10);
-
- mService.saveDirtyInfo();
-
- // Remove a package.
- uninstallPackage(USER_10, CALLING_PACKAGE_2);
- mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10);
-
- assertEquals(set(CALLING_PACKAGE_2),
- hashSet(user0.getAllPackagesForTest().keySet()));
- assertEquals(set(CALLING_PACKAGE_1),
- hashSet(user10.getAllPackagesForTest().keySet()));
- assertEquals(
- set(PackageWithUser.of(USER_0, LAUNCHER_1),
- PackageWithUser.of(USER_0, LAUNCHER_2)),
- hashSet(user0.getAllLaunchersForTest().keySet()));
- assertEquals(
- set(PackageWithUser.of(USER_10, LAUNCHER_2)),
- hashSet(user10.getAllLaunchersForTest().keySet()));
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
- "s0_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
- "s0_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_10),
- "s10_1");
- assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
- assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
- assertShortcutExists(CALLING_PACKAGE_1, "s10_1", USER_10);
- assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10);
-
- mService.saveDirtyInfo();
-
- // Remove the other launcher from user 10 too.
- uninstallPackage(USER_10, LAUNCHER_2);
- mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10);
-
- assertEquals(set(CALLING_PACKAGE_2),
- hashSet(user0.getAllPackagesForTest().keySet()));
- assertEquals(set(CALLING_PACKAGE_1),
- hashSet(user10.getAllPackagesForTest().keySet()));
- assertEquals(
- set(PackageWithUser.of(USER_0, LAUNCHER_1),
- PackageWithUser.of(USER_0, LAUNCHER_2)),
- hashSet(user0.getAllLaunchersForTest().keySet()));
- assertEquals(
- set(),
- hashSet(user10.getAllLaunchersForTest().keySet()));
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
- "s0_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
- "s0_2");
-
- // Note the pinned shortcuts on user-10 no longer referred, so they should both be removed.
- assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
- assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
- assertShortcutNotExists(CALLING_PACKAGE_1, "s10_1", USER_10);
- assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10);
-
- mService.saveDirtyInfo();
-
- // More remove.
- uninstallPackage(USER_10, CALLING_PACKAGE_1);
- mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10);
-
- assertEquals(set(CALLING_PACKAGE_2),
- hashSet(user0.getAllPackagesForTest().keySet()));
- assertEquals(set(),
- hashSet(user10.getAllPackagesForTest().keySet()));
- assertEquals(
- set(PackageWithUser.of(USER_0, LAUNCHER_1),
- PackageWithUser.of(USER_0, LAUNCHER_2)),
- hashSet(user0.getAllLaunchersForTest().keySet()));
- assertEquals(set(),
- hashSet(user10.getAllLaunchersForTest().keySet()));
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
- "s0_2");
- assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0),
- "s0_2");
-
- // Note the pinned shortcuts on user-10 no longer referred, so they should both be removed.
- assertShortcutNotExists(CALLING_PACKAGE_1, "s0_1", USER_0);
- assertShortcutExists(CALLING_PACKAGE_2, "s0_2", USER_0);
- assertShortcutNotExists(CALLING_PACKAGE_1, "s10_1", USER_10);
- assertShortcutNotExists(CALLING_PACKAGE_2, "s10_2", USER_10);
-
- mService.saveDirtyInfo();
- }
-
- public void testHandleGonePackage_crossProfile() {
- // Create some shortcuts.
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
- // Pin some.
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s1"), HANDLE_USER_0);
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s2"), UserHandle.of(USER_P0));
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
- list("s3"), HANDLE_USER_0);
- });
-
- runWithCaller(LAUNCHER_1, USER_P0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s2"), HANDLE_USER_0);
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s3"), UserHandle.of(USER_P0));
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2,
- list("s1"), HANDLE_USER_0);
- });
-
- runWithCaller(LAUNCHER_1, USER_10, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
- list("s3"), HANDLE_USER_10);
- });
-
- // Check the state.
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
- // Make sure all the information is persisted.
- mService.saveDirtyInfo();
- initService();
- mService.handleUnlockUser(USER_0);
- mService.handleUnlockUser(USER_P0);
- mService.handleUnlockUser(USER_10);
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
- // Start uninstalling.
- uninstallPackage(USER_10, LAUNCHER_1);
- mService.checkPackageChanges(USER_10);
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
- // Uninstall.
- uninstallPackage(USER_10, CALLING_PACKAGE_1);
- mService.checkPackageChanges(USER_10);
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
- uninstallPackage(USER_P0, LAUNCHER_1);
- mService.checkPackageChanges(USER_0);
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
- mService.checkPackageChanges(USER_P0);
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
- uninstallPackage(USER_P0, CALLING_PACKAGE_1);
-
- mService.saveDirtyInfo();
- initService();
- mService.handleUnlockUser(USER_0);
- mService.handleUnlockUser(USER_P0);
- mService.handleUnlockUser(USER_10);
-
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
- assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
- // Uninstall
- uninstallPackage(USER_0, LAUNCHER_1);
-
- mService.saveDirtyInfo();
- initService();
- mService.handleUnlockUser(USER_0);
- mService.handleUnlockUser(USER_P0);
- mService.handleUnlockUser(USER_10);
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
-
- uninstallPackage(USER_0, CALLING_PACKAGE_2);
-
- mService.saveDirtyInfo();
- initService();
- mService.handleUnlockUser(USER_0);
- mService.handleUnlockUser(USER_P0);
- mService.handleUnlockUser(USER_10);
-
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
- assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
-
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_P0));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_P0));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_P0));
-
- assertNull(getPackageShortcut(CALLING_PACKAGE_2, "s1", USER_0));
- assertNull(getPackageShortcut(CALLING_PACKAGE_2, "s2", USER_0));
- assertNull(getPackageShortcut(CALLING_PACKAGE_2, "s3", USER_0));
-
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_10));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
- assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
- }
-
- private void checkCanRestoreTo(boolean expected, ShortcutPackageInfo spi,
- int version, String... signatures) {
- assertEquals(expected, spi.canRestoreTo(mService, genPackage(
- "dummy", /* uid */ 0, version, signatures)));
- }
-
- public void testCanRestoreTo() {
- addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1");
- addPackage(CALLING_PACKAGE_2, CALLING_UID_1, 10, "sig1", "sig2");
-
- final ShortcutPackageInfo spi1 = ShortcutPackageInfo.generateForInstalledPackage(
- mService, CALLING_PACKAGE_1, USER_0);
- final ShortcutPackageInfo spi2 = ShortcutPackageInfo.generateForInstalledPackage(
- mService, CALLING_PACKAGE_2, USER_0);
-
- checkCanRestoreTo(true, spi1, 10, "sig1");
- checkCanRestoreTo(true, spi1, 10, "x", "sig1");
- checkCanRestoreTo(true, spi1, 10, "sig1", "y");
- checkCanRestoreTo(true, spi1, 10, "x", "sig1", "y");
- checkCanRestoreTo(true, spi1, 11, "sig1");
-
- checkCanRestoreTo(false, spi1, 10 /* empty */);
- checkCanRestoreTo(false, spi1, 10, "x");
- checkCanRestoreTo(false, spi1, 10, "x", "y");
- checkCanRestoreTo(false, spi1, 10, "x");
- checkCanRestoreTo(false, spi1, 9, "sig1");
-
- checkCanRestoreTo(true, spi2, 10, "sig1", "sig2");
- checkCanRestoreTo(true, spi2, 10, "sig2", "sig1");
- checkCanRestoreTo(true, spi2, 10, "x", "sig1", "sig2");
- checkCanRestoreTo(true, spi2, 10, "x", "sig2", "sig1");
- checkCanRestoreTo(true, spi2, 10, "sig1", "sig2", "y");
- checkCanRestoreTo(true, spi2, 10, "sig2", "sig1", "y");
- checkCanRestoreTo(true, spi2, 10, "x", "sig1", "sig2", "y");
- checkCanRestoreTo(true, spi2, 10, "x", "sig2", "sig1", "y");
- checkCanRestoreTo(true, spi2, 11, "x", "sig2", "sig1", "y");
-
- checkCanRestoreTo(false, spi2, 10, "sig1", "sig2x");
- checkCanRestoreTo(false, spi2, 10, "sig2", "sig1x");
- checkCanRestoreTo(false, spi2, 10, "x", "sig1x", "sig2");
- checkCanRestoreTo(false, spi2, 10, "x", "sig2x", "sig1");
- checkCanRestoreTo(false, spi2, 10, "sig1", "sig2x", "y");
- checkCanRestoreTo(false, spi2, 10, "sig2", "sig1x", "y");
- checkCanRestoreTo(false, spi2, 10, "x", "sig1x", "sig2", "y");
- checkCanRestoreTo(false, spi2, 10, "x", "sig2x", "sig1", "y");
- checkCanRestoreTo(false, spi2, 11, "x", "sig2x", "sig1", "y");
- }
-
- private boolean bitmapDirectoryExists(String packageName, int userId) {
- final File path = new File(mService.getUserBitmapFilePath(userId), packageName);
- return path.isDirectory();
- }
-
- public void testHandlePackageDelete() {
- final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
- getTestContext().getResources(), R.drawable.black_32x32));
- setCaller(CALLING_PACKAGE_1, USER_0);
- assertTrue(mManager.addDynamicShortcuts(list(
- makeShortcutWithIcon("s1", bmp32x32), makeShortcutWithIcon("s2", bmp32x32)
- )));
-
- setCaller(CALLING_PACKAGE_2, USER_0);
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
-
- setCaller(CALLING_PACKAGE_3, USER_0);
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
-
- setCaller(CALLING_PACKAGE_1, USER_10);
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
-
- setCaller(CALLING_PACKAGE_2, USER_10);
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
-
- setCaller(CALLING_PACKAGE_3, USER_10);
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
-
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
-
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
-
- uninstallPackage(USER_0, CALLING_PACKAGE_1);
- mService.mPackageMonitor.onReceive(getTestContext(),
- genPackageDeleteIntent(CALLING_PACKAGE_1, USER_0));
-
- assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
-
- assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
-
- uninstallPackage(USER_10, CALLING_PACKAGE_2);
- mService.mPackageMonitor.onReceive(getTestContext(),
- genPackageDeleteIntent(CALLING_PACKAGE_2, USER_10));
-
- assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
- assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
-
- assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
- assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
-
- mInjectedPackages.remove(CALLING_PACKAGE_1);
- mInjectedPackages.remove(CALLING_PACKAGE_3);
-
- mService.handleUnlockUser(USER_0);
-
- assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
- assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
- assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
-
- assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
- assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
- assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
-
- mService.handleUnlockUser(USER_10);
-
- assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
- assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
- assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
- assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
- assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
-
- assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
- assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
- assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
- assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
- assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
- }
-
- /** Almost ame as testHandlePackageDelete, except it doesn't uninstall packages. */
- public void testHandlePackageClearData() {
- final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
- getTestContext().getResources(), R.drawable.black_32x32));
- setCaller(CALLING_PACKAGE_1, USER_0);
- assertTrue(mManager.addDynamicShortcuts(list(
- makeShortcutWithIcon("s1", bmp32x32), makeShortcutWithIcon("s2", bmp32x32)
- )));
-
- setCaller(CALLING_PACKAGE_2, USER_0);
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
-
- setCaller(CALLING_PACKAGE_3, USER_0);
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
-
- setCaller(CALLING_PACKAGE_1, USER_10);
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
-
- setCaller(CALLING_PACKAGE_2, USER_10);
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
-
- setCaller(CALLING_PACKAGE_3, USER_10);
- assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
-
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
-
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
-
- mService.mPackageMonitor.onReceive(getTestContext(),
- genPackageDataClear(CALLING_PACKAGE_1, USER_0));
-
- assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
-
- assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
-
- mService.mPackageMonitor.onReceive(getTestContext(),
- genPackageDataClear(CALLING_PACKAGE_2, USER_10));
-
- assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_0));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_10));
- assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, "s1", USER_10));
- assertNotNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_3, "s1", USER_10));
-
- assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_0));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_1, USER_10));
- assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
- assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
- }
-
- public void testHandlePackageUpdate() throws Throwable {
-
- // Set up shortcuts and launchers.
-
- final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
- final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
- getTestContext().getResources(), R.drawable.black_32x32));
-
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"),
- makeShortcutWithIcon("s2", res32x32),
- makeShortcutWithIcon("s3", res32x32),
- makeShortcutWithIcon("s4", bmp32x32))));
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"),
- makeShortcutWithIcon("s2", bmp32x32))));
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcutWithIcon("s1", res32x32))));
- });
-
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcutWithIcon("s1", res32x32),
- makeShortcutWithIcon("s2", res32x32))));
- });
- runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcutWithIcon("s1", bmp32x32),
- makeShortcutWithIcon("s2", bmp32x32))));
- });
-
- LauncherApps.Callback c0 = mock(LauncherApps.Callback.class);
- LauncherApps.Callback c10 = mock(LauncherApps.Callback.class);
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.registerCallback(c0, new Handler(Looper.getMainLooper()));
- });
- runWithCaller(LAUNCHER_1, USER_10, () -> {
- mLauncherApps.registerCallback(c10, new Handler(Looper.getMainLooper()));
- });
-
- mInjectedCurrentTimeLillis = START_TIME + 100;
-
- ArgumentCaptor<List> shortcuts;
-
- // First, call the event without updating the versions.
- reset(c0);
- reset(c10);
-
- mService.mPackageMonitor.onReceive(getTestContext(),
- genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
- mService.mPackageMonitor.onReceive(getTestContext(),
- genPackageUpdateIntent(CALLING_PACKAGE_1, USER_10));
-
- waitOnMainThread();
-
- // Version not changed, so no callback.
- verify(c0, times(0)).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- any(List.class),
- any(UserHandle.class));
- verify(c10, times(0)).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- any(List.class),
- any(UserHandle.class));
-
- // Next, update the version info for package 1.
- reset(c0);
- reset(c10);
- updatePackageVersion(CALLING_PACKAGE_1, 1);
-
- // Then send the broadcast, to only user-0.
- mService.mPackageMonitor.onReceive(getTestContext(),
- genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
-
- waitOnMainThread();
-
- // User-0 should get the notification.
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- shortcuts.capture(),
- eq(HANDLE_USER_0));
-
- // User-10 shouldn't yet get the notification.
- verify(c10, times(0)).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- any(List.class),
- any(UserHandle.class));
- assertShortcutIds(shortcuts.getValue(), "s1", "s2", "s3", "s4");
- assertEquals(START_TIME,
- findShortcut(shortcuts.getValue(), "s1").getLastChangedTimestamp());
- assertEquals(START_TIME + 100,
- findShortcut(shortcuts.getValue(), "s2").getLastChangedTimestamp());
- assertEquals(START_TIME + 100,
- findShortcut(shortcuts.getValue(), "s3").getLastChangedTimestamp());
- assertEquals(START_TIME,
- findShortcut(shortcuts.getValue(), "s4").getLastChangedTimestamp());
-
- // Next, send unlock even on user-10. Now we scan packages on this user and send a
- // notification to the launcher.
- mInjectedCurrentTimeLillis = START_TIME + 200;
-
- when(mMockUserManager.isUserRunning(eq(USER_10))).thenReturn(true);
-
- reset(c0);
- reset(c10);
- mService.handleUnlockUser(USER_10);
-
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0, times(0)).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- any(List.class),
- any(UserHandle.class));
-
- verify(c10).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- shortcuts.capture(),
- eq(HANDLE_USER_10));
-
- assertShortcutIds(shortcuts.getValue(), "s1", "s2");
- assertEquals(START_TIME + 200,
- findShortcut(shortcuts.getValue(), "s1").getLastChangedTimestamp());
- assertEquals(START_TIME + 200,
- findShortcut(shortcuts.getValue(), "s2").getLastChangedTimestamp());
-
-
- // Do the same thing for package 2, which doesn't have resource icons.
- mInjectedCurrentTimeLillis = START_TIME + 300;
-
- reset(c0);
- reset(c10);
- updatePackageVersion(CALLING_PACKAGE_2, 10);
-
- // Then send the broadcast, to only user-0.
- mService.mPackageMonitor.onReceive(getTestContext(),
- genPackageUpdateIntent(CALLING_PACKAGE_2, USER_0));
- mService.handleUnlockUser(USER_10);
-
- waitOnMainThread();
-
- verify(c0, times(0)).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- any(List.class),
- any(UserHandle.class));
-
- verify(c10, times(0)).onShortcutsChanged(
- eq(CALLING_PACKAGE_1),
- any(List.class),
- any(UserHandle.class));
-
- // Do the same thing for package 3
- mInjectedCurrentTimeLillis = START_TIME + 400;
-
- reset(c0);
- reset(c10);
- updatePackageVersion(CALLING_PACKAGE_3, 100);
-
- // Then send the broadcast, to only user-0.
- mService.mPackageMonitor.onReceive(getTestContext(),
- genPackageUpdateIntent(CALLING_PACKAGE_3, USER_0));
- mService.handleUnlockUser(USER_10);
-
- waitOnMainThread();
-
- shortcuts = ArgumentCaptor.forClass(List.class);
- verify(c0).onShortcutsChanged(
- eq(CALLING_PACKAGE_3),
- shortcuts.capture(),
- eq(HANDLE_USER_0));
-
- // User 10 doesn't have package 3, so no callback.
- verify(c10, times(0)).onShortcutsChanged(
- eq(CALLING_PACKAGE_3),
- any(List.class),
- any(UserHandle.class));
-
- assertShortcutIds(shortcuts.getValue(), "s1");
- assertEquals(START_TIME + 400,
- findShortcut(shortcuts.getValue(), "s1").getLastChangedTimestamp());
- }
-
- private void backupAndRestore() {
- int prevUid = mInjectedCallingUid;
-
- mInjectedCallingUid = Process.SYSTEM_UID; // Only system can call it.
-
- dumpsysOnLogcat("Before backup");
-
- final byte[] payload = mService.getBackupPayload(USER_0);
- if (ENABLE_DUMP) {
- final String xml = new String(payload);
- Log.i(TAG, "Backup payload:");
- for (String line : xml.split("\n")) {
- Log.i(TAG, line);
- }
- }
-
- // Before doing anything else, uninstall all packages.
- for (int userId : list(USER_0, USER_P0)) {
- for (String pkg : list(CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3,
- LAUNCHER_1, LAUNCHER_2, LAUNCHER_3)) {
- uninstallPackage(userId, pkg);
- }
- }
-
- shutdownServices();
-
- deleteAllSavedFiles();
-
- initService();
- mService.applyRestore(payload, USER_0);
-
- // handleUnlockUser will perform the gone package check, but it shouldn't remove
- // shadow information.
- mService.handleUnlockUser(USER_0);
-
- dumpsysOnLogcat("After restore");
-
- mInjectedCallingUid = prevUid;
- }
-
- private void prepareCrossProfileDataSet() {
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
- makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
- makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
- makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
- });
- runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list()));
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
- makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("x1"), makeShortcut("x2"), makeShortcut("x3"),
- makeShortcut("x4"), makeShortcut("x5"), makeShortcut("x6"))));
- });
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s1", "s2"), HANDLE_USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s1", "s2", "s3"), HANDLE_USER_0);
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1", "s4"), HANDLE_USER_P0);
- });
- runWithCaller(LAUNCHER_2, USER_0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s2", "s3"), HANDLE_USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s2", "s3", "s4"), HANDLE_USER_0);
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2", "s5"), HANDLE_USER_P0);
- });
- runWithCaller(LAUNCHER_3, USER_0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4"), HANDLE_USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5"), HANDLE_USER_0);
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s6"), HANDLE_USER_P0);
- });
- runWithCaller(LAUNCHER_4, USER_0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list(), HANDLE_USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list(), HANDLE_USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list(), HANDLE_USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_4, list(), HANDLE_USER_0);
- });
-
- // Launcher on a managed profile is referring ot user 0!
- runWithCaller(LAUNCHER_1, USER_P0, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3", "s4"), HANDLE_USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("s3", "s4", "s5"), HANDLE_USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("s3", "s4", "s5", "s6"),
- HANDLE_USER_0);
-
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s4", "s1"), HANDLE_USER_P0);
- });
- runWithCaller(LAUNCHER_1, USER_10, () -> {
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("x4", "x5"), HANDLE_USER_10);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_2, list("x4", "x5", "x6"), HANDLE_USER_10);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_3, list("x4", "x5", "x6", "x1"),
- HANDLE_USER_10);
- });
-
- // Then remove some dynamic shortcuts.
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list()));
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("x1"), makeShortcut("x2"), makeShortcut("x3"))));
- });
- }
-
- private void prepareForBackupTest() {
-
- prepareCrossProfileDataSet();
-
- backupAndRestore();
- }
-
- private void assertExistsAndShadow(ShortcutPackageItem spi) {
- assertNotNull(spi);
- assertTrue(spi.getPackageInfo().isShadow());
- }
-
- /**
- * Make sure the backup data doesn't have the following information:
- * - Launchers on other users.
- * - Non-backup app information.
- *
- * But restores all other infomation.
- *
- * It also omits the following pieces of information, but that's tested in
- * {@link #testShortcutInfoSaveAndLoad_forBackup}.
- * - Unpinned dynamic shortcuts
- * - Bitmaps
- */
- public void testBackupAndRestore() {
- prepareForBackupTest();
-
- checkBackupAndRestore_success();
- }
-
- public void testBackupAndRestore_backupRestoreTwice() {
- prepareForBackupTest();
-
- // Note doing a backup & restore again here shouldn't affect the result.
- dumpsysOnLogcat("Before second backup");
-
- backupAndRestore();
-
- dumpsysOnLogcat("After second backup");
-
- checkBackupAndRestore_success();
- }
-
- public void testBackupAndRestore_backupRestoreMultiple() {
- prepareForBackupTest();
-
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
- // This also shouldn't affect the result.
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertTrue(mManager.setDynamicShortcuts(list(
- makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
- makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
- });
-
- backupAndRestore();
-
- checkBackupAndRestore_success();
- }
-
- public void testBackupAndRestore_restoreToNewVersion() {
- prepareForBackupTest();
-
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
- addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 2);
- addPackage(LAUNCHER_1, LAUNCHER_UID_1, 5);
-
- checkBackupAndRestore_success();
- }
-
- public void testBackupAndRestore_restoreToSuperSetSignatures() {
- prepareForBackupTest();
-
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
- // Change package signatures.
- addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1, "sigx", CALLING_PACKAGE_1);
- addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4, LAUNCHER_1, "sigy");
-
- checkBackupAndRestore_success();
- }
-
- private void checkBackupAndRestore_success() {
- // Make sure non-system user is not restored.
- final ShortcutUser userP0 = mService.getUserShortcutsLocked(USER_P0);
- assertEquals(0, userP0.getAllPackagesForTest().size());
- assertEquals(0, userP0.getAllLaunchersForTest().size());
-
- // Make sure only "allowBackup" apps are restored, and are shadow.
- final ShortcutUser user0 = mService.getUserShortcutsLocked(USER_0);
- assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_1));
- assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_2));
- assertExistsAndShadow(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_1)));
- assertExistsAndShadow(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_2)));
-
- assertNull(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
- assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
- assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
-
- installPackage(USER_0, CALLING_PACKAGE_1);
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertShortcutIds(assertAllPinned(
- mManager.getPinnedShortcuts()),
- "s1", "s2");
- });
-
- installPackage(USER_0, LAUNCHER_1);
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
- /* empty, not restored */ );
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty, not restored */ );
-
- assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
- });
-
- installPackage(USER_0, CALLING_PACKAGE_2);
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertShortcutIds(assertAllPinned(
- mManager.getPinnedShortcuts()),
- "s1", "s2", "s3");
- });
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s1", "s2");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty, not restored */ );
-
- assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
- });
-
- // 3 shouldn't be backed up, so no pinned shortcuts.
- installPackage(USER_0, CALLING_PACKAGE_3);
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertEquals(0, mManager.getPinnedShortcuts().size());
- });
-
- // Launcher on a different profile shouldn't be restored.
- runWithCaller(LAUNCHER_1, USER_P0, () -> {
- assertEquals(0,
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)
- .size());
- assertEquals(0,
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)
- .size());
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* wasn't restored, so still empty */ );
- });
-
- // Package on a different profile, no restore.
- installPackage(USER_P0, CALLING_PACKAGE_1);
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertEquals(0, mManager.getPinnedShortcuts().size());
- });
-
- // Restore launcher 2 on user 0.
- installPackage(USER_0, LAUNCHER_2);
- runWithCaller(LAUNCHER_2, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
- "s2");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s2", "s3");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* wasn't restored, so still empty */ );
-
- assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
- });
-
-
- // Restoration of launcher2 shouldn't affect other packages; so do the same checks and
- // make sure they still have the same result.
- installPackage(USER_0, CALLING_PACKAGE_1);
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertShortcutIds(assertAllPinned(
- mManager.getPinnedShortcuts()),
- "s1", "s2");
- });
-
- installPackage(USER_0, LAUNCHER_1);
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
- "s1");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s1", "s2");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* wasn't restored, so still empty */ );
-
- assertEquals(0, mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0).size());
- });
-
- installPackage(USER_0, CALLING_PACKAGE_2);
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertShortcutIds(assertAllPinned(
- mManager.getPinnedShortcuts()),
- "s1", "s2", "s3");
- });
- }
-
- public void testBackupAndRestore_publisherLowerVersion() {
- prepareForBackupTest();
-
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
- addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 0); // Lower version
-
- checkBackupAndRestore_publisherNotRestored();
- }
-
- public void testBackupAndRestore_publisherWrongSignature() {
- prepareForBackupTest();
-
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
- addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sigx"); // different signature
-
- checkBackupAndRestore_publisherNotRestored();
- }
-
- public void testBackupAndRestore_publisherNoLongerBackupTarget() {
- prepareForBackupTest();
-
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
- updatePackageInfo(CALLING_PACKAGE_1,
- pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
-
- checkBackupAndRestore_publisherNotRestored();
- }
-
- private void checkBackupAndRestore_publisherNotRestored() {
- installPackage(USER_0, CALLING_PACKAGE_1);
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertEquals(0, mManager.getPinnedShortcuts().size());
- });
-
- installPackage(USER_0, CALLING_PACKAGE_2);
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertShortcutIds(assertAllPinned(
- mManager.getPinnedShortcuts()),
- "s1", "s2", "s3");
- });
-
- installPackage(USER_0, LAUNCHER_1);
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s1", "s2");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
- });
- installPackage(USER_0, LAUNCHER_2);
- runWithCaller(LAUNCHER_2, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s2", "s3");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
- });
-
- installPackage(USER_0, CALLING_PACKAGE_3);
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertEquals(0, mManager.getPinnedShortcuts().size());
- });
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s1", "s2");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
- });
- runWithCaller(LAUNCHER_2, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s2", "s3");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
- });
- }
-
- public void testBackupAndRestore_launcherLowerVersion() {
- prepareForBackupTest();
-
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
- addPackage(LAUNCHER_1, LAUNCHER_UID_1, 0); // Lower version
-
- checkBackupAndRestore_launcherNotRestored();
- }
-
- public void testBackupAndRestore_launcherWrongSignature() {
- prepareForBackupTest();
-
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
- addPackage(LAUNCHER_1, LAUNCHER_UID_1, 10, "sigx"); // different signature
-
- checkBackupAndRestore_launcherNotRestored();
- }
-
- public void testBackupAndRestore_launcherNoLongerBackupTarget() {
- prepareForBackupTest();
-
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
- updatePackageInfo(LAUNCHER_1,
- pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
-
- checkBackupAndRestore_launcherNotRestored();
- }
-
- private void checkBackupAndRestore_launcherNotRestored() {
- installPackage(USER_0, CALLING_PACKAGE_1);
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
-
- // s1 was pinned by launcher 1, which is not restored, yet, so we still see "s1" here.
- assertShortcutIds(assertAllPinned(
- mManager.getPinnedShortcuts()),
- "s1", "s2");
- });
-
- installPackage(USER_0, CALLING_PACKAGE_2);
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertShortcutIds(assertAllPinned(
- mManager.getPinnedShortcuts()),
- "s1", "s2", "s3");
- });
-
- // Now we try to restore launcher 1. Then we realize it's not restorable, so L1 has no pinned
- // shortcuts.
- installPackage(USER_0, LAUNCHER_1);
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
- });
-
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
-
- // Now CALLING_PACKAGE_1 realizes "s1" is no longer pinned.
- assertShortcutIds(assertAllPinned(
- mManager.getPinnedShortcuts()),
- "s2");
- });
-
- installPackage(USER_0, LAUNCHER_2);
- runWithCaller(LAUNCHER_2, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
- "s2");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s2", "s3");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
- });
-
- installPackage(USER_0, CALLING_PACKAGE_3);
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertEquals(0, mManager.getPinnedShortcuts().size());
- });
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
- });
- runWithCaller(LAUNCHER_2, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0)),
- "s2");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s2", "s3");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
- });
- }
-
- public void testBackupAndRestore_launcherAndPackageNoLongerBackupTarget() {
- prepareForBackupTest();
-
- // Note doing a backup & restore again here shouldn't affect the result.
- backupAndRestore();
-
- updatePackageInfo(CALLING_PACKAGE_1,
- pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
-
- updatePackageInfo(LAUNCHER_1,
- pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
-
- checkBackupAndRestore_publisherAndLauncherNotRestored();
- }
-
- private void checkBackupAndRestore_publisherAndLauncherNotRestored() {
- installPackage(USER_0, CALLING_PACKAGE_1);
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertEquals(0, mManager.getPinnedShortcuts().size());
- });
-
- installPackage(USER_0, CALLING_PACKAGE_2);
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertShortcutIds(assertAllPinned(
- mManager.getPinnedShortcuts()),
- "s1", "s2", "s3");
- });
-
- installPackage(USER_0, LAUNCHER_1);
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
- });
- installPackage(USER_0, LAUNCHER_2);
- runWithCaller(LAUNCHER_2, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s2", "s3");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
- });
-
- // Because launcher 1 wasn't restored, "s1" is no longer pinned.
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertShortcutIds(assertAllPinned(
- mManager.getPinnedShortcuts()),
- "s2", "s3");
- });
-
- installPackage(USER_0, CALLING_PACKAGE_3);
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- assertEquals(0, mManager.getDynamicShortcuts().size());
- assertEquals(0, mManager.getPinnedShortcuts().size());
- });
-
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
- });
- runWithCaller(LAUNCHER_2, USER_0, () -> {
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
- /* empty */);
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
- "s2", "s3");
- assertShortcutIds(assertAllPinned(
- mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
- /* empty */);
- });
- }
-
- public void testSaveAndLoad_crossProfile() {
- prepareCrossProfileDataSet();
-
- dumpsysOnLogcat("Before save & load");
-
- mService.saveDirtyInfo();
- initService();
-
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
- "s1", "s2", "s3", "s4");
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
- "s1", "s2", "s3", "s4", "s5");
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
- "s1", "s2", "s3", "s4", "s5", "s6");
- });
- runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
- assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts())
- /* empty */);
- assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts())
- /* empty */);
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
- "s1", "s2", "s3");
- assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
- "s1", "s2", "s3", "s4", "s5", "s6");
- });
- runWithCaller(CALLING_PACKAGE_2, USER_P0, () -> {
- assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts())
- /* empty */);
- assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts())
- /* empty */);
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertShortcutIds(assertAllDynamic(mManager.getDynamicShortcuts()),
- "x1", "x2", "x3");
- assertShortcutIds(assertAllPinned(mManager.getPinnedShortcuts()),
- "x4", "x5");
- });
- runWithCaller(LAUNCHER_1, USER_0, () -> {
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
- "s1");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
- "s1", "s2");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
- "s1", "s2", "s3");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
- /* empty */);
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
- "s1", "s4");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
- /* empty */);
- assertExpectException(
- SecurityException.class, "", () -> {
- mLauncherApps.getShortcuts(
- buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10);
- });
- });
- runWithCaller(LAUNCHER_2, USER_0, () -> {
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
- "s2");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
- "s2", "s3");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
- "s2", "s3", "s4");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
- /* empty */);
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
- "s2", "s5");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
- /* empty */);
- });
- runWithCaller(LAUNCHER_3, USER_0, () -> {
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
- "s3");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
- "s3", "s4");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
- "s3", "s4", "s5");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
- /* empty */);
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
- "s3", "s6");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
- /* empty */);
- });
- runWithCaller(LAUNCHER_4, USER_0, () -> {
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0)
- /* empty */);
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0)
- /* empty */);
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0)
- /* empty */);
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_4), HANDLE_USER_0)
- /* empty */);
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0)
- /* empty */);
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_P0)
- /* empty */);
- });
- runWithCaller(LAUNCHER_1, USER_P0, () -> {
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_0),
- "s3", "s4");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_0),
- "s3", "s4", "s5");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_0),
- "s3", "s4", "s5", "s6");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_P0),
- "s1", "s4");
- assertExpectException(
- SecurityException.class, "unrelated profile", () -> {
- mLauncherApps.getShortcuts(
- buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_10);
- });
- });
- runWithCaller(LAUNCHER_1, USER_10, () -> {
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_1), HANDLE_USER_10),
- "x4", "x5");
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_2), HANDLE_USER_10)
- /* empty */);
- assertShortcutIds(
- mLauncherApps.getShortcuts(buildPinnedQuery(CALLING_PACKAGE_3), HANDLE_USER_10)
- /* empty */);
- assertExpectException(
- SecurityException.class, "unrelated profile", () -> {
- mLauncherApps.getShortcuts(
- buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0);
- });
- assertExpectException(
- SecurityException.class, "unrelated profile", () -> {
- mLauncherApps.getShortcuts(
- buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_P0);
- });
- });
- }
-
- public void testThrottling_localeChanges() {
- prepareCrossProfileDataSet();
-
- dumpsysOnLogcat("Before save & load");
-
- mService.saveDirtyInfo();
- initService();
-
- final long origSequenceNumber = mService.getLocaleChangeSequenceNumber();
-
- mInternal.onSystemLocaleChangedNoLock();
-
- assertEquals(origSequenceNumber + 1, mService.getLocaleChangeSequenceNumber());
-
- // Note at this point only user-0 is loaded, and the counters are reset for this user,
- // but it will work for other users too, because we persist when
-
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
-
- mService.saveDirtyInfo();
- initService();
-
- // Make sure the counter is persisted.
- assertEquals(origSequenceNumber + 1, mService.getLocaleChangeSequenceNumber());
- }
-
- public void testThrottling_foreground() throws Exception {
- prepareCrossProfileDataSet();
-
- dumpsysOnLogcat("Before save & load");
-
- mService.saveDirtyInfo();
- initService();
-
- // We need to update the current time from time to time, since some of the internal checks
- // rely on the time being correctly incremented.
- mInjectedCurrentTimeLillis++;
-
- // First, all packages have less than 3 (== initial value) remaining calls.
-
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
-
- mInjectedCurrentTimeLillis++;
-
- // State changed, but not foreground, so no resetting.
- mService.mUidObserver.onUidStateChanged(
- CALLING_UID_1, ActivityManager.PROCESS_STATE_TOP_SLEEPING);
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
-
- mInjectedCurrentTimeLillis++;
-
- // State changed, package1 foreground, reset.
- mService.mUidObserver.onUidStateChanged(
- CALLING_UID_1, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- mService.mUidObserver.onUidStateChanged(
- CALLING_UID_1, ActivityManager.PROCESS_STATE_TOP_SLEEPING);
-
- mInjectedCurrentTimeLillis++;
-
- // Different app comes to foreground briefly, and goes back to background.
- // Now, make sure package 2's counter is reset, even in this case.
- mService.mUidObserver.onUidStateChanged(
- CALLING_UID_2, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
- mService.mUidObserver.onUidStateChanged(
- CALLING_UID_2, ActivityManager.PROCESS_STATE_TOP_SLEEPING);
-
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
-
- mInjectedCurrentTimeLillis++;
-
- // Do the same thing one more time. This would catch the bug with mixuing up
- // the current time and the elapsed time.
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- mManager.updateShortcuts(list(makeShortcut("s")));
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
-
- mService.mUidObserver.onUidStateChanged(
- CALLING_UID_2, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
- mService.mUidObserver.onUidStateChanged(
- CALLING_UID_2, ActivityManager.PROCESS_STATE_TOP_SLEEPING);
-
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
-
- mInjectedCurrentTimeLillis++;
-
- // Package 1 on user-10 comes to foreground.
- // Now, also try calling some APIs and make sure foreground apps don't get throttled.
- mService.mUidObserver.onUidStateChanged(
- UserHandle.getUid(USER_10, CALLING_UID_1),
- ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
-
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
-
- assertEquals(0, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
-
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
-
- assertEquals(0, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
-
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
-
- assertEquals(0, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
-
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
-
- assertEquals(0, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
-
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
-
- assertEquals(0, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
-
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
- mManager.setDynamicShortcuts(list(makeShortcut("s")));
-
- assertEquals(3, mManager.getRemainingCallCount()); // Still 3!
- });
- }
-
-
- public void testThrottling_resetByInternalCall() throws Exception {
- prepareCrossProfileDataSet();
-
- dumpsysOnLogcat("Before save & load");
-
- mService.saveDirtyInfo();
- initService();
-
- // First, all packages have less than 3 (== initial value) remaining calls.
-
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
-
- // Simulate a call from sys UI.
- mCallerPermissions.add(permission.RESET_SHORTCUT_MANAGER_THROTTLING);
- mService.onApplicationActive(CALLING_PACKAGE_1, USER_0);
-
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
-
- mService.onApplicationActive(CALLING_PACKAGE_3, USER_0);
-
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
-
- mService.onApplicationActive(CALLING_PACKAGE_1, USER_10);
-
- runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_4, USER_0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
- MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount());
- });
- runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
- assertEquals(3, mManager.getRemainingCallCount());
- });
- }
-
- public void testOnApplicationActive_permission() {
- assertExpectException(SecurityException.class, "Missing permission", () ->
- mService.onApplicationActive(CALLING_PACKAGE_1, USER_0));
-
- // Has permission, now it should pass.
- mCallerPermissions.add(permission.RESET_SHORTCUT_MANAGER_THROTTLING);
- mService.onApplicationActive(CALLING_PACKAGE_1, USER_0);
- }
-
- // ShortcutInfo tests
-
- public void testShortcutInfoMissingMandatoryFields() {
- assertExpectException(
- IllegalArgumentException.class,
- "ID must be provided",
- () -> new ShortcutInfo.Builder(getTestContext()).build());
- assertExpectException(
- IllegalArgumentException.class,
- "title must be provided",
- () -> new ShortcutInfo.Builder(getTestContext()).setId("id").build()
- .enforceMandatoryFields());
- assertExpectException(
- NullPointerException.class,
- "Intent must be provided",
- () -> new ShortcutInfo.Builder(getTestContext()).setId("id").setTitle("x").build()
- .enforceMandatoryFields());
- }
-
- public void testShortcutInfoParcel() {
- setCaller(CALLING_PACKAGE_1, USER_10);
- ShortcutInfo si = parceled(new ShortcutInfo.Builder(mClientContext)
- .setId("id")
- .setTitle("title")
- .setIntent(makeIntent("action", ShortcutActivity.class))
- .build());
- assertEquals(mClientContext.getPackageName(), si.getPackageName());
- assertEquals(USER_10, si.getUserId());
- assertEquals(HANDLE_USER_10, si.getUserHandle());
- assertEquals("id", si.getId());
- assertEquals("title", si.getTitle());
- assertEquals("action", si.getIntent().getAction());
-
- PersistableBundle pb = new PersistableBundle();
- pb.putInt("k", 1);
-
- si = new ShortcutInfo.Builder(getTestContext())
- .setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
- .setIcon(Icon.createWithResource(mClientContext, 123))
- .setTitle("title")
- .setText("text")
- .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
- .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
- .setWeight(123)
- .setExtras(pb)
- .build();
- si.addFlags(ShortcutInfo.FLAG_PINNED);
- si.setBitmapPath("abc");
- si.setIconResourceId(456);
-
- si = parceled(si);
-
- assertEquals(getTestContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
- assertEquals(123, si.getIcon().getResId());
- assertEquals("title", si.getTitle());
- assertEquals("text", si.getText());
- assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
- assertEquals("action", si.getIntent().getAction());
- assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getWeight());
- assertEquals(1, si.getExtras().getInt("k"));
-
- assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
- assertEquals("abc", si.getBitmapPath());
- assertEquals(456, si.getIconResourceId());
- }
-
- public void testShortcutInfoClone() {
- setCaller(CALLING_PACKAGE_1, USER_11);
-
- PersistableBundle pb = new PersistableBundle();
- pb.putInt("k", 1);
- ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
- .setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
- .setIcon(Icon.createWithResource(mClientContext, 123))
- .setTitle("title")
- .setText("text")
- .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
- .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
- .setWeight(123)
- .setExtras(pb)
- .build();
- sorig.addFlags(ShortcutInfo.FLAG_PINNED);
- sorig.setBitmapPath("abc");
- sorig.setIconResourceId(456);
-
- ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
-
- assertEquals(USER_11, si.getUserId());
- assertEquals(HANDLE_USER_11, si.getUserHandle());
- assertEquals(mClientContext.getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
- assertEquals(123, si.getIcon().getResId());
- assertEquals("title", si.getTitle());
- assertEquals("text", si.getText());
- assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
- assertEquals("action", si.getIntent().getAction());
- assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getWeight());
- assertEquals(1, si.getExtras().getInt("k"));
-
- assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
- assertEquals("abc", si.getBitmapPath());
- assertEquals(456, si.getIconResourceId());
-
- si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
-
- assertEquals(mClientContext.getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
- assertEquals(null, si.getIcon());
- assertEquals("title", si.getTitle());
- assertEquals("text", si.getText());
- assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
- assertEquals("action", si.getIntent().getAction());
- assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getWeight());
- assertEquals(1, si.getExtras().getInt("k"));
-
- assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
- assertEquals(null, si.getBitmapPath());
-
- assertEquals(456, si.getIconResourceId());
-
- si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
-
- assertEquals(mClientContext.getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
- assertEquals(null, si.getIcon());
- assertEquals("title", si.getTitle());
- assertEquals("text", si.getText());
- assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
- assertEquals(null, si.getIntent());
- assertEquals(123, si.getWeight());
- assertEquals(1, si.getExtras().getInt("k"));
-
- assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
- assertEquals(null, si.getBitmapPath());
-
- assertEquals(456, si.getIconResourceId());
-
- si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
-
- assertEquals(mClientContext.getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(null, si.getActivityComponent());
- assertEquals(null, si.getIcon());
- assertEquals(null, si.getTitle());
- assertEquals(null, si.getText());
- assertEquals(null, si.getCategories());
- assertEquals(null, si.getIntent());
- assertEquals(0, si.getWeight());
- assertEquals(null, si.getExtras());
-
- assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags());
- assertEquals(null, si.getBitmapPath());
-
- assertEquals(456, si.getIconResourceId());
- }
-
- public void testShortcutInfoClone_minimum() {
- PersistableBundle pb = new PersistableBundle();
- pb.putInt("k", 1);
- ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
- .setId("id")
- .setTitle("title")
- .setIntent(makeIntent("action", ShortcutActivity.class))
- .build();
- ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
-
- assertEquals(getTestContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals("title", si.getTitle());
- assertEquals("action", si.getIntent().getAction());
- assertEquals(null, si.getCategories());
-
- si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
-
- assertEquals(getTestContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals("title", si.getTitle());
- assertEquals("action", si.getIntent().getAction());
- assertEquals(null, si.getCategories());
-
- si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
-
- assertEquals(getTestContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals("title", si.getTitle());
- assertEquals(null, si.getIntent());
- assertEquals(null, si.getCategories());
-
- si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
-
- assertEquals(getTestContext().getPackageName(), si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(null, si.getTitle());
- assertEquals(null, si.getIntent());
- assertEquals(null, si.getCategories());
- }
-
- public void testShortcutInfoCopyNonNullFieldsFrom() throws InterruptedException {
- PersistableBundle pb = new PersistableBundle();
- pb.putInt("k", 1);
- ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext())
- .setId("id")
- .setActivityComponent(new ComponentName("a", "b"))
- .setIcon(Icon.createWithResource(mClientContext, 123))
- .setTitle("title")
- .setText("text")
- .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
- .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
- .setWeight(123)
- .setExtras(pb)
- .build();
- sorig.addFlags(ShortcutInfo.FLAG_PINNED);
- sorig.setBitmapPath("abc");
- sorig.setIconResourceId(456);
-
- ShortcutInfo si;
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setActivityComponent(new ComponentName("x", "y")).build());
- assertEquals("text", si.getText());
- assertEquals(new ComponentName("x", "y"), si.getActivityComponent());
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setIcon(Icon.createWithResource(mClientContext, 456)).build());
- assertEquals("text", si.getText());
- assertEquals(456, si.getIcon().getResId());
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setTitle("xyz").build());
- assertEquals("text", si.getText());
- assertEquals("xyz", si.getTitle());
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setText("xxx").build());
- assertEquals(123, si.getWeight());
- assertEquals("xxx", si.getText());
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setCategories(set()).build());
- assertEquals("text", si.getText());
- assertEquals(set(), si.getCategories());
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setCategories(set("x")).build());
- assertEquals("text", si.getText());
- assertEquals(set("x"), si.getCategories());
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setIntent(makeIntent("action2", ShortcutActivity.class)).build());
- assertEquals("text", si.getText());
- assertEquals("action2", si.getIntent().getAction());
- assertEquals(null, si.getIntent().getStringExtra("key"));
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setIntent(makeIntent("action3", ShortcutActivity.class, "key", "x")).build());
- assertEquals("text", si.getText());
- assertEquals("action3", si.getIntent().getAction());
- assertEquals("x", si.getIntent().getStringExtra("key"));
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setWeight(999).build());
- assertEquals("text", si.getText());
- assertEquals(999, si.getWeight());
-
-
- PersistableBundle pb2 = new PersistableBundle();
- pb2.putInt("x", 99);
-
- si = sorig.clone(/* flags=*/ 0);
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setExtras(pb2).build());
- assertEquals("text", si.getText());
- assertEquals(99, si.getExtras().getInt("x"));
-
- // Make sure the timestamp gets updated too.
-
- final long timestamp = si.getLastChangedTimestamp();
- Thread.sleep(2);
-
- si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
- .setTitle("xyz").build());
-
- assertTrue(si.getLastChangedTimestamp() > timestamp);
- }
-
- public void testShortcutInfoSaveAndLoad() throws InterruptedException {
- setCaller(CALLING_PACKAGE_1, USER_10);
-
- final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
- getTestContext().getResources(), R.drawable.black_32x32));
-
- PersistableBundle pb = new PersistableBundle();
- pb.putInt("k", 1);
- ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
- .setId("id")
- .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
- .setIcon(bmp32x32)
- .setTitle("title")
- .setText("text")
- .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
- .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
- .setWeight(123)
- .setExtras(pb)
- .build();
-
- mManager.addDynamicShortcuts(list(sorig));
-
- Thread.sleep(2);
- final long now = System.currentTimeMillis();
-
- // Save and load.
- mService.saveDirtyInfo();
- initService();
- mService.handleUnlockUser(USER_10);
-
- ShortcutInfo si;
- si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_10);
-
- assertEquals(USER_10, si.getUserId());
- assertEquals(HANDLE_USER_10, si.getUserHandle());
- assertEquals(CALLING_PACKAGE_1, si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
- assertEquals(null, si.getIcon());
- assertEquals("title", si.getTitle());
- assertEquals("text", si.getText());
- assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
- assertEquals("action", si.getIntent().getAction());
- assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getWeight());
- assertEquals(1, si.getExtras().getInt("k"));
-
- assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE, si.getFlags());
- assertNotNull(si.getBitmapPath()); // Something should be set.
- assertEquals(0, si.getIconResourceId());
- assertTrue(si.getLastChangedTimestamp() < now);
- }
-
- public void testShortcutInfoSaveAndLoad_forBackup() {
- setCaller(CALLING_PACKAGE_1, USER_0);
-
- final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
- getTestContext().getResources(), R.drawable.black_32x32));
-
- PersistableBundle pb = new PersistableBundle();
- pb.putInt("k", 1);
- ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext)
- .setId("id")
- .setActivityComponent(new ComponentName(mClientContext, ShortcutActivity2.class))
- .setIcon(bmp32x32)
- .setTitle("title")
- .setText("text")
- .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
- .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
- .setWeight(123)
- .setExtras(pb)
- .build();
-
- mManager.addDynamicShortcuts(list(sorig));
-
- // Dynamic shortcuts won't be backed up, so we need to pin it.
- setCaller(LAUNCHER_1, USER_0);
- mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id"), HANDLE_USER_0);
-
- // Do backup & restore.
- backupAndRestore();
-
- mService.handleUnlockUser(USER_0); // Load user-0.
-
- ShortcutInfo si;
- si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_0);
-
- assertEquals(CALLING_PACKAGE_1, si.getPackageName());
- assertEquals("id", si.getId());
- assertEquals(ShortcutActivity2.class.getName(), si.getActivityComponent().getClassName());
- assertEquals(null, si.getIcon());
- assertEquals("title", si.getTitle());
- assertEquals("text", si.getText());
- assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
- assertEquals("action", si.getIntent().getAction());
- assertEquals("val", si.getIntent().getStringExtra("key"));
- assertEquals(123, si.getWeight());
- assertEquals(1, si.getExtras().getInt("k"));
-
- assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
- assertNull(si.getBitmapPath()); // No icon.
- assertEquals(0, si.getIconResourceId());
- }
-
- public void testDumpsys_crossProfile() {
- prepareCrossProfileDataSet();
- dumpsysOnLogcat("test1", /* force= */ true);
- }
-
- public void testDumpsys_withIcons() throws IOException {
- testIcons();
- // Dump after having some icons.
- dumpsysOnLogcat("test1", /* force= */ true);
- }
-}
diff --git a/services/tests/shortcutmanagerutils/Android.mk b/services/tests/shortcutmanagerutils/Android.mk
deleted file mode 100644
index 701e058..0000000
--- a/services/tests/shortcutmanagerutils/Android.mk
+++ /dev/null
@@ -1,31 +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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, src)
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- mockito-target
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE := ShortcutManagerTestUtils
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
deleted file mode 100644
index ad49c2f..0000000
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ /dev/null
@@ -1,516 +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.
- */
-package com.android.server.pm.shortcutmanagertest;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyList;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.app.Instrumentation;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.BaseBundle;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.UserHandle;
-import android.test.MoreAsserts;
-import android.util.Log;
-
-import junit.framework.Assert;
-
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
-import org.mockito.Mockito;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.function.BooleanSupplier;
-import java.util.function.Function;
-import java.util.function.Predicate;
-
-public class ShortcutManagerTestUtils {
- private static final String TAG = "ShortcutManagerUtils";
-
- private static final boolean ENABLE_DUMPSYS = true; // DO NOT SUBMIT WITH true
-
- private static final int STANDARD_TIMEOUT_SEC = 5;
-
- private ShortcutManagerTestUtils() {
- }
-
- private static List<String> readAll(ParcelFileDescriptor pfd) {
- try {
- try {
- final ArrayList<String> ret = new ArrayList<>();
- try (BufferedReader r = new BufferedReader(
- new FileReader(pfd.getFileDescriptor()))) {
- String line;
- while ((line = r.readLine()) != null) {
- ret.add(line);
- }
- r.readLine();
- }
- return ret;
- } finally {
- pfd.close();
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private static String concatResult(List<String> result) {
- final StringBuilder sb = new StringBuilder();
- for (String s : result) {
- sb.append(s);
- sb.append("\n");
- }
- return sb.toString();
- }
-
- private static List<String> runCommand(Instrumentation instrumentation, String command) {
- return runCommand(instrumentation, command, null);
- }
- private static List<String> runCommand(Instrumentation instrumentation, String command,
- Predicate<List<String>> resultAsserter) {
- Log.d(TAG, "Running command: " + command);
- final List<String> result;
- try {
- result = readAll(
- instrumentation.getUiAutomation().executeShellCommand(command));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- if (resultAsserter != null && !resultAsserter.test(result)) {
- fail("Command '" + command + "' failed, output was:\n" + concatResult(result));
- }
- return result;
- }
-
- private static void runCommandForNoOutput(Instrumentation instrumentation, String command) {
- runCommand(instrumentation, command, result -> result.size() == 0);
- }
-
- private static List<String> runShortcutCommand(Instrumentation instrumentation, String command,
- Predicate<List<String>> resultAsserter) {
- return runCommand(instrumentation, "cmd shortcut " + command, resultAsserter);
- }
-
- public static List<String> runShortcutCommandForSuccess(Instrumentation instrumentation,
- String command) {
- return runShortcutCommand(instrumentation, command, result -> result.contains("Success"));
- }
-
- public static String getDefaultLauncher(Instrumentation instrumentation) {
- final String PREFIX = "Launcher: ComponentInfo{";
- final String POSTFIX = "}";
- final List<String> result = runShortcutCommandForSuccess(
- instrumentation, "get-default-launcher");
- for (String s : result) {
- if (s.startsWith(PREFIX) && s.endsWith(POSTFIX)) {
- return s.substring(PREFIX.length(), s.length() - POSTFIX.length());
- }
- }
- fail("Default launcher not found");
- return null;
- }
-
- public static void setDefaultLauncher(Instrumentation instrumentation, String component) {
- runCommandForNoOutput(instrumentation, "cmd package set-home-activity " + component);
- }
-
- public static void setDefaultLauncher(Instrumentation instrumentation, Context packageContext) {
- setDefaultLauncher(instrumentation, packageContext.getPackageName()
- + "/android.content.pm.cts.shortcutmanager.packages.Launcher");
- }
-
- public static void overrideConfig(Instrumentation instrumentation, String config) {
- runShortcutCommandForSuccess(instrumentation, "override-config " + config);
- }
-
- public static void resetConfig(Instrumentation instrumentation) {
- runShortcutCommandForSuccess(instrumentation, "reset-config");
- }
-
- public static void resetThrottling(Instrumentation instrumentation) {
- runShortcutCommandForSuccess(instrumentation, "reset-throttling");
- }
-
- public static void resetAllThrottling(Instrumentation instrumentation) {
- runShortcutCommandForSuccess(instrumentation, "reset-all-throttling");
- }
-
- public static void clearShortcuts(Instrumentation instrumentation, int userId,
- String packageName) {
- runShortcutCommandForSuccess(instrumentation, "clear-shortcuts "
- + " --user " + userId + " " + packageName);
- }
-
- public static void dumpsysShortcut(Instrumentation instrumentation) {
- if (!ENABLE_DUMPSYS) {
- return;
- }
- for (String s : runCommand(instrumentation, "dumpsys shortcut")) {
- Log.e(TAG, s);
- }
- }
-
- public static Bundle makeBundle(Object... keysAndValues) {
- assertTrue((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;
- }
-
- public static <T> List<T> list(T... array) {
- return Arrays.asList(array);
- }
-
- public static <T> Set<T> hashSet(Set<T> in) {
- return new HashSet<T>(in);
- }
-
- public static <T> Set<T> set(T... values) {
- return set(v -> v, values);
- }
-
- public static <T, V> Set<T> set(Function<V, T> converter, V... values) {
- return set(converter, Arrays.asList(values));
- }
-
- public static <T, V> Set<T> set(Function<V, T> converter, List<V> values) {
- final HashSet<T> ret = new HashSet<>();
- for (V v : values) {
- ret.add(converter.apply(v));
- }
- return ret;
- }
-
- public static void resetAll(Collection<?> mocks) {
- for (Object o : mocks) {
- reset(o);
- }
- }
- public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
- String expectedExceptionMessageRegex, Runnable r) {
- assertExpectException("", expectedExceptionType, expectedExceptionMessageRegex, r);
- }
-
- public static void assertDynamicShortcutCountExceeded(Runnable r) {
- assertExpectException(IllegalArgumentException.class,
- "Max number of dynamic shortcuts exceeded", r);
- }
-
- public static void assertExpectException(String message,
- Class<? extends Throwable> expectedExceptionType,
- String expectedExceptionMessageRegex, Runnable r) {
- try {
- r.run();
- Assert.fail("Expected exception type " + expectedExceptionType.getName()
- + " was not thrown (message=" + message + ")");
- } catch (Throwable e) {
- Assert.assertTrue(
- "Expected exception type was " + expectedExceptionType.getName()
- + " but caught " + e + " (message=" + message + ")",
- expectedExceptionType.isAssignableFrom(e.getClass()));
- if (expectedExceptionMessageRegex != null) {
- MoreAsserts.assertContainsRegex(expectedExceptionMessageRegex, e.getMessage());
- }
- }
- }
-
- public static List<ShortcutInfo> assertShortcutIds(List<ShortcutInfo> actualShortcuts,
- String... expectedIds) {
- final HashSet<String> expected = new HashSet<>(list(expectedIds));
- final HashSet<String> actual = new HashSet<>();
- for (ShortcutInfo s : actualShortcuts) {
- actual.add(s.getId());
- }
-
- // Compare the sets.
- assertEquals(expected, actual);
- return actualShortcuts;
- }
-
- public static List<ShortcutInfo> assertAllHaveIntents(
- List<ShortcutInfo> actualShortcuts) {
- for (ShortcutInfo s : actualShortcuts) {
- assertNotNull("ID " + s.getId(), s.getIntent());
- }
- return actualShortcuts;
- }
-
- public static List<ShortcutInfo> assertAllNotHaveIntents(
- List<ShortcutInfo> actualShortcuts) {
- for (ShortcutInfo s : actualShortcuts) {
- assertNull("ID " + s.getId(), s.getIntent());
- }
- return actualShortcuts;
- }
-
- public static List<ShortcutInfo> assertAllHaveTitle(
- List<ShortcutInfo> actualShortcuts) {
- for (ShortcutInfo s : actualShortcuts) {
- assertNotNull("ID " + s.getId(), s.getTitle());
- }
- return actualShortcuts;
- }
-
- public static List<ShortcutInfo> assertAllNotHaveTitle(
- List<ShortcutInfo> actualShortcuts) {
- for (ShortcutInfo s : actualShortcuts) {
- assertNull("ID " + s.getId(), s.getTitle());
- }
- return actualShortcuts;
- }
-
- public static List<ShortcutInfo> assertAllHaveIconResId(
- List<ShortcutInfo> actualShortcuts) {
- for (ShortcutInfo s : actualShortcuts) {
- assertTrue("ID " + s.getId() + " not have icon res ID", s.hasIconResource());
- assertFalse("ID " + s.getId() + " shouldn't have icon FD", s.hasIconFile());
- }
- return actualShortcuts;
- }
-
- public static List<ShortcutInfo> assertAllHaveIconFile(
- List<ShortcutInfo> actualShortcuts) {
- for (ShortcutInfo s : actualShortcuts) {
- assertFalse("ID " + s.getId() + " shouldn't have icon res ID", s.hasIconResource());
- assertTrue("ID " + s.getId() + " not have icon FD", s.hasIconFile());
- }
- return actualShortcuts;
- }
-
- public static List<ShortcutInfo> assertAllHaveIcon(
- List<ShortcutInfo> actualShortcuts) {
- for (ShortcutInfo s : actualShortcuts) {
- assertTrue("ID " + s.getId() + " has no icon ", s.hasIconFile() || s.hasIconResource());
- }
- return actualShortcuts;
- }
-
- public static List<ShortcutInfo> assertAllKeyFieldsOnly(
- List<ShortcutInfo> actualShortcuts) {
- for (ShortcutInfo s : actualShortcuts) {
- assertTrue("ID " + s.getId(), s.hasKeyFieldsOnly());
- }
- return actualShortcuts;
- }
-
- public static List<ShortcutInfo> assertAllNotKeyFieldsOnly(
- List<ShortcutInfo> actualShortcuts) {
- for (ShortcutInfo s : actualShortcuts) {
- assertFalse("ID " + s.getId(), s.hasKeyFieldsOnly());
- }
- return actualShortcuts;
- }
-
- public static List<ShortcutInfo> assertAllDynamic(List<ShortcutInfo> actualShortcuts) {
- for (ShortcutInfo s : actualShortcuts) {
- assertTrue("ID " + s.getId(), s.isDynamic());
- }
- return actualShortcuts;
- }
-
- public static List<ShortcutInfo> assertAllPinned(List<ShortcutInfo> actualShortcuts) {
- for (ShortcutInfo s : actualShortcuts) {
- assertTrue("ID " + s.getId(), s.isPinned());
- }
- return actualShortcuts;
- }
-
- public static List<ShortcutInfo> assertAllDynamicOrPinned(
- List<ShortcutInfo> actualShortcuts) {
- for (ShortcutInfo s : actualShortcuts) {
- assertTrue("ID " + s.getId(), s.isDynamic() || s.isPinned());
- }
- return actualShortcuts;
- }
-
- public static void assertDynamicOnly(ShortcutInfo si) {
- assertTrue(si.isDynamic());
- assertFalse(si.isPinned());
- }
-
- public static void assertPinnedOnly(ShortcutInfo si) {
- assertFalse(si.isDynamic());
- assertTrue(si.isPinned());
- }
-
- public static void assertDynamicAndPinned(ShortcutInfo si) {
- assertTrue(si.isDynamic());
- assertTrue(si.isPinned());
- }
-
- public static void assertBitmapSize(int expectedWidth, int expectedHeight, Bitmap bitmap) {
- assertEquals("width", expectedWidth, bitmap.getWidth());
- assertEquals("height", expectedHeight, bitmap.getHeight());
- }
-
- public static <T> void assertAllUnique(Collection<T> list) {
- final Set<Object> set = new HashSet<>();
- for (T item : list) {
- if (set.contains(item)) {
- fail("Duplicate item found: " + item + " (in the list: " + list + ")");
- }
- set.add(item);
- }
- }
-
- public static ShortcutInfo findShortcut(List<ShortcutInfo> list, String id) {
- for (ShortcutInfo si : list) {
- if (si.getId().equals(id)) {
- return si;
- }
- }
- fail("Shortcut " + id + " not found in the list");
- return null;
- }
-
- public static Bitmap pfdToBitmap(ParcelFileDescriptor pfd) {
- assertNotNull(pfd);
- try {
- try {
- return BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
- } finally {
- pfd.close();
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- public static void assertBundleEmpty(BaseBundle b) {
- assertTrue(b == null || b.size() == 0);
- }
-
- public static void assertCallbackNotReceived(LauncherApps.Callback mock) {
- verify(mock, times(0)).onShortcutsChanged(anyString(), anyList(),
- any(UserHandle.class));
- }
-
- public static void assertCallbackReceived(LauncherApps.Callback mock,
- UserHandle user, String packageName, String... ids) {
- verify(mock).onShortcutsChanged(eq(packageName), checkShortcutIds(ids),
- eq(user));
- }
-
- public static boolean checkAssertSuccess(Runnable r) {
- try {
- r.run();
- return true;
- } catch (AssertionError e) {
- return false;
- }
- }
-
- public static <T> T checkArgument(Predicate<T> checker, String description,
- List<T> matchedCaptor) {
- final Matcher<T> m = new BaseMatcher<T>() {
- @Override
- public boolean matches(Object item) {
- if (item == null) {
- return false;
- }
- final T value = (T) item;
- if (!checker.test(value)) {
- return false;
- }
-
- if (matchedCaptor != null) {
- matchedCaptor.add(value);
- }
- return true;
- }
-
- @Override
- public void describeTo(Description d) {
- d.appendText(description);
- }
- };
- return Mockito.argThat(m);
- }
-
- public static List<ShortcutInfo> checkShortcutIds(String... ids) {
- return checkArgument((List<ShortcutInfo> list) -> {
- final Set<String> actualSet = set(si -> si.getId(), list);
- return actualSet.equals(set(ids));
-
- }, "Shortcut IDs=[" + Arrays.toString(ids) + "]", null);
- }
-
- public static void waitUntil(String message, BooleanSupplier condition) {
- waitUntil(message, condition, STANDARD_TIMEOUT_SEC);
- }
-
- public static void waitUntil(String message, BooleanSupplier condition, int timeoutSeconds) {
- final long timeout = System.currentTimeMillis() + (timeoutSeconds * 1000L);
- while (System.currentTimeMillis() < timeout) {
- if (condition.getAsBoolean()) {
- return;
- }
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- fail("Timed out for: " + message);
- }
-}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 8f09bda..33e6cea 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -699,7 +699,7 @@
sDefaults.putBoolean(KEY_CARRIER_VOLTE_AVAILABLE_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VT_AVAILABLE_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false);
- sDefaults.putBoolean(KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, true);
+ sDefaults.putBoolean(KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL, false);
sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT, 2);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/AssetsAtlasActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/AssetsAtlasActivity.java
index df7e3bb..cbf99dc 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/AssetsAtlasActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/AssetsAtlasActivity.java
@@ -42,7 +42,7 @@
BitmapsView(Context c) {
super(c);
- Drawable d = c.getResources().getDrawable(R.drawable.text_select_handle_left);
+ Drawable d = c.getResources().getDrawable(R.drawable.star_big_on);
mBitmap = ((BitmapDrawable) d).getBitmap();
}