Merge "Deprecate shell-based UI Automator." into nyc-dev am: f298f4a7c7
am: 79f42608c2
* commit '79f42608c265ecc22c09d239bdfd04dcaafca616':
Deprecate shell-based UI Automator.
Change-Id: I9173d27b964f7ccf05b6f84c7e71a3fe4b3b7033
diff --git a/api/current.txt b/api/current.txt
index 82a55b5..b5ce2b1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8190,6 +8190,7 @@
field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions";
field public static final java.lang.String SEARCH_SERVICE = "search";
field public static final java.lang.String SENSOR_SERVICE = "sensor";
+ field public static final java.lang.String SHORTCUT_SERVICE = "shortcut";
field public static final java.lang.String STORAGE_SERVICE = "storage";
field public static final java.lang.String SYSTEM_HEALTH_SERVICE = "systemhealth";
field public static final java.lang.String TELECOM_SERVICE = "telecom";
@@ -9499,13 +9500,22 @@
public class LauncherApps {
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
+ method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(int);
+ method public android.graphics.drawable.Drawable getShortcutIconDrawable(int);
+ 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);
}
@@ -9518,6 +9528,19 @@
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 {
@@ -10018,6 +10041,84 @@
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 java.lang.String getDisabledMessage();
+ method public int getDisabledMessageResId();
+ 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 int getRank();
+ method public java.lang.String getText();
+ method public int getTextResId();
+ method public java.lang.String getTitle();
+ method public int getTitleResId();
+ method public android.os.UserHandle getUserHandle();
+ method public boolean hasIconFile();
+ method public boolean hasIconResource();
+ method public boolean hasKeyFieldsOnly();
+ method public boolean hasStringResourcesResolved();
+ method public boolean isDisabled();
+ method public boolean isDynamic();
+ method public boolean isFromManifest();
+ 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_DISABLED = 64; // 0x40
+ field public static final int FLAG_DYNAMIC = 1; // 0x1
+ field public static final int FLAG_FROM_MANIFEST = 32; // 0x20
+ 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 int FLAG_STRINGS_RESOLVED = 128; // 0x80
+ 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 setDisabledMessage(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setDisabledMessageResId(int);
+ 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 setRank(int);
+ method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setTextResId(int);
+ method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setTitleResId(int);
+ }
+
+ public class ShortcutManager {
+ method public boolean addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+ method public void disableShortcuts(java.util.List<java.lang.String>);
+ method public void disableShortcuts(java.util.List<java.lang.String>, int);
+ method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String);
+ 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 void reportShortcutUsed(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);
@@ -30640,6 +30741,7 @@
public static class CallLog.Calls implements android.provider.BaseColumns {
ctor public CallLog.Calls();
method public static java.lang.String getLastOutgoingCall(android.content.Context);
+ field public static final int ANSWERED_EXTERNALLY_TYPE = 7; // 0x7
field public static final int BLOCKED_TYPE = 6; // 0x6
field public static final java.lang.String CACHED_FORMATTED_NUMBER = "formatted_number";
field public static final java.lang.String CACHED_LOOKUP_URI = "lookup_uri";
@@ -30662,6 +30764,7 @@
field public static final java.lang.String DURATION = "duration";
field public static final java.lang.String EXTRA_CALL_TYPE_FILTER = "android.provider.extra.CALL_TYPE_FILTER";
field public static final java.lang.String FEATURES = "features";
+ field public static final int FEATURES_PULLED_EXTERNALLY = 2; // 0x2
field public static final int FEATURES_VIDEO = 1; // 0x1
field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location";
field public static final int INCOMING_TYPE = 1; // 0x1
@@ -32281,6 +32384,7 @@
field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
+ field public static final java.lang.String ACTION_DELETION_HELPER_SETTINGS = "android.settings.DELETION_HELPER_SETTINGS";
field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
@@ -35936,9 +36040,14 @@
method public void phoneAccountSelected(android.telecom.PhoneAccountHandle, boolean);
method public void playDtmfTone(char);
method public void postDialContinue(boolean);
+ method public void pullExternalCall();
+ method public final void putExtras(android.os.Bundle);
method public void registerCallback(android.telecom.Call.Callback);
method public void registerCallback(android.telecom.Call.Callback, android.os.Handler);
method public void reject(boolean, java.lang.String);
+ method public final void removeExtras(java.util.List<java.lang.String>);
+ method public final void removeExtras(java.lang.String...);
+ method public void sendCallEvent(java.lang.String, android.os.Bundle);
method public void splitFromConference();
method public void stopDtmfTone();
method public void swapConference();
@@ -35952,6 +36061,7 @@
field public static final int STATE_DISCONNECTING = 10; // 0xa
field public static final int STATE_HOLDING = 3; // 0x3
field public static final int STATE_NEW = 0; // 0x0
+ field public static final int STATE_PULLING_CALL = 11; // 0xb
field public static final int STATE_RINGING = 2; // 0x2
field public static final int STATE_SELECT_PHONE_ACCOUNT = 8; // 0x8
}
@@ -35962,6 +36072,7 @@
method public void onCannedTextResponsesLoaded(android.telecom.Call, java.util.List<java.lang.String>);
method public void onChildrenChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
method public void onConferenceableCallsChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
+ method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details);
method public void onParentChanged(android.telecom.Call, android.telecom.Call);
method public void onPostDialWait(android.telecom.Call, java.lang.String);
@@ -35992,6 +36103,7 @@
method public static java.lang.String propertiesToString(int);
field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 4194304; // 0x400000
field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
+ field public static final int CAPABILITY_CAN_PULL_CALL = 8388608; // 0x800000
field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
field public static final int CAPABILITY_HOLD = 1; // 0x1
field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
@@ -36012,6 +36124,7 @@
field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20
field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2
field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
+ field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
field public static final int PROPERTY_WIFI = 8; // 0x8
}
@@ -36062,6 +36175,7 @@
method public final android.telecom.CallAudioState getCallAudioState();
method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
method public final int getConnectionCapabilities();
+ method public final int getConnectionProperties();
method public final long getConnectionTime();
method public final java.util.List<android.telecom.Connection> getConnections();
method public final android.telecom.DisconnectCause getDisconnectCause();
@@ -36074,6 +36188,7 @@
method public void onCallAudioStateChanged(android.telecom.CallAudioState);
method public void onConnectionAdded(android.telecom.Connection);
method public void onDisconnect();
+ method public void onExtrasChanged(android.os.Bundle);
method public void onHold();
method public void onMerge(android.telecom.Connection);
method public void onMerge();
@@ -36082,14 +36197,18 @@
method public void onStopDtmfTone();
method public void onSwap();
method public void onUnhold();
+ method public final void putExtras(android.os.Bundle);
method public final void removeConnection(android.telecom.Connection);
+ method public final void removeExtras(java.util.List<java.lang.String>);
+ method public final void removeExtras(java.lang.String...);
method public final void setActive();
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
method public final void setConnectionCapabilities(int);
+ method public final void setConnectionProperties(int);
method public final void setConnectionTime(long);
method public final void setDialing();
method public final void setDisconnected(android.telecom.DisconnectCause);
- method public final void setExtras(android.os.Bundle);
+ method public final deprecated void setExtras(android.os.Bundle);
method public final void setOnHold();
method public final void setStatusHints(android.telecom.StatusHints);
method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider);
@@ -36115,6 +36234,7 @@
method public final android.telecom.Conference getConference();
method public final java.util.List<android.telecom.Conferenceable> getConferenceables();
method public final int getConnectionCapabilities();
+ method public final int getConnectionProperties();
method public final android.telecom.DisconnectCause getDisconnectCause();
method public final android.os.Bundle getExtras();
method public final int getState();
@@ -36125,16 +36245,24 @@
method public void onAnswer(int);
method public void onAnswer();
method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method public void onCallEvent(java.lang.String, android.os.Bundle);
method public void onDisconnect();
+ method public void onExtrasChanged(android.os.Bundle);
method public void onHold();
method public void onPlayDtmfTone(char);
method public void onPostDialContinue(boolean);
+ method public void onPullExternalCall();
method public void onReject();
method public void onReject(java.lang.String);
method public void onSeparate();
method public void onStateChanged(int);
method public void onStopDtmfTone();
method public void onUnhold();
+ method public static java.lang.String propertiesToString(int);
+ method public final void putExtras(android.os.Bundle);
+ method public final void removeExtras(java.util.List<java.lang.String>);
+ method public final void removeExtras(java.lang.String...);
+ method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
method public final void setActive();
method public final void setAddress(android.net.Uri, int);
method public final void setAudioModeIsVoip(boolean);
@@ -36142,9 +36270,10 @@
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
method public final void setConnectionCapabilities(int);
+ method public final void setConnectionProperties(int);
method public final void setDialing();
method public final void setDisconnected(android.telecom.DisconnectCause);
- method public final void setExtras(android.os.Bundle);
+ method public final deprecated void setExtras(android.os.Bundle);
method public final void setInitialized();
method public final void setInitializing();
method public final void setNextPostDialChar(char);
@@ -36158,6 +36287,7 @@
method public static java.lang.String stateToString(int);
field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 8388608; // 0x800000
field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
+ field public static final int CAPABILITY_CAN_PULL_CALL = 16777216; // 0x1000000
field public static final int CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION = 4194304; // 0x400000
field public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 524288; // 0x80000
field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
@@ -36175,15 +36305,18 @@
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800
field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
+ field public static final java.lang.String EVENT_CALL_PULL_FAILED = "android.telecom.event.CALL_PULL_FAILED";
field public static final java.lang.String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT";
field public static final java.lang.String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS";
field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
+ field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
field public static final int STATE_ACTIVE = 4; // 0x4
field public static final int STATE_DIALING = 3; // 0x3
field public static final int STATE_DISCONNECTED = 6; // 0x6
field public static final int STATE_HOLDING = 5; // 0x5
field public static final int STATE_INITIALIZING = 0; // 0x0
field public static final int STATE_NEW = 1; // 0x1
+ field public static final int STATE_PULLING_CALL = 7; // 0x7
field public static final int STATE_RINGING = 2; // 0x2
}
@@ -36261,7 +36394,9 @@
method public java.lang.String getReason();
method public int getTone();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int ANSWERED_ELSEWHERE = 11; // 0xb
field public static final int BUSY = 7; // 0x7
+ field public static final int CALL_PULLED = 12; // 0xc
field public static final int CANCELED = 4; // 0x4
field public static final int CONNECTION_MANAGER_NOT_SUPPORTED = 10; // 0xa
field public static final android.os.Parcelable.Creator<android.telecom.DisconnectCause> CREATOR;
@@ -36297,6 +36432,7 @@
method public void onCallAudioStateChanged(android.telecom.CallAudioState);
method public void onCallRemoved(android.telecom.Call);
method public void onCanAddCallChanged(boolean);
+ method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
method public void onSilenceRinger();
method public final void setAudioRoute(int);
method public final void setMuted(boolean);
@@ -36419,6 +36555,7 @@
method public void onConferenceableConnectionsChanged(android.telecom.RemoteConference, java.util.List<android.telecom.RemoteConnection>);
method public void onConnectionAdded(android.telecom.RemoteConference, android.telecom.RemoteConnection);
method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConference, int);
+ method public void onConnectionPropertiesChanged(android.telecom.RemoteConference, int);
method public void onConnectionRemoved(android.telecom.RemoteConference, android.telecom.RemoteConnection);
method public void onDestroyed(android.telecom.RemoteConference);
method public void onDisconnected(android.telecom.RemoteConference, android.telecom.DisconnectCause);
@@ -36437,6 +36574,7 @@
method public android.telecom.RemoteConference getConference();
method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
method public int getConnectionCapabilities();
+ method public int getConnectionProperties();
method public android.telecom.DisconnectCause getDisconnectCause();
method public final android.os.Bundle getExtras();
method public int getState();
@@ -36448,6 +36586,7 @@
method public boolean isVoipAudioMode();
method public void playDtmfTone(char);
method public void postDialContinue(boolean);
+ method public void pullExternalCall();
method public void registerCallback(android.telecom.RemoteConnection.Callback);
method public void registerCallback(android.telecom.RemoteConnection.Callback, android.os.Handler);
method public void reject();
@@ -36464,6 +36603,8 @@
method public void onConferenceChanged(android.telecom.RemoteConnection, android.telecom.RemoteConference);
method public void onConferenceableConnectionsChanged(android.telecom.RemoteConnection, java.util.List<android.telecom.RemoteConnection>);
method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConnection, int);
+ method public void onConnectionEvent(android.telecom.RemoteConnection, java.lang.String, android.os.Bundle);
+ method public void onConnectionPropertiesChanged(android.telecom.RemoteConnection, int);
method public void onDestroyed(android.telecom.RemoteConnection);
method public void onDisconnected(android.telecom.RemoteConnection, android.telecom.DisconnectCause);
method public void onExtrasChanged(android.telecom.RemoteConnection, android.os.Bundle);
@@ -36560,6 +36701,7 @@
field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE";
field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
+ field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -37202,6 +37344,26 @@
method public void onSubscriptionsChanged();
}
+ public final class TelephonyHistogram implements android.os.Parcelable {
+ ctor public TelephonyHistogram(int, int, int);
+ ctor public TelephonyHistogram(android.telephony.TelephonyHistogram);
+ ctor public TelephonyHistogram(android.os.Parcel);
+ method public void addTimeTaken(int);
+ method public int describeContents();
+ method public int getAverageTime();
+ method public int getBucketCount();
+ method public int[] getBucketCounters();
+ method public int[] getBucketEndPoints();
+ method public int getCategory();
+ method public int getId();
+ method public int getMaxTime();
+ method public int getMinTime();
+ method public int getSampleCount();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.telephony.TelephonyHistogram> CREATOR;
+ field public static final int TELEPHONY_CATEGORY_RIL = 1; // 0x1
+ }
+
public class TelephonyManager {
method public boolean canChangeDtmfToneLength();
method public java.util.List<android.telephony.CellInfo> getAllCellInfo();
@@ -37245,6 +37407,7 @@
method public int getSimState();
method public java.lang.String getSubscriberId();
method public java.lang.String getSubscriberId(int);
+ method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
method public java.lang.String getVoiceMailAlphaTag();
method public java.lang.String getVoiceMailAlphaTag(int);
method public java.lang.String getVoiceMailNumber();
@@ -41333,6 +41496,10 @@
field public static final int KEYCODE_FOCUS = 80; // 0x50
field public static final int KEYCODE_FORWARD = 125; // 0x7d
field public static final int KEYCODE_FORWARD_DEL = 112; // 0x70
+ field public static final int KEYCODE_FP_NAV_DOWN = 281; // 0x119
+ field public static final int KEYCODE_FP_NAV_LEFT = 282; // 0x11a
+ field public static final int KEYCODE_FP_NAV_RIGHT = 283; // 0x11b
+ field public static final int KEYCODE_FP_NAV_UP = 280; // 0x118
field public static final int KEYCODE_FUNCTION = 119; // 0x77
field public static final int KEYCODE_G = 35; // 0x23
field public static final int KEYCODE_GRAVE = 68; // 0x44
diff --git a/api/system-current.txt b/api/system-current.txt
index f2ebaca..876dfa8 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8512,6 +8512,7 @@
field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions";
field public static final java.lang.String SEARCH_SERVICE = "search";
field public static final java.lang.String SENSOR_SERVICE = "sensor";
+ field public static final java.lang.String SHORTCUT_SERVICE = "shortcut";
field public static final java.lang.String STORAGE_SERVICE = "storage";
field public static final java.lang.String SYSTEM_HEALTH_SERVICE = "systemhealth";
field public static final java.lang.String TELECOM_SERVICE = "telecom";
@@ -9852,13 +9853,22 @@
public class LauncherApps {
method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(java.lang.String, android.os.UserHandle);
+ method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(int);
+ method public android.graphics.drawable.Drawable getShortcutIconDrawable(int);
+ 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);
}
@@ -9871,6 +9881,19 @@
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 {
@@ -10441,6 +10464,84 @@
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 java.lang.String getDisabledMessage();
+ method public int getDisabledMessageResId();
+ 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 int getRank();
+ method public java.lang.String getText();
+ method public int getTextResId();
+ method public java.lang.String getTitle();
+ method public int getTitleResId();
+ method public android.os.UserHandle getUserHandle();
+ method public boolean hasIconFile();
+ method public boolean hasIconResource();
+ method public boolean hasKeyFieldsOnly();
+ method public boolean hasStringResourcesResolved();
+ method public boolean isDisabled();
+ method public boolean isDynamic();
+ method public boolean isFromManifest();
+ 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_DISABLED = 64; // 0x40
+ field public static final int FLAG_DYNAMIC = 1; // 0x1
+ field public static final int FLAG_FROM_MANIFEST = 32; // 0x20
+ 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 int FLAG_STRINGS_RESOLVED = 128; // 0x80
+ 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 setDisabledMessage(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setDisabledMessageResId(int);
+ 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 setRank(int);
+ method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setTextResId(int);
+ method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setTitleResId(int);
+ }
+
+ public class ShortcutManager {
+ method public boolean addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+ method public void disableShortcuts(java.util.List<java.lang.String>);
+ method public void disableShortcuts(java.util.List<java.lang.String>, int);
+ method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String);
+ 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 void reportShortcutUsed(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);
@@ -33211,6 +33312,7 @@
public static class CallLog.Calls implements android.provider.BaseColumns {
ctor public CallLog.Calls();
method public static java.lang.String getLastOutgoingCall(android.content.Context);
+ field public static final int ANSWERED_EXTERNALLY_TYPE = 7; // 0x7
field public static final int BLOCKED_TYPE = 6; // 0x6
field public static final java.lang.String CACHED_FORMATTED_NUMBER = "formatted_number";
field public static final java.lang.String CACHED_LOOKUP_URI = "lookup_uri";
@@ -33233,6 +33335,7 @@
field public static final java.lang.String DURATION = "duration";
field public static final java.lang.String EXTRA_CALL_TYPE_FILTER = "android.provider.extra.CALL_TYPE_FILTER";
field public static final java.lang.String FEATURES = "features";
+ field public static final int FEATURES_PULLED_EXTERNALLY = 2; // 0x2
field public static final int FEATURES_VIDEO = 1; // 0x1
field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location";
field public static final int INCOMING_TYPE = 1; // 0x1
@@ -34984,6 +35087,7 @@
field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
+ field public static final java.lang.String ACTION_DELETION_HELPER_SETTINGS = "android.settings.DELETION_HELPER_SETTINGS";
field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
@@ -38770,10 +38874,15 @@
method public void phoneAccountSelected(android.telecom.PhoneAccountHandle, boolean);
method public void playDtmfTone(char);
method public void postDialContinue(boolean);
+ method public void pullExternalCall();
+ method public final void putExtras(android.os.Bundle);
method public void registerCallback(android.telecom.Call.Callback);
method public void registerCallback(android.telecom.Call.Callback, android.os.Handler);
method public void reject(boolean, java.lang.String);
+ method public final void removeExtras(java.util.List<java.lang.String>);
+ method public final void removeExtras(java.lang.String...);
method public deprecated void removeListener(android.telecom.Call.Listener);
+ method public void sendCallEvent(java.lang.String, android.os.Bundle);
method public void splitFromConference();
method public void stopDtmfTone();
method public void swapConference();
@@ -38788,6 +38897,7 @@
field public static final int STATE_HOLDING = 3; // 0x3
field public static final int STATE_NEW = 0; // 0x0
field public static final deprecated int STATE_PRE_DIAL_WAIT = 8; // 0x8
+ field public static final int STATE_PULLING_CALL = 11; // 0xb
field public static final int STATE_RINGING = 2; // 0x2
field public static final int STATE_SELECT_PHONE_ACCOUNT = 8; // 0x8
}
@@ -38798,6 +38908,7 @@
method public void onCannedTextResponsesLoaded(android.telecom.Call, java.util.List<java.lang.String>);
method public void onChildrenChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
method public void onConferenceableCallsChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
+ method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details);
method public void onParentChanged(android.telecom.Call, android.telecom.Call);
method public void onPostDialWait(android.telecom.Call, java.lang.String);
@@ -38828,6 +38939,7 @@
method public static java.lang.String propertiesToString(int);
field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 4194304; // 0x400000
field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
+ field public static final int CAPABILITY_CAN_PULL_CALL = 8388608; // 0x800000
field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
field public static final int CAPABILITY_HOLD = 1; // 0x1
field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
@@ -38848,6 +38960,7 @@
field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20
field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2
field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
+ field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
field public static final int PROPERTY_WIFI = 8; // 0x8
}
@@ -38904,6 +39017,7 @@
method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
method public final deprecated long getConnectTimeMillis();
method public final int getConnectionCapabilities();
+ method public final int getConnectionProperties();
method public final long getConnectionTime();
method public final java.util.List<android.telecom.Connection> getConnections();
method public final android.telecom.DisconnectCause getDisconnectCause();
@@ -38918,6 +39032,7 @@
method public void onCallAudioStateChanged(android.telecom.CallAudioState);
method public void onConnectionAdded(android.telecom.Connection);
method public void onDisconnect();
+ method public void onExtrasChanged(android.os.Bundle);
method public void onHold();
method public void onMerge(android.telecom.Connection);
method public void onMerge();
@@ -38926,15 +39041,19 @@
method public void onStopDtmfTone();
method public void onSwap();
method public void onUnhold();
+ method public final void putExtras(android.os.Bundle);
method public final void removeConnection(android.telecom.Connection);
+ method public final void removeExtras(java.util.List<java.lang.String>);
+ method public final void removeExtras(java.lang.String...);
method public final void setActive();
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
method public final deprecated void setConnectTimeMillis(long);
method public final void setConnectionCapabilities(int);
+ method public final void setConnectionProperties(int);
method public final void setConnectionTime(long);
method public final void setDialing();
method public final void setDisconnected(android.telecom.DisconnectCause);
- method public final void setExtras(android.os.Bundle);
+ method public final deprecated void setExtras(android.os.Bundle);
method public final void setOnHold();
method public final void setStatusHints(android.telecom.StatusHints);
method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider);
@@ -38961,6 +39080,7 @@
method public final android.telecom.Conference getConference();
method public final java.util.List<android.telecom.Conferenceable> getConferenceables();
method public final int getConnectionCapabilities();
+ method public final int getConnectionProperties();
method public final android.telecom.DisconnectCause getDisconnectCause();
method public final android.os.Bundle getExtras();
method public final int getState();
@@ -38972,16 +39092,24 @@
method public void onAnswer();
method public deprecated void onAudioStateChanged(android.telecom.AudioState);
method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method public void onCallEvent(java.lang.String, android.os.Bundle);
method public void onDisconnect();
+ method public void onExtrasChanged(android.os.Bundle);
method public void onHold();
method public void onPlayDtmfTone(char);
method public void onPostDialContinue(boolean);
+ method public void onPullExternalCall();
method public void onReject();
method public void onReject(java.lang.String);
method public void onSeparate();
method public void onStateChanged(int);
method public void onStopDtmfTone();
method public void onUnhold();
+ method public static java.lang.String propertiesToString(int);
+ method public final void putExtras(android.os.Bundle);
+ method public final void removeExtras(java.util.List<java.lang.String>);
+ method public final void removeExtras(java.lang.String...);
+ method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
method public final void setActive();
method public final void setAddress(android.net.Uri, int);
method public final void setAudioModeIsVoip(boolean);
@@ -38989,9 +39117,10 @@
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
method public final void setConnectionCapabilities(int);
+ method public final void setConnectionProperties(int);
method public final void setDialing();
method public final void setDisconnected(android.telecom.DisconnectCause);
- method public final void setExtras(android.os.Bundle);
+ method public final deprecated void setExtras(android.os.Bundle);
method public final void setInitialized();
method public final void setInitializing();
method public final void setNextPostDialChar(char);
@@ -39005,6 +39134,7 @@
method public static java.lang.String stateToString(int);
field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 8388608; // 0x800000
field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
+ field public static final int CAPABILITY_CAN_PULL_CALL = 16777216; // 0x1000000
field public static final int CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION = 4194304; // 0x400000
field public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 524288; // 0x80000
field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
@@ -39022,15 +39152,18 @@
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800
field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
+ field public static final java.lang.String EVENT_CALL_PULL_FAILED = "android.telecom.event.CALL_PULL_FAILED";
field public static final java.lang.String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT";
field public static final java.lang.String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS";
field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
+ field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
field public static final int STATE_ACTIVE = 4; // 0x4
field public static final int STATE_DIALING = 3; // 0x3
field public static final int STATE_DISCONNECTED = 6; // 0x6
field public static final int STATE_HOLDING = 5; // 0x5
field public static final int STATE_INITIALIZING = 0; // 0x0
field public static final int STATE_NEW = 1; // 0x1
+ field public static final int STATE_PULLING_CALL = 7; // 0x7
field public static final int STATE_RINGING = 2; // 0x2
}
@@ -39108,7 +39241,9 @@
method public java.lang.String getReason();
method public int getTone();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int ANSWERED_ELSEWHERE = 11; // 0xb
field public static final int BUSY = 7; // 0x7
+ field public static final int CALL_PULLED = 12; // 0xc
field public static final int CANCELED = 4; // 0x4
field public static final int CONNECTION_MANAGER_NOT_SUPPORTED = 10; // 0xa
field public static final android.os.Parcelable.Creator<android.telecom.DisconnectCause> CREATOR;
@@ -39145,6 +39280,7 @@
method public void onCallAudioStateChanged(android.telecom.CallAudioState);
method public void onCallRemoved(android.telecom.Call);
method public void onCanAddCallChanged(boolean);
+ method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
method public deprecated void onPhoneCreated(android.telecom.Phone);
method public deprecated void onPhoneDestroyed(android.telecom.Phone);
method public void onSilenceRinger();
@@ -39322,6 +39458,7 @@
method public void onConferenceableConnectionsChanged(android.telecom.RemoteConference, java.util.List<android.telecom.RemoteConnection>);
method public void onConnectionAdded(android.telecom.RemoteConference, android.telecom.RemoteConnection);
method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConference, int);
+ method public void onConnectionPropertiesChanged(android.telecom.RemoteConference, int);
method public void onConnectionRemoved(android.telecom.RemoteConference, android.telecom.RemoteConnection);
method public void onDestroyed(android.telecom.RemoteConference);
method public void onDisconnected(android.telecom.RemoteConference, android.telecom.DisconnectCause);
@@ -39340,6 +39477,7 @@
method public android.telecom.RemoteConference getConference();
method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
method public int getConnectionCapabilities();
+ method public int getConnectionProperties();
method public android.telecom.DisconnectCause getDisconnectCause();
method public final android.os.Bundle getExtras();
method public int getState();
@@ -39351,6 +39489,7 @@
method public boolean isVoipAudioMode();
method public void playDtmfTone(char);
method public void postDialContinue(boolean);
+ method public void pullExternalCall();
method public void registerCallback(android.telecom.RemoteConnection.Callback);
method public void registerCallback(android.telecom.RemoteConnection.Callback, android.os.Handler);
method public void reject();
@@ -39368,6 +39507,8 @@
method public void onConferenceChanged(android.telecom.RemoteConnection, android.telecom.RemoteConference);
method public void onConferenceableConnectionsChanged(android.telecom.RemoteConnection, java.util.List<android.telecom.RemoteConnection>);
method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConnection, int);
+ method public void onConnectionEvent(android.telecom.RemoteConnection, java.lang.String, android.os.Bundle);
+ method public void onConnectionPropertiesChanged(android.telecom.RemoteConnection, int);
method public void onDestroyed(android.telecom.RemoteConnection);
method public void onDisconnected(android.telecom.RemoteConnection, android.telecom.DisconnectCause);
method public void onExtrasChanged(android.telecom.RemoteConnection, android.os.Bundle);
@@ -39491,6 +39632,7 @@
field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE";
field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
+ field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -40135,6 +40277,26 @@
method public void onSubscriptionsChanged();
}
+ public final class TelephonyHistogram implements android.os.Parcelable {
+ ctor public TelephonyHistogram(int, int, int);
+ ctor public TelephonyHistogram(android.telephony.TelephonyHistogram);
+ ctor public TelephonyHistogram(android.os.Parcel);
+ method public void addTimeTaken(int);
+ method public int describeContents();
+ method public int getAverageTime();
+ method public int getBucketCount();
+ method public int[] getBucketCounters();
+ method public int[] getBucketEndPoints();
+ method public int getCategory();
+ method public int getId();
+ method public int getMaxTime();
+ method public int getMinTime();
+ method public int getSampleCount();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.telephony.TelephonyHistogram> CREATOR;
+ field public static final int TELEPHONY_CATEGORY_RIL = 1; // 0x1
+ }
+
public class TelephonyManager {
method public void answerRingingCall();
method public void call(java.lang.String, java.lang.String);
@@ -40197,6 +40359,7 @@
method public int getSimState();
method public java.lang.String getSubscriberId();
method public java.lang.String getSubscriberId(int);
+ method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
method public java.lang.String getVoiceMailAlphaTag();
method public java.lang.String getVoiceMailAlphaTag(int);
method public java.lang.String getVoiceMailNumber();
@@ -44331,6 +44494,10 @@
field public static final int KEYCODE_FOCUS = 80; // 0x50
field public static final int KEYCODE_FORWARD = 125; // 0x7d
field public static final int KEYCODE_FORWARD_DEL = 112; // 0x70
+ field public static final int KEYCODE_FP_NAV_DOWN = 281; // 0x119
+ field public static final int KEYCODE_FP_NAV_LEFT = 282; // 0x11a
+ field public static final int KEYCODE_FP_NAV_RIGHT = 283; // 0x11b
+ field public static final int KEYCODE_FP_NAV_UP = 280; // 0x118
field public static final int KEYCODE_FUNCTION = 119; // 0x77
field public static final int KEYCODE_G = 35; // 0x23
field public static final int KEYCODE_GRAVE = 68; // 0x44
diff --git a/api/test-current.txt b/api/test-current.txt
index c02580c..301c1b2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8198,6 +8198,7 @@
field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions";
field public static final java.lang.String SEARCH_SERVICE = "search";
field public static final java.lang.String SENSOR_SERVICE = "sensor";
+ field public static final java.lang.String SHORTCUT_SERVICE = "shortcut";
field public static final java.lang.String STORAGE_SERVICE = "storage";
field public static final java.lang.String SYSTEM_HEALTH_SERVICE = "systemhealth";
field public static final java.lang.String TELECOM_SERVICE = "telecom";
@@ -9511,13 +9512,22 @@
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.graphics.drawable.Drawable getShortcutBadgedIconDrawable(int);
+ method public android.graphics.drawable.Drawable getShortcutIconDrawable(int);
+ 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);
}
@@ -9530,6 +9540,19 @@
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 {
@@ -10031,6 +10054,85 @@
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 java.lang.String getDisabledMessage();
+ method public int getDisabledMessageResId();
+ 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 int getRank();
+ method public java.lang.String getText();
+ method public int getTextResId();
+ method public java.lang.String getTitle();
+ method public int getTitleResId();
+ method public android.os.UserHandle getUserHandle();
+ method public boolean hasIconFile();
+ method public boolean hasIconResource();
+ method public boolean hasKeyFieldsOnly();
+ method public boolean hasStringResourcesResolved();
+ method public boolean isDisabled();
+ method public boolean isDynamic();
+ method public boolean isFromManifest();
+ 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_DISABLED = 64; // 0x40
+ field public static final int FLAG_DYNAMIC = 1; // 0x1
+ field public static final int FLAG_FROM_MANIFEST = 32; // 0x20
+ 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 int FLAG_STRINGS_RESOLVED = 128; // 0x80
+ 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 setDisabledMessage(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setDisabledMessageResId(int);
+ 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 setRank(int);
+ method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setTextResId(int);
+ method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
+ method public android.content.pm.ShortcutInfo.Builder setTitleResId(int);
+ }
+
+ public class ShortcutManager {
+ ctor public ShortcutManager(android.content.Context);
+ method public boolean addDynamicShortcuts(java.util.List<android.content.pm.ShortcutInfo>);
+ method public void disableShortcuts(java.util.List<java.lang.String>);
+ method public void disableShortcuts(java.util.List<java.lang.String>, int);
+ method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String);
+ 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 void reportShortcutUsed(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);
@@ -30713,6 +30815,7 @@
public static class CallLog.Calls implements android.provider.BaseColumns {
ctor public CallLog.Calls();
method public static java.lang.String getLastOutgoingCall(android.content.Context);
+ field public static final int ANSWERED_EXTERNALLY_TYPE = 7; // 0x7
field public static final int BLOCKED_TYPE = 6; // 0x6
field public static final java.lang.String CACHED_FORMATTED_NUMBER = "formatted_number";
field public static final java.lang.String CACHED_LOOKUP_URI = "lookup_uri";
@@ -30735,6 +30838,7 @@
field public static final java.lang.String DURATION = "duration";
field public static final java.lang.String EXTRA_CALL_TYPE_FILTER = "android.provider.extra.CALL_TYPE_FILTER";
field public static final java.lang.String FEATURES = "features";
+ field public static final int FEATURES_PULLED_EXTERNALLY = 2; // 0x2
field public static final int FEATURES_VIDEO = 1; // 0x1
field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location";
field public static final int INCOMING_TYPE = 1; // 0x1
@@ -32354,6 +32458,7 @@
field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
+ field public static final java.lang.String ACTION_DELETION_HELPER_SETTINGS = "android.settings.DELETION_HELPER_SETTINGS";
field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
@@ -36013,9 +36118,14 @@
method public void phoneAccountSelected(android.telecom.PhoneAccountHandle, boolean);
method public void playDtmfTone(char);
method public void postDialContinue(boolean);
+ method public void pullExternalCall();
+ method public final void putExtras(android.os.Bundle);
method public void registerCallback(android.telecom.Call.Callback);
method public void registerCallback(android.telecom.Call.Callback, android.os.Handler);
method public void reject(boolean, java.lang.String);
+ method public final void removeExtras(java.util.List<java.lang.String>);
+ method public final void removeExtras(java.lang.String...);
+ method public void sendCallEvent(java.lang.String, android.os.Bundle);
method public void splitFromConference();
method public void stopDtmfTone();
method public void swapConference();
@@ -36029,6 +36139,7 @@
field public static final int STATE_DISCONNECTING = 10; // 0xa
field public static final int STATE_HOLDING = 3; // 0x3
field public static final int STATE_NEW = 0; // 0x0
+ field public static final int STATE_PULLING_CALL = 11; // 0xb
field public static final int STATE_RINGING = 2; // 0x2
field public static final int STATE_SELECT_PHONE_ACCOUNT = 8; // 0x8
}
@@ -36039,6 +36150,7 @@
method public void onCannedTextResponsesLoaded(android.telecom.Call, java.util.List<java.lang.String>);
method public void onChildrenChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
method public void onConferenceableCallsChanged(android.telecom.Call, java.util.List<android.telecom.Call>);
+ method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details);
method public void onParentChanged(android.telecom.Call, android.telecom.Call);
method public void onPostDialWait(android.telecom.Call, java.lang.String);
@@ -36069,6 +36181,7 @@
method public static java.lang.String propertiesToString(int);
field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 4194304; // 0x400000
field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
+ field public static final int CAPABILITY_CAN_PULL_CALL = 8388608; // 0x800000
field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
field public static final int CAPABILITY_HOLD = 1; // 0x1
field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
@@ -36089,6 +36202,7 @@
field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20
field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2
field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
+ field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
field public static final int PROPERTY_WIFI = 8; // 0x8
}
@@ -36139,6 +36253,7 @@
method public final android.telecom.CallAudioState getCallAudioState();
method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
method public final int getConnectionCapabilities();
+ method public final int getConnectionProperties();
method public final long getConnectionTime();
method public final java.util.List<android.telecom.Connection> getConnections();
method public final android.telecom.DisconnectCause getDisconnectCause();
@@ -36151,6 +36266,7 @@
method public void onCallAudioStateChanged(android.telecom.CallAudioState);
method public void onConnectionAdded(android.telecom.Connection);
method public void onDisconnect();
+ method public void onExtrasChanged(android.os.Bundle);
method public void onHold();
method public void onMerge(android.telecom.Connection);
method public void onMerge();
@@ -36159,14 +36275,18 @@
method public void onStopDtmfTone();
method public void onSwap();
method public void onUnhold();
+ method public final void putExtras(android.os.Bundle);
method public final void removeConnection(android.telecom.Connection);
+ method public final void removeExtras(java.util.List<java.lang.String>);
+ method public final void removeExtras(java.lang.String...);
method public final void setActive();
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
method public final void setConnectionCapabilities(int);
+ method public final void setConnectionProperties(int);
method public final void setConnectionTime(long);
method public final void setDialing();
method public final void setDisconnected(android.telecom.DisconnectCause);
- method public final void setExtras(android.os.Bundle);
+ method public final deprecated void setExtras(android.os.Bundle);
method public final void setOnHold();
method public final void setStatusHints(android.telecom.StatusHints);
method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider);
@@ -36192,6 +36312,7 @@
method public final android.telecom.Conference getConference();
method public final java.util.List<android.telecom.Conferenceable> getConferenceables();
method public final int getConnectionCapabilities();
+ method public final int getConnectionProperties();
method public final android.telecom.DisconnectCause getDisconnectCause();
method public final android.os.Bundle getExtras();
method public final int getState();
@@ -36202,16 +36323,24 @@
method public void onAnswer(int);
method public void onAnswer();
method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+ method public void onCallEvent(java.lang.String, android.os.Bundle);
method public void onDisconnect();
+ method public void onExtrasChanged(android.os.Bundle);
method public void onHold();
method public void onPlayDtmfTone(char);
method public void onPostDialContinue(boolean);
+ method public void onPullExternalCall();
method public void onReject();
method public void onReject(java.lang.String);
method public void onSeparate();
method public void onStateChanged(int);
method public void onStopDtmfTone();
method public void onUnhold();
+ method public static java.lang.String propertiesToString(int);
+ method public final void putExtras(android.os.Bundle);
+ method public final void removeExtras(java.util.List<java.lang.String>);
+ method public final void removeExtras(java.lang.String...);
+ method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
method public final void setActive();
method public final void setAddress(android.net.Uri, int);
method public final void setAudioModeIsVoip(boolean);
@@ -36219,9 +36348,10 @@
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
method public final void setConnectionCapabilities(int);
+ method public final void setConnectionProperties(int);
method public final void setDialing();
method public final void setDisconnected(android.telecom.DisconnectCause);
- method public final void setExtras(android.os.Bundle);
+ method public final deprecated void setExtras(android.os.Bundle);
method public final void setInitialized();
method public final void setInitializing();
method public final void setNextPostDialChar(char);
@@ -36235,6 +36365,7 @@
method public static java.lang.String stateToString(int);
field public static final int CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO = 8388608; // 0x800000
field public static final int CAPABILITY_CAN_PAUSE_VIDEO = 1048576; // 0x100000
+ field public static final int CAPABILITY_CAN_PULL_CALL = 16777216; // 0x1000000
field public static final int CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION = 4194304; // 0x400000
field public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 524288; // 0x80000
field public static final int CAPABILITY_DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
@@ -36252,15 +36383,18 @@
field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800
field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2
field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8
+ field public static final java.lang.String EVENT_CALL_PULL_FAILED = "android.telecom.event.CALL_PULL_FAILED";
field public static final java.lang.String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT";
field public static final java.lang.String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS";
field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
+ field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
field public static final int STATE_ACTIVE = 4; // 0x4
field public static final int STATE_DIALING = 3; // 0x3
field public static final int STATE_DISCONNECTED = 6; // 0x6
field public static final int STATE_HOLDING = 5; // 0x5
field public static final int STATE_INITIALIZING = 0; // 0x0
field public static final int STATE_NEW = 1; // 0x1
+ field public static final int STATE_PULLING_CALL = 7; // 0x7
field public static final int STATE_RINGING = 2; // 0x2
}
@@ -36338,7 +36472,9 @@
method public java.lang.String getReason();
method public int getTone();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int ANSWERED_ELSEWHERE = 11; // 0xb
field public static final int BUSY = 7; // 0x7
+ field public static final int CALL_PULLED = 12; // 0xc
field public static final int CANCELED = 4; // 0x4
field public static final int CONNECTION_MANAGER_NOT_SUPPORTED = 10; // 0xa
field public static final android.os.Parcelable.Creator<android.telecom.DisconnectCause> CREATOR;
@@ -36374,6 +36510,7 @@
method public void onCallAudioStateChanged(android.telecom.CallAudioState);
method public void onCallRemoved(android.telecom.Call);
method public void onCanAddCallChanged(boolean);
+ method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
method public void onSilenceRinger();
method public final void setAudioRoute(int);
method public final void setMuted(boolean);
@@ -36496,6 +36633,7 @@
method public void onConferenceableConnectionsChanged(android.telecom.RemoteConference, java.util.List<android.telecom.RemoteConnection>);
method public void onConnectionAdded(android.telecom.RemoteConference, android.telecom.RemoteConnection);
method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConference, int);
+ method public void onConnectionPropertiesChanged(android.telecom.RemoteConference, int);
method public void onConnectionRemoved(android.telecom.RemoteConference, android.telecom.RemoteConnection);
method public void onDestroyed(android.telecom.RemoteConference);
method public void onDisconnected(android.telecom.RemoteConference, android.telecom.DisconnectCause);
@@ -36514,6 +36652,7 @@
method public android.telecom.RemoteConference getConference();
method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
method public int getConnectionCapabilities();
+ method public int getConnectionProperties();
method public android.telecom.DisconnectCause getDisconnectCause();
method public final android.os.Bundle getExtras();
method public int getState();
@@ -36525,6 +36664,7 @@
method public boolean isVoipAudioMode();
method public void playDtmfTone(char);
method public void postDialContinue(boolean);
+ method public void pullExternalCall();
method public void registerCallback(android.telecom.RemoteConnection.Callback);
method public void registerCallback(android.telecom.RemoteConnection.Callback, android.os.Handler);
method public void reject();
@@ -36541,6 +36681,8 @@
method public void onConferenceChanged(android.telecom.RemoteConnection, android.telecom.RemoteConference);
method public void onConferenceableConnectionsChanged(android.telecom.RemoteConnection, java.util.List<android.telecom.RemoteConnection>);
method public void onConnectionCapabilitiesChanged(android.telecom.RemoteConnection, int);
+ method public void onConnectionEvent(android.telecom.RemoteConnection, java.lang.String, android.os.Bundle);
+ method public void onConnectionPropertiesChanged(android.telecom.RemoteConnection, int);
method public void onDestroyed(android.telecom.RemoteConnection);
method public void onDisconnected(android.telecom.RemoteConnection, android.telecom.DisconnectCause);
method public void onExtrasChanged(android.telecom.RemoteConnection, android.os.Bundle);
@@ -36637,6 +36779,7 @@
field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE";
field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
+ field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -37279,6 +37422,26 @@
method public void onSubscriptionsChanged();
}
+ public final class TelephonyHistogram implements android.os.Parcelable {
+ ctor public TelephonyHistogram(int, int, int);
+ ctor public TelephonyHistogram(android.telephony.TelephonyHistogram);
+ ctor public TelephonyHistogram(android.os.Parcel);
+ method public void addTimeTaken(int);
+ method public int describeContents();
+ method public int getAverageTime();
+ method public int getBucketCount();
+ method public int[] getBucketCounters();
+ method public int[] getBucketEndPoints();
+ method public int getCategory();
+ method public int getId();
+ method public int getMaxTime();
+ method public int getMinTime();
+ method public int getSampleCount();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.telephony.TelephonyHistogram> CREATOR;
+ field public static final int TELEPHONY_CATEGORY_RIL = 1; // 0x1
+ }
+
public class TelephonyManager {
method public boolean canChangeDtmfToneLength();
method public java.util.List<android.telephony.CellInfo> getAllCellInfo();
@@ -37322,6 +37485,7 @@
method public int getSimState();
method public java.lang.String getSubscriberId();
method public java.lang.String getSubscriberId(int);
+ method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
method public java.lang.String getVoiceMailAlphaTag();
method public java.lang.String getVoiceMailAlphaTag(int);
method public java.lang.String getVoiceMailNumber();
@@ -41412,6 +41576,10 @@
field public static final int KEYCODE_FOCUS = 80; // 0x50
field public static final int KEYCODE_FORWARD = 125; // 0x7d
field public static final int KEYCODE_FORWARD_DEL = 112; // 0x70
+ field public static final int KEYCODE_FP_NAV_DOWN = 281; // 0x119
+ field public static final int KEYCODE_FP_NAV_LEFT = 282; // 0x11a
+ field public static final int KEYCODE_FP_NAV_RIGHT = 283; // 0x11b
+ field public static final int KEYCODE_FP_NAV_UP = 280; // 0x118
field public static final int KEYCODE_FUNCTION = 119; // 0x77
field public static final int KEYCODE_G = 35; // 0x23
field public static final int KEYCODE_GRAVE = 68; // 0x44
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index c597ed2..e8fcd3b 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -18,6 +18,9 @@
#define LOG_TAG "BootAnimation"
#include <stdint.h>
+#include <sys/inotify.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
#include <sys/types.h>
#include <math.h>
#include <fcntl.h>
@@ -57,23 +60,29 @@
#include "BootAnimation.h"
#include "AudioPlayer.h"
-#define OEM_BOOTANIMATION_FILE "/oem/media/bootanimation.zip"
-#define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip"
-#define SYSTEM_ENCRYPTED_BOOTANIMATION_FILE "/system/media/bootanimation-encrypted.zip"
-#define EXIT_PROP_NAME "service.bootanim.exit"
-
namespace android {
+static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip";
+static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip";
+static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip";
+static const char SYSTEM_DATA_DIR_PATH[] = "/data/system";
+static const char SYSTEM_TIME_DIR_NAME[] = "time";
+static const char SYSTEM_TIME_DIR_PATH[] = "/data/system/time";
+static const char LAST_TIME_CHANGED_FILE_NAME[] = "last_time_change";
+static const char LAST_TIME_CHANGED_FILE_PATH[] = "/data/system/time/last_time_change";
+static const char ACCURATE_TIME_FLAG_FILE_NAME[] = "time_is_accurate";
+static const char ACCURATE_TIME_FLAG_FILE_PATH[] = "/data/system/time/time_is_accurate";
+static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
static const int ANIM_ENTRY_NAME_MAX = 256;
// ---------------------------------------------------------------------------
-BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true) {
+BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
+ mTimeCheckThread(NULL) {
mSession = new SurfaceComposerClient();
}
-BootAnimation::~BootAnimation() {
-}
+BootAnimation::~BootAnimation() {}
void BootAnimation::onFirstRef() {
status_t err = mSession->linkToComposerDeath(this);
@@ -638,11 +647,21 @@
bool BootAnimation::movie()
{
-
Animation* animation = loadAnimation(mZipFileName);
if (animation == NULL)
return false;
+ bool anyPartHasClock = false;
+ for (size_t i=0; i < animation->parts.size(); i++) {
+ if(animation->parts[i].clockPosY >= 0) {
+ anyPartHasClock = true;
+ break;
+ }
+ }
+ if (!anyPartHasClock) {
+ mClockEnabled = false;
+ }
+
// Blend required to draw time on top of animation frames.
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glShadeModel(GL_FLAT);
@@ -664,7 +683,18 @@
mClockEnabled = clockTextureInitialized;
}
+ if (mClockEnabled && !updateIsTimeAccurate()) {
+ mTimeCheckThread = new TimeCheckThread(this);
+ mTimeCheckThread->run("BootAnimation::TimeCheckThread", PRIORITY_NORMAL);
+ }
+
playAnimation(*animation);
+
+ if (mTimeCheckThread != NULL) {
+ mTimeCheckThread->requestExit();
+ mTimeCheckThread = NULL;
+ }
+
releaseAnimation(animation);
if (clockTextureInitialized) {
@@ -745,7 +775,7 @@
// which is equivalent to mHeight - (yc + animation.height)
glDrawTexiOES(xc, mHeight - (yc + animation.height),
0, animation.width, animation.height);
- if (mClockEnabled && part.clockPosY >= 0) {
+ if (mClockEnabled && mTimeIsAccurate && part.clockPosY >= 0) {
drawTime(mClock, part.clockPosY);
}
@@ -824,6 +854,132 @@
mLoadedFiles.remove(fn);
return animation;
}
+
+bool BootAnimation::updateIsTimeAccurate() {
+ static constexpr long long MAX_TIME_IN_PAST = 60000LL * 60LL * 24LL * 30LL; // 30 days
+ static constexpr long long MAX_TIME_IN_FUTURE = 60000LL * 90LL; // 90 minutes
+
+ if (mTimeIsAccurate) {
+ return true;
+ }
+
+ struct stat statResult;
+ if(stat(ACCURATE_TIME_FLAG_FILE_PATH, &statResult) == 0) {
+ mTimeIsAccurate = true;
+ return true;
+ }
+
+ FILE* file = fopen(LAST_TIME_CHANGED_FILE_PATH, "r");
+ if (file != NULL) {
+ long long lastChangedTime = 0;
+ fscanf(file, "%lld", &lastChangedTime);
+ fclose(file);
+ if (lastChangedTime > 0) {
+ struct timespec now;
+ clock_gettime(CLOCK_REALTIME, &now);
+ // Match the Java timestamp format
+ long long rtcNow = (now.tv_sec * 1000LL) + (now.tv_nsec / 1000000LL);
+ if (lastChangedTime > rtcNow - MAX_TIME_IN_PAST
+ && lastChangedTime < rtcNow + MAX_TIME_IN_FUTURE) {
+ mTimeIsAccurate = true;
+ }
+ }
+ }
+
+ return mTimeIsAccurate;
+}
+
+BootAnimation::TimeCheckThread::TimeCheckThread(BootAnimation* bootAnimation) : Thread(false),
+ mInotifyFd(-1), mSystemWd(-1), mTimeWd(-1), mBootAnimation(bootAnimation) {}
+
+BootAnimation::TimeCheckThread::~TimeCheckThread() {
+ // mInotifyFd may be -1 but that's ok since we're not at risk of attempting to close a valid FD.
+ close(mInotifyFd);
+}
+
+bool BootAnimation::TimeCheckThread::threadLoop() {
+ bool shouldLoop = doThreadLoop() && !mBootAnimation->mTimeIsAccurate
+ && mBootAnimation->mClockEnabled;
+ if (!shouldLoop) {
+ close(mInotifyFd);
+ mInotifyFd = -1;
+ }
+ return shouldLoop;
+}
+
+bool BootAnimation::TimeCheckThread::doThreadLoop() {
+ static constexpr int BUFF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1));
+
+ // Poll instead of doing a blocking read so the Thread can exit if requested.
+ struct pollfd pfd = { mInotifyFd, POLLIN, 0 };
+ ssize_t pollResult = poll(&pfd, 1, 1000);
+
+ if (pollResult == 0) {
+ return true;
+ } else if (pollResult < 0) {
+ ALOGE("Could not poll inotify events");
+ return false;
+ }
+
+ char buff[BUFF_LEN] __attribute__ ((aligned(__alignof__(struct inotify_event))));;
+ ssize_t length = read(mInotifyFd, buff, BUFF_LEN);
+ if (length == 0) {
+ return true;
+ } else if (length < 0) {
+ ALOGE("Could not read inotify events");
+ return false;
+ }
+
+ const struct inotify_event *event;
+ for (char* ptr = buff; ptr < buff + length; ptr += sizeof(struct inotify_event) + event->len) {
+ event = (const struct inotify_event *) ptr;
+ if (event->wd == mSystemWd && strcmp(SYSTEM_TIME_DIR_NAME, event->name) == 0) {
+ addTimeDirWatch();
+ } else if (event->wd == mTimeWd && (strcmp(LAST_TIME_CHANGED_FILE_NAME, event->name) == 0
+ || strcmp(ACCURATE_TIME_FLAG_FILE_NAME, event->name) == 0)) {
+ return !mBootAnimation->updateIsTimeAccurate();
+ }
+ }
+
+ return true;
+}
+
+void BootAnimation::TimeCheckThread::addTimeDirWatch() {
+ mTimeWd = inotify_add_watch(mInotifyFd, SYSTEM_TIME_DIR_PATH,
+ IN_CLOSE_WRITE | IN_MOVED_TO | IN_ATTRIB);
+ if (mTimeWd > 0) {
+ // No need to watch for the time directory to be created if it already exists
+ inotify_rm_watch(mInotifyFd, mSystemWd);
+ mSystemWd = -1;
+ }
+}
+
+status_t BootAnimation::TimeCheckThread::readyToRun() {
+ mInotifyFd = inotify_init();
+ if (mInotifyFd < 0) {
+ ALOGE("Could not initialize inotify fd");
+ return NO_INIT;
+ }
+
+ mSystemWd = inotify_add_watch(mInotifyFd, SYSTEM_DATA_DIR_PATH, IN_CREATE | IN_ATTRIB);
+ if (mSystemWd < 0) {
+ close(mInotifyFd);
+ mInotifyFd = -1;
+ ALOGE("Could not add watch for %s", SYSTEM_DATA_DIR_PATH);
+ return NO_INIT;
+ }
+
+ addTimeDirWatch();
+
+ if (mBootAnimation->updateIsTimeAccurate()) {
+ close(mInotifyFd);
+ mInotifyFd = -1;
+ return ALREADY_EXISTS;
+ }
+
+ return NO_ERROR;
+}
+
// ---------------------------------------------------------------------------
}
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index d49e1ec..1c3d53a 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -51,6 +51,24 @@
virtual void onFirstRef();
virtual void binderDied(const wp<IBinder>& who);
+ bool updateIsTimeAccurate();
+
+ class TimeCheckThread : public Thread {
+ public:
+ TimeCheckThread(BootAnimation* bootAnimation);
+ virtual ~TimeCheckThread();
+ private:
+ virtual status_t readyToRun();
+ virtual bool threadLoop();
+ bool doThreadLoop();
+ void addTimeDirWatch();
+
+ int mInotifyFd;
+ int mSystemWd;
+ int mTimeWd;
+ BootAnimation* mBootAnimation;
+ };
+
struct Texture {
GLint w;
GLint h;
@@ -113,8 +131,10 @@
sp<SurfaceControl> mFlingerSurfaceControl;
sp<Surface> mFlingerSurface;
bool mClockEnabled;
+ bool mTimeIsAccurate;
String8 mZipFileName;
SortedVector<String8> mLoadedFiles;
+ sp<TimeCheckThread> mTimeCheckThread;
};
// ---------------------------------------------------------------------------
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index 7824072..9928512 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -201,7 +201,7 @@
createContextThemeWrapper);
mWindow.alwaysReadCloseOnTouchAttr();
- mAlert = new AlertController(getContext(), this, getWindow());
+ mAlert = AlertController.create(getContext(), this, getWindow());
}
static int resolveDialogTheme(Context context, int themeResId) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9eb9423..e619796 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3611,8 +3611,6 @@
*
* @see #getSystemService
* @see android.content.pm.ShortcutManager
- *
- * @hide
*/
public static final String SHORTCUT_SERVICE = "shortcut";
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index 2ba24f6..06200bf 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -39,6 +39,9 @@
boolean updateShortcuts(String packageName, in ParceledListSlice shortcuts, int userId);
+ void disableShortcuts(String packageName, in List shortcutIds, String disabledMessage,
+ int disabledMessageResId, int userId);
+
int getMaxDynamicShortcutCount(String packageName, int userId);
int getRemainingCallCount(String packageName, int userId);
@@ -47,6 +50,8 @@
int getIconMaxDimensions(String packageName, int userId);
+ void reportShortcutUsed(String packageName, String shortcutId, int userId);
+
void resetThrottling(); // system only API for developer opsions
void onApplicationActive(String packageName, int userId); // system only API for sysUI
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 8ca27c5..acd85cb 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -25,6 +25,7 @@
import android.content.Intent;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -166,8 +167,6 @@
* @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) {
@@ -176,8 +175,6 @@
/**
* Represents a query passed to {@link #getShortcuts(ShortcutQuery, UserHandle)}.
- *
- * @hide
*/
public static class ShortcutQuery {
/**
@@ -247,7 +244,9 @@
}
/**
- * If non-null, returns only shortcuts associated with the activity.
+ * If non-null, returns only shortcuts associated with the activity, which are
+ * {@link ShortcutInfo}s that have null {@link ShortcutInfo#getActivityComponent()}, or
+ * {@link ShortcutInfo#getActivityComponent()} equals to {@code activity}.
*/
public void setActivity(@Nullable ComponentName activity) {
mActivity = activity;
@@ -426,8 +425,6 @@
* 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 {
@@ -447,8 +444,6 @@
* @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,
@@ -488,8 +483,6 @@
* @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) {
@@ -529,8 +522,6 @@
* #hasShortcutHostPermission()}.
*
* @param shortcut The target shortcut.
- *
- * @hide
*/
public ParcelFileDescriptor getShortcutIconFd(
@NonNull ShortcutInfo shortcut) {
@@ -548,8 +539,6 @@
* @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) {
@@ -566,6 +555,16 @@
}
}
+ /** TODO Javadoc */
+ public Drawable getShortcutIconDrawable(int density) {
+ throw new RuntimeException("TODO implement it");
+ }
+
+ /** TODO Javadoc */
+ public Drawable getShortcutBadgedIconDrawable(int density) {
+ throw new RuntimeException("TODO implement it");
+ }
+
/**
* Launches a shortcut.
*
@@ -579,8 +578,6 @@
* @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,
@@ -600,8 +597,6 @@
* @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 4340d04..3648d9e 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -22,6 +22,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcel;
@@ -50,8 +52,6 @@
* </ul>
*
* @see {@link ShortcutManager}.
- *
- * @hide
*/
public final class ShortcutInfo implements Parcelable {
/* @hide */
@@ -69,6 +69,15 @@
/* @hide */
public static final int FLAG_KEY_FIELDS_ONLY = 1 << 4;
+ /* @hide */
+ public static final int FLAG_FROM_MANIFEST = 1 << 5;
+
+ /* @hide */
+ public static final int FLAG_DISABLED = 1 << 6;
+
+ /* @hide */
+ public static final int FLAG_STRINGS_RESOLVED = 1 << 7;
+
/** @hide */
@IntDef(flag = true,
value = {
@@ -77,6 +86,9 @@
FLAG_HAS_ICON_RES,
FLAG_HAS_ICON_FILE,
FLAG_KEY_FIELDS_ONLY,
+ FLAG_FROM_MANIFEST,
+ FLAG_DISABLED,
+ FLAG_STRINGS_RESOLVED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ShortcutFlags {}
@@ -126,28 +138,37 @@
@Nullable
private Icon mIcon;
- @NonNull
+ private int mTitleResId;
+
+ @Nullable
private String mTitle;
+ private int mTextResId;
+
@Nullable
private String mText;
- @NonNull
+ private int mDisabledMessageResId;
+
+ @Nullable
+ private String mDisabledMessage;
+
+ @Nullable
private ArraySet<String> mCategories;
/**
* Intent *with extras removed*.
*/
- @NonNull
+ @Nullable
private Intent mIntent;
/**
* Extras for the intent.
*/
- @NonNull
+ @Nullable
private PersistableBundle mIntentPersistableExtras;
- private int mWeight;
+ private int mRank;
@Nullable
private PersistableBundle mExtras;
@@ -178,7 +199,11 @@
mActivityComponent = b.mActivityComponent;
mIcon = b.mIcon;
mTitle = b.mTitle;
+ mTitleResId = b.mTitleResId;
mText = b.mText;
+ mTextResId = b.mTextResId;
+ mDisabledMessage = b.mDisabledMessage;
+ mDisabledMessageResId = b.mDisabledMessageResId;
mCategories = clone(b.mCategories);
mIntent = b.mIntent;
if (mIntent != null) {
@@ -188,7 +213,7 @@
mIntentPersistableExtras = new PersistableBundle(intentExtras);
}
}
- mWeight = b.mWeight;
+ mRank = b.mRank;
mExtras = b.mExtras;
updateTimestamp();
}
@@ -204,7 +229,10 @@
*/
public void enforceMandatoryFields() {
Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided");
- Preconditions.checkStringNotEmpty(mTitle, "Shortcut title must be provided");
+ Preconditions.checkNotNull(mActivityComponent, "activityComponent must be provided");
+ if (mTitle == null && mTitleResId == 0) {
+ throw new IllegalArgumentException("Shortcut title must be provided");
+ }
Preconditions.checkNotNull(mIntent, "Shortcut Intent must be provided");
}
@@ -230,13 +258,17 @@
}
mTitle = source.mTitle;
+ mTitleResId = source.mTitleResId;
mText = source.mText;
+ mTextResId = source.mTextResId;
+ mDisabledMessage = source.mDisabledMessage;
+ mDisabledMessageResId = source.mDisabledMessageResId;
mCategories = clone(source.mCategories);
if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) {
mIntent = source.mIntent;
mIntentPersistableExtras = source.mIntentPersistableExtras;
}
- mWeight = source.mWeight;
+ mRank = source.mRank;
mExtras = source.mExtras;
} else {
// Set this bit.
@@ -244,6 +276,30 @@
}
}
+ /** @hide */
+ public void resolveStringsRequiringCrossUser(Context context) throws NameNotFoundException {
+ mFlags |= FLAG_STRINGS_RESOLVED;
+
+ if ((mTitleResId == 0) && (mTextResId == 0) && (mDisabledMessageResId == 0)) {
+ return; // Bail early.
+ }
+ final Resources res = context.getPackageManager().getResourcesForApplicationAsUser(
+ mPackageName, mUserId);
+
+ if (mTitleResId != 0) {
+ mTitle = res.getString(mTitleResId);
+ mTitleResId = 0;
+ }
+ if (mTextResId != 0) {
+ mText = res.getString(mTextResId);
+ mTextResId = 0;
+ }
+ if (mDisabledMessageResId != 0) {
+ mDisabledMessage = res.getString(mDisabledMessageResId);
+ mDisabledMessageResId = 0;
+ }
+ }
+
/**
* Copy a {@link ShortcutInfo}, optionally removing fields.
* @hide
@@ -277,9 +333,24 @@
}
if (source.mTitle != null) {
mTitle = source.mTitle;
+ mTitleResId = 0;
+ } else if (source.mTitleResId != 0) {
+ mTitle = null;
+ mTitleResId = source.getTitleResId();
}
if (source.mText != null) {
mText = source.mText;
+ mTextResId = 0;
+ } else if (source.mTextResId != 0) {
+ mText = null;
+ mTextResId = source.mTextResId;
+ }
+ if (source.mDisabledMessage != null) {
+ mDisabledMessage = source.mDisabledMessage;
+ mDisabledMessageResId = 0;
+ } else if (source.mDisabledMessageResId != 0) {
+ mDisabledMessage = null;
+ mDisabledMessageResId = source.mDisabledMessageResId;
}
if (source.mCategories != null) {
mCategories = clone(source.mCategories);
@@ -288,8 +359,8 @@
mIntent = source.mIntent;
mIntentPersistableExtras = source.mIntentPersistableExtras;
}
- if (source.mWeight != 0) {
- mWeight = source.mWeight;
+ if (source.mRank != 0) {
+ mRank = source.mRank;
}
if (source.mExtras != null) {
mExtras = source.mExtras;
@@ -310,7 +381,6 @@
throw getInvalidIconException();
}
if (icon.hasTint()) {
- // TODO support it
throw new IllegalArgumentException("Icons with tints are not supported");
}
@@ -335,15 +405,23 @@
private Icon mIcon;
+ private int mTitleResId;
+
private String mTitle;
+ private int mTextResId;
+
private String mText;
+ private int mDisabledMessageResId;
+
+ private String mDisabledMessage;
+
private Set<String> mCategories;
private Intent mIntent;
- private int mWeight;
+ private int mRank;
private PersistableBundle mExtras;
@@ -398,6 +476,13 @@
return this;
}
+ /** TODO Javadoc */
+ public Builder setTitleResId(int titleResId) {
+ Preconditions.checkState(mTitle == null, "title already set");
+ mTitleResId = titleResId;
+ return this;
+ }
+
/**
* Sets the title of a shortcut. This is a mandatory field.
*
@@ -406,10 +491,18 @@
*/
@NonNull
public Builder setTitle(@NonNull String title) {
+ Preconditions.checkState(mTitleResId == 0, "titleResId already set");
mTitle = Preconditions.checkStringNotEmpty(title, "title");
return this;
}
+ /** TODO Javadoc */
+ public Builder setTextResId(int textResId) {
+ Preconditions.checkState(mText == null, "text already set");
+ mTextResId = textResId;
+ return this;
+ }
+
/**
* Sets the text of a shortcut. This is an optional field.
*
@@ -418,10 +511,27 @@
*/
@NonNull
public Builder setText(@NonNull String text) {
+ Preconditions.checkState(mTextResId == 0, "textResId already set");
mText = Preconditions.checkStringNotEmpty(text, "text");
return this;
}
+ /** TODO Javadoc */
+ public Builder setDisabledMessageResId(int disabledMessageResId) {
+ Preconditions.checkState(mDisabledMessage == null, "disabledMessage already set");
+ mDisabledMessageResId = disabledMessageResId;
+ return this;
+ }
+
+ @NonNull
+ public Builder setDisabledMessage(@NonNull String disabledMessage) {
+ Preconditions.checkState(
+ mDisabledMessageResId == 0, "disabledMessageResId already set");
+ mDisabledMessage =
+ Preconditions.checkStringNotEmpty(disabledMessage, "disabledMessage");
+ return this;
+ }
+
/**
* Sets categories for a shortcut. Launcher applications may use this information to
* categorise shortcuts.
@@ -441,16 +551,16 @@
@NonNull
public Builder setIntent(@NonNull Intent intent) {
mIntent = Preconditions.checkNotNull(intent, "intent");
+ Preconditions.checkNotNull(mIntent.getAction(), "Intent action must be set.");
return this;
}
/**
- * Optionally sets the weight of a shortcut, which will be used by the launcher for sorting.
- * The larger the weight, the more "important" a shortcut is.
+ * TODO javadoc.
*/
@NonNull
- public Builder setWeight(int weight) {
- mWeight = weight;
+ public Builder setRank(int rank) {
+ mRank = rank;
return this;
}
@@ -528,6 +638,11 @@
return mTitle;
}
+ /** TODO Javadoc */
+ public int getTitleResId() {
+ return mTitleResId;
+ }
+
/**
* Return the shortcut text.
*/
@@ -536,6 +651,24 @@
return mText;
}
+ /** TODO Javadoc */
+ public int getTextResId() {
+ return mTextResId;
+ }
+
+ /**
+ * Return the message that should be shown when a shortcut in disabled state is launched.
+ */
+ @Nullable
+ public String getDisabledMessage() {
+ return mDisabledMessage;
+ }
+
+ /** TODO Javadoc */
+ public int getDisabledMessageResId() {
+ return mDisabledMessageResId;
+ }
+
/**
* Return the categories.
*/
@@ -585,11 +718,10 @@
}
/**
- * Return the weight of a shortcut, which will be used by Launcher for sorting.
- * The larger the weight, the more "important" a shortcut is.
+ * TODO Javadoc
*/
- public int getWeight() {
- return mWeight;
+ public int getRank() {
+ return mRank;
}
/**
@@ -656,6 +788,19 @@
}
/**
+ * Return whether a shortcut is published via manifest or not. If true, the shortcut is
+ * immutable.
+ */
+ public boolean isFromManifest() {
+ return hasFlags(FLAG_FROM_MANIFEST);
+ }
+
+ /** Return whether a shortcut is disabled by publisher or not. */
+ public boolean isDisabled() {
+ return !hasFlags(FLAG_DISABLED);
+ }
+
+ /**
* Return whether a shortcut's icon is a resource in the owning package.
*
* @see LauncherApps#getShortcutIconResId(ShortcutInfo)
@@ -664,6 +809,16 @@
return hasFlags(FLAG_HAS_ICON_RES);
}
+ /** @hide */
+ public boolean hasStringResources() {
+ return (mTitleResId != 0) || (mTextResId != 0) || (mDisabledMessageResId != 0);
+ }
+
+ /** @hide */
+ public boolean hasAnyResources() {
+ return hasIconResource() || hasStringResources();
+ }
+
/**
* Return whether a shortcut's icon is stored as a file.
*
@@ -690,6 +845,11 @@
return hasFlags(FLAG_KEY_FIELDS_ONLY);
}
+ /** TODO Javadoc */
+ public boolean hasStringResourcesResolved() {
+ return hasFlags(FLAG_STRINGS_RESOLVED);
+ }
+
/** @hide */
public void updateTimestamp() {
mLastChangedTimestamp = System.currentTimeMillis();
@@ -737,10 +897,14 @@
mActivityComponent = source.readParcelable(cl);
mIcon = source.readParcelable(cl);
mTitle = source.readString();
+ mTitleResId = source.readInt();
mText = source.readString();
+ mTextResId = source.readInt();
+ mDisabledMessage = source.readString();
+ mDisabledMessageResId = source.readInt();
mIntent = source.readParcelable(cl);
mIntentPersistableExtras = source.readParcelable(cl);
- mWeight = source.readInt();
+ mRank = source.readInt();
mExtras = source.readParcelable(cl);
mLastChangedTimestamp = source.readLong();
mFlags = source.readInt();
@@ -766,11 +930,15 @@
dest.writeParcelable(mActivityComponent, flags);
dest.writeParcelable(mIcon, flags);
dest.writeString(mTitle);
+ dest.writeInt(mTitleResId);
dest.writeString(mText);
+ dest.writeInt(mTextResId);
+ dest.writeString(mDisabledMessage);
+ dest.writeInt(mDisabledMessageResId);
dest.writeParcelable(mIntent, flags);
dest.writeParcelable(mIntentPersistableExtras, flags);
- dest.writeInt(mWeight);
+ dest.writeInt(mRank);
dest.writeParcelable(mExtras, flags);
dest.writeLong(mLastChangedTimestamp);
dest.writeInt(mFlags);
@@ -838,9 +1006,18 @@
sb.append(", title=");
sb.append(secure ? "***" : mTitle);
+ sb.append(", titleResId=");
+ sb.append(mTitleResId);
sb.append(", text=");
sb.append(secure ? "***" : mText);
+ sb.append(", textResId=");
+ sb.append(mTextResId);
+
+ sb.append(", disabledMessage=");
+ sb.append(secure ? "***" : mDisabledMessage);
+ sb.append(", disabledMessageResId=");
+ sb.append(mDisabledMessageResId);
sb.append(", categories=");
sb.append(mCategories);
@@ -848,8 +1025,8 @@
sb.append(", icon=");
sb.append(mIcon);
- sb.append(", weight=");
- sb.append(mWeight);
+ sb.append(", rank=");
+ sb.append(mRank);
sb.append(", timestamp=");
sb.append(mLastChangedTimestamp);
@@ -865,6 +1042,32 @@
sb.append(", flags=");
sb.append(mFlags);
+ sb.append(" [");
+ if (hasFlags(FLAG_DISABLED)) {
+ sb.append("X");
+ }
+ if (hasFlags(FLAG_FROM_MANIFEST)) {
+ sb.append("M");
+ }
+ if (hasFlags(FLAG_DYNAMIC)) {
+ sb.append("D");
+ }
+ if (hasFlags(FLAG_PINNED)) {
+ sb.append("P");
+ }
+ if (hasFlags(FLAG_HAS_ICON_FILE)) {
+ sb.append("If");
+ }
+ if (hasFlags(FLAG_HAS_ICON_RES)) {
+ sb.append("Ir");
+ }
+ if (hasFlags(FLAG_KEY_FIELDS_ONLY)) {
+ sb.append("K");
+ }
+ if (hasFlags(FLAG_STRINGS_RESOLVED)) {
+ sb.append("S");
+ }
+ sb.append("]");
if (includeInternalData) {
@@ -882,9 +1085,10 @@
/** @hide */
public ShortcutInfo(
@UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
- Icon icon, String title, String text, Set<String> categories, Intent intent,
+ Icon icon, String title, int titleResId, String text, int textResId,
+ String disabledMessage, int disabledMessageResId, Set<String> categories, Intent intent,
PersistableBundle intentPersistableExtras,
- int weight, PersistableBundle extras, long lastChangedTimestamp,
+ int rank, PersistableBundle extras, long lastChangedTimestamp,
int flags, int iconResId, String bitmapPath) {
mUserId = userId;
mId = id;
@@ -892,11 +1096,15 @@
mActivityComponent = activityComponent;
mIcon = icon;
mTitle = title;
+ mTitleResId = titleResId;
mText = text;
+ mTextResId = textResId;
+ mDisabledMessage = disabledMessage;
+ mDisabledMessageResId = disabledMessageResId;
mCategories = clone(categories);
mIntent = intent;
mIntentPersistableExtras = intentPersistableExtras;
- mWeight = weight;
+ mRank = rank;
mExtras = extras;
mLastChangedTimestamp = lastChangedTimestamp;
mFlags = flags;
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 16486e1..44fa98d 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.content.Context;
-import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -29,6 +28,8 @@
// TODO Enhance javadoc
/**
+ * <b>TODO: Update to reflect DR changes.</b><br>
+ *
* {@link ShortcutManager} manages shortcuts created by applications.
*
* <h3>Dynamic shortcuts and pinned shortcuts</h3>
@@ -80,16 +81,24 @@
*
* <h3>Backup and Restore</h3>
*
- * Shortcuts will be backed up and restored across devices. This means all information, including
- * IDs, must be meaningful on a different device.
+ * Pinned shortcuts will be backed up and restored across devices. This means all information
+ * within shortcuts, including IDs, must be meaningful on different devices.
+ *
+ * <p>Note that:
+ * <ul>
+ * <li>Dynamic shortcuts will not be backed up or restored.
+ * <li>Icons of pinned shortcuts will <b>not</b> be backed up for performance reasons, unless
+ * they refer to resources. Instead, launcher applications are supposed to back up and restore
+ * icons of pinned shortcuts by themselves, and thus from the user's point of view, pinned
+ * shortcuts will look to have icons restored.
+ * </ul>
+ *
*
* <h3>APIs for launcher</h3>
*
* Launcher applications should use {@link LauncherApps} to get shortcuts that are published from
* applications. Launcher applications can also pin shortcuts with
* {@link LauncherApps#pinShortcuts(String, List, UserHandle)}.
- *
- * @hide
*/
public class ShortcutManager {
private static final String TAG = "ShortcutManager";
@@ -149,8 +158,8 @@
}
/**
- * Publish a single dynamic shortcut. If there's already dynamic or pinned shortcuts with
- * the same ID, they will all be updated.
+ * Publish list of dynamic shortcuts. If there's already dynamic or pinned shortcuts with
+ * the same IDs, they will all be updated.
*
* <p>This API will be rate-limited.
*
@@ -169,7 +178,7 @@
}
/**
- * Delete a single dynamic shortcut by ID.
+ * Delete dynamic shortcuts by ID.
*/
public void removeDynamicShortcuts(@NonNull List<String> shortcutIds) {
try {
@@ -221,6 +230,45 @@
}
/**
+ * TODO Javadoc
+ */
+ public void disableShortcuts(@NonNull List<String> shortcutIds) {
+ try {
+ mService.disableShortcuts(mContext.getPackageName(), shortcutIds,
+ /* disabledMessage =*/ null, /* disabledMessageResId =*/ 0,
+ injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * TODO Javadoc
+ */
+ public void disableShortcuts(@NonNull List<String> shortcutIds, int disabledMessageResId) {
+ try {
+ mService.disableShortcuts(mContext.getPackageName(), shortcutIds,
+ /* disabledMessage =*/ null, disabledMessageResId,
+ injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * TODO Javadoc
+ */
+ public void disableShortcuts(@NonNull List<String> shortcutIds, String disabledMessage) {
+ try {
+ mService.disableShortcuts(mContext.getPackageName(), shortcutIds,
+ disabledMessage, /* disabledMessageResId =*/ 0,
+ injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return the max number of dynamic shortcuts that each application can have at a time.
*/
public int getMaxDynamicShortcutCount() {
@@ -270,6 +318,15 @@
}
}
+ public void reportShortcutUsed(String shortcutId) {
+ try {
+ mService.reportShortcutUsed(mContext.getPackageName(), shortcutId,
+ injectMyUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** @hide injection point */
@VisibleForTesting
protected int injectMyUserId() {
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index dd3a36c..6cd8403 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -28,8 +28,8 @@
*/
public class UserInfo implements Parcelable {
- /** 8 bits for user type */
- public static final int FLAG_MASK_USER_TYPE = 0x000000FF;
+ /** 16 bits for user type */
+ public static final int FLAG_MASK_USER_TYPE = 0x0000FFFF;
/**
* *************************** NOTE ***************************
@@ -87,6 +87,11 @@
*/
public static final int FLAG_EPHEMERAL = 0x00000100;
+ /**
+ * User is for demo purposes only and can be removed at any time.
+ */
+ public static final int FLAG_DEMO = 0x00000200;
+
public static final int NO_PROFILE_GROUP_ID = UserHandle.USER_NULL;
public int id;
@@ -153,6 +158,10 @@
return (flags & FLAG_INITIALIZED) == FLAG_INITIALIZED;
}
+ public boolean isDemo() {
+ return (flags & FLAG_DEMO) == FLAG_DEMO;
+ }
+
/**
* Returns true if the user is a split system user.
* <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 41ff9fd..783c25a 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -290,6 +290,7 @@
// Guarded by NfcAdapter.class
static boolean sIsInitialized = false;
+ static boolean sHasNfcFeature;
// Final after first constructor, except for
// attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
@@ -435,42 +436,65 @@
}
/**
+ * Helper to check if this device is NFC HCE capable, by checking for
+ * FEATURE_NFC_HOST_CARD_EMULATION and/or FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * but without using a context.
+ */
+ private static boolean hasNfcHceFeature() {
+ IPackageManager pm = ActivityThread.getPackageManager();
+ if (pm == null) {
+ Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
+ return false;
+ }
+ try {
+ return pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)
+ || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
+ return false;
+ }
+ }
+
+ /**
* Returns the NfcAdapter for application context,
* or throws if NFC is not available.
* @hide
*/
public static synchronized NfcAdapter getNfcAdapter(Context context) {
if (!sIsInitialized) {
+ sHasNfcFeature = hasNfcFeature();
+ boolean hasHceFeature = hasNfcHceFeature();
/* is this device meant to have NFC */
- if (!hasNfcFeature()) {
+ if (!sHasNfcFeature && !hasHceFeature) {
Log.v(TAG, "this device does not have NFC support");
throw new UnsupportedOperationException();
}
-
sService = getServiceInterface();
if (sService == null) {
Log.e(TAG, "could not retrieve NFC service");
throw new UnsupportedOperationException();
}
- try {
- sTagService = sService.getNfcTagInterface();
- } catch (RemoteException e) {
- Log.e(TAG, "could not retrieve NFC Tag service");
- throw new UnsupportedOperationException();
+ if (sHasNfcFeature) {
+ try {
+ sTagService = sService.getNfcTagInterface();
+ } catch (RemoteException e) {
+ Log.e(TAG, "could not retrieve NFC Tag service");
+ throw new UnsupportedOperationException();
+ }
}
-
- try {
- sCardEmulationService = sService.getNfcCardEmulationInterface();
- } catch (RemoteException e) {
- Log.e(TAG, "could not retrieve card emulation service");
- throw new UnsupportedOperationException();
- }
-
- try {
- sNfcFCardEmulationService = sService.getNfcFCardEmulationInterface();
- } catch (RemoteException e) {
- Log.e(TAG, "could not retrieve NFC-F card emulation service");
- throw new UnsupportedOperationException();
+ if (hasHceFeature) {
+ try {
+ sNfcFCardEmulationService = sService.getNfcFCardEmulationInterface();
+ } catch (RemoteException e) {
+ Log.e(TAG, "could not retrieve NFC-F card emulation service");
+ throw new UnsupportedOperationException();
+ }
+ try {
+ sCardEmulationService = sService.getNfcCardEmulationInterface();
+ } catch (RemoteException e) {
+ Log.e(TAG, "could not retrieve card emulation service");
+ throw new UnsupportedOperationException();
+ }
}
sIsInitialized = true;
@@ -838,8 +862,14 @@
*
* @param uris an array of Uri(s) to push over Android Beam
* @param activity activity for which the Uri(s) will be pushed
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
*/
public void setBeamPushUris(Uri[] uris, Activity activity) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
if (activity == null) {
throw new NullPointerException("activity cannot be null");
}
@@ -914,8 +944,14 @@
*
* @param callback callback, or null to disable
* @param activity activity for which the Uri(s) will be pushed
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
*/
public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
if (activity == null) {
throw new NullPointerException("activity cannot be null");
}
@@ -992,9 +1028,15 @@
* @param activities optional additional activities, however we strongly recommend
* to only register one at a time, and to do so in that activity's
* {@link Activity#onCreate}
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
*/
public void setNdefPushMessage(NdefMessage message, Activity activity,
Activity ... activities) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
int targetSdkVersion = getSdkVersion();
try {
if (activity == null) {
@@ -1024,6 +1066,11 @@
*/
@SystemApi
public void setNdefPushMessage(NdefMessage message, Activity activity, int flags) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
if (activity == null) {
throw new NullPointerException("activity cannot be null");
}
@@ -1094,9 +1141,15 @@
* @param activities optional additional activities, however we strongly recommend
* to only register one at a time, and to do so in that activity's
* {@link Activity#onCreate}
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
*/
public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
Activity ... activities) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
int targetSdkVersion = getSdkVersion();
try {
if (activity == null) {
@@ -1168,9 +1221,15 @@
* @param activities optional additional activities, however we strongly recommend
* to only register one at a time, and to do so in that activity's
* {@link Activity#onCreate}
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
*/
public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
Activity activity, Activity ... activities) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
int targetSdkVersion = getSdkVersion();
try {
if (activity == null) {
@@ -1227,9 +1286,15 @@
* @param techLists the tech lists used to perform matching for dispatching of the
* {@link NfcAdapter#ACTION_TECH_DISCOVERED} intent
* @throws IllegalStateException if the Activity is not currently in the foreground
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
*/
public void enableForegroundDispatch(Activity activity, PendingIntent intent,
IntentFilter[] filters, String[][] techLists) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
if (activity == null || intent == null) {
throw new NullPointerException();
}
@@ -1263,8 +1328,14 @@
*
* @param activity the Activity to disable dispatch to
* @throws IllegalStateException if the Activity has already been paused
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
*/
public void disableForegroundDispatch(Activity activity) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity,
mForegroundDispatchListener);
disableForegroundDispatchInternal(activity, false);
@@ -1309,9 +1380,15 @@
* @param callback the callback to be called when a tag is discovered
* @param flags Flags indicating poll technologies and other optional parameters
* @param extras Additional extras for configuring reader mode.
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
*/
public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
Bundle extras) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
mNfcActivityManager.enableReaderMode(activity, callback, flags, extras);
}
@@ -1321,8 +1398,14 @@
* all supported tag technologies.
*
* @param activity the Activity that currently has reader mode enabled
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
*/
public void disableReaderMode(Activity activity) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
mNfcActivityManager.disableReaderMode(activity);
}
@@ -1349,8 +1432,14 @@
*
* @param activity the current foreground Activity that has registered data to share
* @return whether the Beam animation was successfully invoked
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
*/
public boolean invokeBeam(Activity activity) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
if (activity == null) {
throw new NullPointerException("activity may not be null.");
}
@@ -1404,10 +1493,16 @@
* @param activity foreground activity
* @param message a NDEF Message to push over NFC
* @throws IllegalStateException if the activity is not currently in the foreground
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
* @deprecated use {@link #setNdefPushMessage} instead
*/
@Deprecated
public void enableForegroundNdefPush(Activity activity, NdefMessage message) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
if (activity == null || message == null) {
throw new NullPointerException();
}
@@ -1432,10 +1527,16 @@
*
* @param activity the Foreground activity
* @throws IllegalStateException if the Activity has already been paused
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
* @deprecated use {@link #setNdefPushMessage} instead
*/
@Deprecated
public void disableForegroundNdefPush(Activity activity) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
if (activity == null) {
throw new NullPointerException();
}
@@ -1452,6 +1553,9 @@
*/
@SystemApi
public boolean enableNdefPush() {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
try {
return sService.enableNdefPush();
} catch (RemoteException e) {
@@ -1467,6 +1571,11 @@
*/
@SystemApi
public boolean disableNdefPush() {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
try {
return sService.disableNdefPush();
} catch (RemoteException e) {
@@ -1497,8 +1606,14 @@
*
* @see android.provider.Settings#ACTION_NFCSHARING_SETTINGS
* @return true if NDEF Push feature is enabled
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
*/
public boolean isNdefPushEnabled() {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
try {
return sService.isNdefPushEnabled();
} catch (RemoteException e) {
@@ -1623,6 +1738,11 @@
@SystemApi
public boolean addNfcUnlockHandler(final NfcUnlockHandler unlockHandler,
String[] tagTechnologies) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
// If there are no tag technologies, don't bother adding unlock handler
if (tagTechnologies.length == 0) {
return false;
@@ -1666,6 +1786,11 @@
*/
@SystemApi
public boolean removeNfcUnlockHandler(NfcUnlockHandler unlockHandler) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
try {
synchronized (mLock) {
if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index dcec982..2613994 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1972,6 +1972,10 @@
if (!supportsMultipleUsers()) {
return false;
}
+ // If Demo Mode is on, don't show user switcher
+ if (isDeviceInDemoMode(mContext)) {
+ return false;
+ }
List<UserInfo> users = getUsers(true);
if (users == null) {
return false;
@@ -1988,6 +1992,14 @@
}
/**
+ * @hide
+ */
+ public static boolean isDeviceInDemoMode(Context context) {
+ return Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.DEVICE_DEMO_MODE, 0) > 0;
+ }
+
+ /**
* Returns a serial number on this device for a given userHandle. User handles can be recycled
* when deleting and creating users, but serial numbers are not reused until the device is wiped.
* @param userHandle
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 893eb37..fbd61cf 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -179,6 +179,7 @@
* <li>{@link #VOICEMAIL_TYPE}</li>
* <li>{@link #REJECTED_TYPE}</li>
* <li>{@link #BLOCKED_TYPE}</li>
+ * <li>{@link #ANSWERED_EXTERNALLY_TYPE}</li>
* </ul>
* </p>
*/
@@ -200,7 +201,6 @@
* Call log type for a call which was answered on another device. Used in situations where
* a call rings on multiple devices simultaneously and it ended up being answered on a
* device other than the current one.
- * @hide
*/
public static final int ANSWERED_EXTERNALLY_TYPE = 7;
@@ -214,10 +214,7 @@
/** Call had video. */
public static final int FEATURES_VIDEO = 0x1;
- /**
- * Call was pulled externally.
- * @hide
- */
+ /** Call was pulled externally. */
public static final int FEATURES_PULLED_EXTERNALLY = 0x2;
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f833f3f..237bc28 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1277,6 +1277,17 @@
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_WEBVIEW_SETTINGS = "android.settings.WEBVIEW_SETTINGS";
+ /**
+ * Activity Action: Show the Deletion Helper settings.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_DELETION_HELPER_SETTINGS
+ = "android.settings.DELETION_HELPER_SETTINGS";
+
// End of Intent actions for Settings
/**
@@ -8536,6 +8547,15 @@
public static final String SAFE_BOOT_DISALLOWED = "safe_boot_disallowed";
/**
+ * Whether this device is currently in retail demo mode. If true, device
+ * usage is severely limited.
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ * @hide
+ */
+ public static final String DEVICE_DEMO_MODE = "device_demo_mode";
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
*
@@ -8932,6 +8952,15 @@
* @hide
*/
public static final String ENABLE_CELLULAR_ON_BOOT = "enable_cellular_on_boot";
+
+ /**
+ * Whether toggling OEM unlock is disallowed. If disallowed, it is not possible to enable or
+ * disable OEM unlock.
+ * <p>
+ * Type: int (0: allow OEM unlock setting. 1: disallow OEM unlock)
+ * @hide
+ */
+ public static final String OEM_UNLOCK_DISALLOWED = "oem_unlock_disallowed";
}
/**
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index 6a3cc02..55aac9f 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -107,6 +107,47 @@
public static final String ACTION_SYNC_VOICEMAIL = "android.provider.action.SYNC_VOICEMAIL";
/**
+ * Broadcast intent to inform a new visual voicemail SMS has been received. This intent will
+ * only be delivered to the voicemail client. The intent will have the following extra values:
+ *
+ * <ul>
+ * <li><em>{@link #EXTRA_VOICEMAIL_SMS_TYPE}</em> - (String) The event type of the SMS. Common
+ * values are "SYNC" or "STATUS"</li>
+ * <li><em>{@link #EXTRA_VOICEMAIL_SMS_DATA}</em> - (Bundle) The fields sent by the SMS</li>
+ * <li><em>{@link #EXTRA_VOICEMAIL_SMS_SUBID}</em> - (Integer) The subscription ID of the
+ * phone account that received the SMS</li>
+ * </ul>
+ */
+ /** @hide */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_VOICEMAIL_SMS_RECEIVED =
+ "android.intent.action.VOICEMAIL_SMS_RECEIVED";
+
+ /**
+ * Extra included in {@link #ACTION_VOICEMAIL_SMS_RECEIVED} broadcast intents to indicate the
+ * event type of the SMS. Common values are "SYNC" or "STATUS"
+ */
+ /** @hide */
+ public static final String EXTRA_VOICEMAIL_SMS_PREFIX =
+ "com.android.voicemail.extra.VOICEMAIL_SMS_PREFIX";
+
+ /**
+ * Extra included in {@link #ACTION_VOICEMAIL_SMS_RECEIVED} broadcast intents to indicate the
+ * fields sent by the SMS
+ */
+ /** @hide */
+ public static final String EXTRA_VOICEMAIL_SMS_FIELDS =
+ "com.android.voicemail.extra.VOICEMAIL_SMS_FIELDS";
+
+ /**
+ * Extra included in {@link #ACTION_VOICEMAIL_SMS_RECEIVED} broadcast intents to indicate he
+ * subscription ID of the phone account that received the SMS.
+ */
+ /** @hide */
+ public static final String EXTRA_VOICEMAIL_SMS_SUBID =
+ "com.android.voicemail.extra.VOICEMAIL_SMS_SUBID";
+
+ /**
* Extra included in {@link Intent#ACTION_PROVIDER_CHANGED} broadcast intents to indicate if the
* receiving package made this change.
*/
diff --git a/core/java/android/service/carrier/CarrierIdentifier.java b/core/java/android/service/carrier/CarrierIdentifier.java
index a70c24d..b47e872 100644
--- a/core/java/android/service/carrier/CarrierIdentifier.java
+++ b/core/java/android/service/carrier/CarrierIdentifier.java
@@ -126,4 +126,13 @@
mGid1 = in.readString();
mGid2 = in.readString();
}
+
+ /** @hide */
+ public interface MatchType {
+ int ALL = 0;
+ int SPN = 1;
+ int IMSI_PREFIX = 2;
+ int GID1 = 3;
+ int GID2 = 4;
+ }
}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 636384c..599c9c72 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -796,8 +796,16 @@
public static final int KEYCODE_COPY = 278;
/** Key code constant: Paste key. */
public static final int KEYCODE_PASTE = 279;
+ /** Key code constant: fingerprint navigation up */
+ public static final int KEYCODE_FP_NAV_UP = 280;
+ /** Key code constant: fingerprint navigation down */
+ public static final int KEYCODE_FP_NAV_DOWN = 281;
+ /** Key code constant: fingerprint navigation left*/
+ public static final int KEYCODE_FP_NAV_LEFT = 282;
+ /** Key code constant: fingerprint navigation right */
+ public static final int KEYCODE_FP_NAV_RIGHT = 283;
- private static final int LAST_KEYCODE = KEYCODE_PASTE;
+ private static final int LAST_KEYCODE = KEYCODE_FP_NAV_RIGHT;
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
@@ -1844,6 +1852,10 @@
case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
case KeyEvent.KEYCODE_BRIGHTNESS_UP:
case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
+ case KeyEvent.KEYCODE_FP_NAV_UP:
+ case KeyEvent.KEYCODE_FP_NAV_DOWN:
+ case KeyEvent.KEYCODE_FP_NAV_LEFT:
+ case KeyEvent.KEYCODE_FP_NAV_RIGHT:
return true;
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 6dc5ff7..4a0f350 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -3176,7 +3176,9 @@
// we don't add a filter to the static version returned by getSystemService.
inflater = inflater.cloneInContext(inflationContext);
inflater.setFilter(this);
- return inflater.inflate(rv.getLayoutId(), parent, false);
+ View v = inflater.inflate(rv.getLayoutId(), parent, false);
+ v.setTagInternal(R.id.widget_frame, rv.getLayoutId());
+ return v;
}
private static void loadTransitionOverride(Context context,
@@ -3354,7 +3356,7 @@
// across orientation change, and has the RemoteViews re-applied in the new orientation,
// we throw an exception, since the layouts may be completely unrelated.
if (hasLandscapeAndPortraitLayouts()) {
- if (v.getId() != rvToApply.getLayoutId()) {
+ if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
" that does not share the same root layout id.");
}
@@ -3390,7 +3392,7 @@
// across orientation change, and has the RemoteViews re-applied in the new orientation,
// we throw an exception, since the layouts may be completely unrelated.
if (hasLandscapeAndPortraitLayouts()) {
- if (v.getId() != rvToApply.getLayoutId()) {
+ if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
" that does not share the same root layout id.");
}
diff --git a/core/java/com/android/internal/app/AlertActivity.java b/core/java/com/android/internal/app/AlertActivity.java
index ed48b0d..35ffa71 100644
--- a/core/java/com/android/internal/app/AlertActivity.java
+++ b/core/java/com/android/internal/app/AlertActivity.java
@@ -49,7 +49,7 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mAlert = new AlertController(this, this, getWindow());
+ mAlert = AlertController.create(this, this, getWindow());
mAlertParams = new AlertController.AlertParams(this);
}
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index b7ac600..c5ed29f 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -61,14 +61,15 @@
import java.lang.ref.WeakReference;
public class AlertController {
+ public static final int MICRO = 1;
private final Context mContext;
private final DialogInterface mDialogInterface;
- private final Window mWindow;
+ protected final Window mWindow;
private CharSequence mTitle;
- private CharSequence mMessage;
- private ListView mListView;
+ protected CharSequence mMessage;
+ protected ListView mListView;
private View mView;
private int mViewLayoutResId;
@@ -91,14 +92,14 @@
private CharSequence mButtonNeutralText;
private Message mButtonNeutralMessage;
- private ScrollView mScrollView;
+ protected ScrollView mScrollView;
private int mIconId = 0;
private Drawable mIcon;
private ImageView mIconView;
private TextView mTitleView;
- private TextView mMessageView;
+ protected TextView mMessageView;
private View mCustomTitleView;
private boolean mForceInverseBackground;
@@ -176,7 +177,21 @@
return outValue.data != 0;
}
- public AlertController(Context context, DialogInterface di, Window window) {
+ public static final AlertController create(Context context, DialogInterface di, Window window) {
+ final TypedArray a = context.obtainStyledAttributes(
+ null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
+ int controllerType = a.getInt(R.styleable.AlertDialog_controllerType, 0);
+ a.recycle();
+
+ switch (controllerType) {
+ case MICRO:
+ return new MicroAlertController(context, di, window);
+ default:
+ return new AlertController(context, di, window);
+ }
+ }
+
+ protected AlertController(Context context, DialogInterface di, Window window) {
mContext = context;
mDialogInterface = di;
mWindow = window;
@@ -643,7 +658,7 @@
}
}
- private void setupContent(ViewGroup contentPanel) {
+ protected void setupContent(ViewGroup contentPanel) {
mScrollView = (ScrollView) contentPanel.findViewById(R.id.scrollView);
mScrollView.setFocusable(false);
@@ -871,8 +886,8 @@
listView.setAdapter(mAdapter);
final int checkedItem = mCheckedItem;
if (checkedItem > -1) {
- listView.setItemChecked(checkedItem, true);
- listView.setSelection(checkedItem);
+ listView.setItemChecked(checkedItem + listView.getHeaderViewsCount(), true);
+ listView.setSelection(checkedItem + listView.getHeaderViewsCount());
}
}
}
@@ -1051,7 +1066,8 @@
if (mCheckedItems != null) {
boolean isItemChecked = mCheckedItems[position];
if (isItemChecked) {
- listView.setItemChecked(position, true);
+ listView.setItemChecked(
+ position + listView.getHeaderViewsCount(), true);
}
}
return view;
@@ -1072,7 +1088,8 @@
public void bindView(View view, Context context, Cursor cursor) {
CheckedTextView text = (CheckedTextView) view.findViewById(R.id.text1);
text.setText(cursor.getString(mLabelIndex));
- listView.setItemChecked(cursor.getPosition(),
+ listView.setItemChecked(
+ cursor.getPosition() + listView.getHeaderViewsCount(),
cursor.getInt(mIsCheckedIndex) == 1);
}
@@ -1116,6 +1133,7 @@
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+ position -= listView.getHeaderViewsCount();
mOnClickListener.onClick(dialog.mDialogInterface, position);
if (!mIsSingleChoice) {
dialog.mDialogInterface.dismiss();
@@ -1126,6 +1144,7 @@
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+ position -= listView.getHeaderViewsCount();
if (mCheckedItems != null) {
mCheckedItems[position] = listView.isItemChecked(position);
}
diff --git a/core/java/com/android/internal/app/MicroAlertController.java b/core/java/com/android/internal/app/MicroAlertController.java
new file mode 100644
index 0000000..085b226
--- /dev/null
+++ b/core/java/com/android/internal/app/MicroAlertController.java
@@ -0,0 +1,85 @@
+/*
+ * 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.internal.app;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.AbsListView;
+
+import com.android.internal.app.AlertController;
+import com.android.internal.R;
+
+public class MicroAlertController extends AlertController {
+ public MicroAlertController(Context context, DialogInterface di, Window window) {
+ super(context, di, window);
+ }
+
+ @Override
+ protected void setupContent(ViewGroup contentPanel) {
+ // Special case for small screen - the scroll view is higher in hierarchy
+ mScrollView = (ScrollView) mWindow.findViewById(R.id.scrollView);
+
+ // Special case for users that only want to display a String
+ mMessageView = (TextView) contentPanel.findViewById(R.id.message);
+ if (mMessageView == null) {
+ return;
+ }
+
+ if (mMessage != null) {
+ mMessageView.setText(mMessage);
+ } else {
+ // no message, remove associated views
+ mMessageView.setVisibility(View.GONE);
+ contentPanel.removeView(mMessageView);
+
+ if (mListView != null) {
+ // has ListView, swap ScrollView with ListView
+
+ // move topPanel into header of ListView
+ View topPanel = mScrollView.findViewById(R.id.topPanel);
+ ((ViewGroup) topPanel.getParent()).removeView(topPanel);
+ topPanel.setLayoutParams(
+ new AbsListView.LayoutParams(topPanel.getLayoutParams()));
+ mListView.addHeaderView(topPanel, null, false);
+
+ // move buttonPanel into footer of ListView
+ View buttonPanel = mScrollView.findViewById(R.id.buttonPanel);
+ ((ViewGroup) buttonPanel.getParent()).removeView(buttonPanel);
+ buttonPanel.setLayoutParams(
+ new AbsListView.LayoutParams(buttonPanel.getLayoutParams()));
+ mListView.addFooterView(buttonPanel, null, false);
+
+ // swap ScrollView w/ ListView
+ final ViewGroup scrollParent = (ViewGroup) mScrollView.getParent();
+ final int childIndex = scrollParent.indexOfChild(mScrollView);
+ scrollParent.removeViewAt(childIndex);
+ scrollParent.addView(mListView, childIndex,
+ new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ } else {
+ // no content, just hide everything
+ contentPanel.setVisibility(View.GONE);
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 085e159..0a4ac0d 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -69,9 +69,13 @@
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto;
import com.android.internal.widget.ResolverDrawerLayout;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
@@ -359,6 +363,12 @@
if (isVoiceInteraction()) {
onSetupVoiceInteraction();
}
+ final Set<String> categories = intent.getCategories();
+ MetricsLogger.action(this, mAdapter.hasFilteredItem()
+ ? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
+ : MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED,
+ intent.getAction() + ":" + intent.getType() + ":"
+ + (categories != null ? Arrays.toString(categories.toArray()) : ""));
}
public final void setFilteredComponents(ComponentName[] components) {
@@ -649,6 +659,19 @@
TargetInfo target = mAdapter.targetInfoForPosition(which, filtered);
if (onTargetSelected(target, always)) {
+ if (always && filtered) {
+ MetricsLogger.action(
+ this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_ALWAYS);
+ } else if (filtered) {
+ MetricsLogger.action(
+ this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE);
+ } else {
+ MetricsLogger.action(
+ this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_TAP);
+ }
+ MetricsLogger.action(this, mAdapter.hasFilteredItem()
+ ? MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED
+ : MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED);
finish();
}
}
diff --git a/core/res/res/layout-notround/alert_dialog_header_micro.xml b/core/res/res/layout-notround/alert_dialog_header_micro.xml
new file mode 100644
index 0000000..fc840d9
--- /dev/null
+++ b/core/res/res/layout-notround/alert_dialog_header_micro.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="top|center_horizontal"
+ android:minHeight="@dimen/alert_dialog_title_height">
+ <ImageView android:id="@+id/icon"
+ android:maxHeight="24dp"
+ android:maxWidth="24dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginBottom="8dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@null" />
+ <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
+ style="?android:attr/windowTitleStyle"
+ android:ellipsize="end"
+ android:layout_marginStart="8dp"
+ android:layout_marginBottom="8dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart" />
+</LinearLayout>
diff --git a/core/res/res/layout-round/alert_dialog_header_micro.xml b/core/res/res/layout-round/alert_dialog_header_micro.xml
new file mode 100644
index 0000000..6f7ae02
--- /dev/null
+++ b/core/res/res/layout-round/alert_dialog_header_micro.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="top|center_horizontal"
+ android:minHeight="@dimen/alert_dialog_title_height">
+ <ImageView android:id="@+id/icon"
+ android:maxHeight="24dp"
+ android:maxWidth="24dp"
+ android:layout_marginTop="12dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@null" />
+ <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
+ style="?android:attr/windowTitleStyle"
+ android:ellipsize="end"
+ android:layout_marginTop="36dp"
+ android:layout_marginBottom="4dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="center" />
+</FrameLayout>
diff --git a/core/res/res/layout-watch/input_method_extract_view.xml b/core/res/res/layout-watch/input_method_extract_view.xml
index e3cd2ce..de4ca69 100644
--- a/core/res/res/layout-watch/input_method_extract_view.xml
+++ b/core/res/res/layout-watch/input_method_extract_view.xml
@@ -34,7 +34,6 @@
android:textColor="@color/primary_text_default_material_dark"
android:textColorHighlight="@color/accent_material_dark"
android:textSize="18dp"
- android:cursorVisible="false"
android:gravity="bottom|right"
/>
diff --git a/core/res/res/layout/alert_dialog_micro.xml b/core/res/res/layout/alert_dialog_micro.xml
index abdbd16..04f8a2a 100644
--- a/core/res/res/layout/alert_dialog_micro.xml
+++ b/core/res/res/layout/alert_dialog_micro.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2014 The Android Open Source Project
+ ~ 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.
@@ -14,127 +14,100 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/parentPanel"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@android:color/white"
- android:layout_gravity="center"
- android:orientation="vertical">
-
- <LinearLayout android:id="@+id/topPanel"
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/parentPanel"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <View android:id="@+id/titleDividerTop"
+ android:layout_height="match_parent">
+ <ScrollView
+ android:id="@+id/scrollView"
android:layout_width="match_parent"
- android:layout_height="2dip"
- android:visibility="gone"
- android:background="@android:color/holo_blue_light" />
- <LinearLayout android:id="@+id/title_template"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center_vertical|start"
- android:minHeight="@dimen/alert_dialog_title_height"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip">
- <ImageView android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingEnd="8dip"
- android:src="@null" />
- <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
- style="?android:attr/windowTitleStyle"
- android:singleLine="true"
- android:ellipsize="end"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAlignment="viewStart" />
- </LinearLayout>
- <View android:id="@+id/titleDivider"
- android:layout_width="match_parent"
- android:layout_height="2dip"
- android:visibility="gone"
- android:background="@android:color/holo_blue_light" />
- <!-- If the client uses a customTitle, it will be added here. -->
- </LinearLayout>
-
- <LinearLayout android:id="@+id/contentPanel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:orientation="vertical"
- android:minHeight="64dp">
- <ScrollView android:id="@+id/scrollView"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clipToPadding="false">
- <TextView android:id="@+id/message"
- style="?android:attr/textAppearanceMedium"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="16dip"
- android:paddingEnd="16dip"
- android:paddingTop="8dip"
- android:paddingBottom="8dip"/>
- </ScrollView>
- </LinearLayout>
-
- <FrameLayout android:id="@+id/customPanel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:minHeight="64dp">
- <FrameLayout android:id="@+android:id/custom"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
- </FrameLayout>
-
- <LinearLayout android:id="@+id/buttonPanel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- android:orientation="vertical"
- android:divider="?android:attr/dividerHorizontal"
- android:showDividers="beginning"
- android:dividerPadding="0dip">
+ android:layout_height="match_parent">
<LinearLayout
- style="?android:attr/buttonBarStyle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layoutDirection="locale"
- android:measureWithLargestChild="true">
- <Button android:id="@+id/button2"
- android:layout_width="wrap_content"
- android:layout_gravity="start"
- android:layout_weight="1"
- android:maxLines="2"
- style="?android:attr/buttonBarButtonStyle"
- android:textSize="14sp"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- android:layout_height="wrap_content" />
- <Button android:id="@+id/button3"
- android:layout_width="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_weight="1"
- android:maxLines="2"
- style="?android:attr/buttonBarButtonStyle"
- android:textSize="14sp"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- android:layout_height="wrap_content" />
- <Button android:id="@+id/button1"
- android:layout_width="wrap_content"
- android:layout_gravity="end"
- android:layout_weight="1"
- android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?android:attr/buttonBarButtonStyle"
- android:textSize="14sp"
- android:layout_height="wrap_content" />
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <!-- Top Panel -->
+ <FrameLayout
+ android:paddingLeft="@dimen/dialog_padding_micro"
+ android:paddingRight="@dimen/dialog_padding_micro"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/topPanel">
+ <include android:id="@+id/title_template"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ layout="@layout/alert_dialog_header_micro"/>
+ </FrameLayout>
+
+ <!-- Content Panel -->
+ <FrameLayout android:id="@+id/contentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false">
+ <TextView android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Micro.AlertDialog.Message"
+ android:paddingStart="@dimen/dialog_padding_micro"
+ android:paddingEnd="@dimen/dialog_padding_micro"
+ android:paddingTop="8dip"
+ android:paddingBottom="8dip"/>
+ </FrameLayout>
+
+ <!-- Custom Panel, to replace content panel if needed -->
+ <FrameLayout android:id="@+id/customPanel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:minHeight="64dp">
+ <FrameLayout android:id="@+android:id/custom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+
+ <!-- Button Panel -->
+ <FrameLayout
+ android:id="@+id/buttonPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:divider="?android:attr/dividerHorizontal"
+ android:showDividers="beginning"
+ android:dividerPadding="0dip"
+ android:orientation="vertical"
+ android:minHeight="@dimen/alert_dialog_button_bar_height"
+ android:paddingBottom="@dimen/dialog_padding_micro"
+ style="?android:attr/buttonBarStyle"
+ android:layoutDirection="locale"
+ android:measureWithLargestChild="true">
+ <Button android:id="@+id/button1"
+ android:layout_gravity="start"
+ android:layout_weight="1"
+ android:layout_marginLeft="@dimen/dialog_padding_micro"
+ android:layout_marginRight="@dimen/dialog_padding_micro"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ <Button android:id="@+id/button3"
+ android:layout_gravity="start"
+ android:layout_weight="1"
+ android:layout_marginLeft="@dimen/dialog_padding_micro"
+ android:layout_marginRight="@dimen/dialog_padding_micro"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ <Button android:id="@+id/button2"
+ android:layout_gravity="start"
+ android:layout_weight="1"
+ android:layout_marginLeft="@dimen/dialog_padding_micro"
+ android:layout_marginRight="@dimen/dialog_padding_micro"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+ </FrameLayout>
</LinearLayout>
- </LinearLayout>
-</LinearLayout>
+ </ScrollView>
+</FrameLayout>
diff --git a/core/res/res/values-notround/dimens_micro.xml b/core/res/res/values-notround/dimens_micro.xml
new file mode 100644
index 0000000..326f7e5
--- /dev/null
+++ b/core/res/res/values-notround/dimens_micro.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <dimen name="dialog_padding_micro">8dp</dimen>
+ <dimen name="list_item_padding_horizontal_micro">16dp</dimen>
+</resources>
diff --git a/core/res/res/values-notround/styles_micro.xml b/core/res/res/values-notround/styles_micro.xml
new file mode 100644
index 0000000..c45e36459
--- /dev/null
+++ b/core/res/res/values-notround/styles_micro.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <style name="TextAppearance.Micro.AlertDialog.Message" parent="TextAppearance.Micro.Small">
+ <item name="textAlignment">viewStart</item>
+ </style>
+</resources>
diff --git a/core/res/res/values-round/dimens_micro.xml b/core/res/res/values-round/dimens_micro.xml
new file mode 100644
index 0000000..9bf97dd
--- /dev/null
+++ b/core/res/res/values-round/dimens_micro.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <dimen name="dialog_padding_micro">32dp</dimen>
+ <dimen name="list_item_padding_horizontal_micro">22dp</dimen>
+</resources>
diff --git a/core/res/res/values-round/styles_micro.xml b/core/res/res/values-round/styles_micro.xml
new file mode 100644
index 0000000..7a1e511
--- /dev/null
+++ b/core/res/res/values-round/styles_micro.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <style name="TextAppearance.Micro.AlertDialog.Message" parent="TextAppearance.Micro.Small">
+ <item name="textAlignment">center</item>
+ </style>
+</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d12e34c..69b91ef 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1835,6 +1835,10 @@
<enum name="KEYCODE_CUT" value="277" />
<enum name="KEYCODE_COPY" value="278" />
<enum name="KEYCODE_PASTE" value="279" />
+ <enum name="KEYCODE_FP_NAV_UP" value="280" />
+ <enum name="KEYCODE_FP_NAV_DOWN" value="281" />
+ <enum name="KEYCODE_FP_NAV_LEFT" value="282" />
+ <enum name="KEYCODE_FP_NAV_RIGHT" value="283" />
</attr>
<!-- ***************************************************************** -->
@@ -2046,6 +2050,13 @@
<attr name="showTitle" format="boolean" />
<!-- @hide Whether fullDark, etc. should use default values if null. -->
<attr name="needsDefaultBackgrounds" format="boolean" />
+ <!-- @hide Workaround until we replace AlertController with custom layout. -->
+ <attr name="controllerType">
+ <!-- The default controller. -->
+ <enum name="normal" value="0" />
+ <!-- Controller for micro specific layout. -->
+ <enum name="micro" value="1" />
+ </attr>
</declare-styleable>
<!-- @hide -->
diff --git a/core/res/res/values/colors_micro.xml b/core/res/res/values/colors_micro.xml
new file mode 100644
index 0000000..b268e9a
--- /dev/null
+++ b/core/res/res/values/colors_micro.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<resources>
+ <color name="foreground_micro_dark">@color/white</color>
+ <color name="foreground_micro_light">@color/black</color>
+
+ <color name="background_micro_dark">@color/micro_blue_grey_b15</color>
+ <color name="background_micro_light">@color/white</color>
+ <color name="background_floating_micro_dark">@color/micro_blue_grey_b30</color>
+ <color name="background_floating_micro_light">@color/micro_blue_grey_500</color>
+
+ <color name="primary_micro_dark">@color/micro_blue_grey_b65</color>
+ <color name="primary_dark_micro_dark">@color/micro_blue_grey_b40</color>
+
+ <color name="accent_micro_light">@color/micro_blue_grey_500</color>
+ <color name="accent_micro_dark">@color/micro_blue_grey_b100</color>
+
+ <color name="button_micro_dark">@color/micro_button_gray</color>
+
+ <!-- Primary & accent colors -->
+ <eat-comment />
+
+ <!-- App color -->
+ <color name="micro_blue_grey_500">#ff607d8b</color>
+ <!-- Accent -->
+ <color name="micro_blue_grey_b100">#ffb0e5ff</color>
+ <!-- Lighter UI element -->
+ <color name="micro_blue_grey_b65">#ff7295a6</color>
+ <!-- UI Element -->
+ <color name="micro_blue_grey_b40">#ff465b66</color>
+ <!-- Lighter background -->
+ <color name="micro_blue_grey_b30">#ff35454d</color>
+ <!-- Dark background -->
+ <color name="micro_blue_grey_b15">#ff1a2226</color>
+
+ <!-- Button colors -->
+ <eat-comment />
+
+ <color name="micro_confirm_green">#ff4fc0b0</color>
+ <color name="micro_button_gray">#ffc2c2c2</color>
+ <color name="micro_action_blue">#ff0288d1</color>
+</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b93fd73..34bc67f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2487,4 +2487,7 @@
<string-array translatable="false" name="config_defaultPinnerServiceFiles">
</string-array>
+ <!-- Component that is the default launcher when demo mode is enabled. -->
+ <string name="config_demoModeLauncherComponent"></string>
+
</resources>
diff --git a/core/res/res/values/dimens_micro.xml b/core/res/res/values/dimens_micro.xml
new file mode 100644
index 0000000..dc8b124
--- /dev/null
+++ b/core/res/res/values/dimens_micro.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <dimen name="text_size_headline_micro">22sp</dimen>
+ <dimen name="text_size_title_micro">20sp</dimen>
+ <dimen name="text_size_subtitle_micro">18sp</dimen>
+ <dimen name="text_size_body1_micro">16sp</dimen>
+ <dimen name="text_size_body2_micro">14sp</dimen>
+ <dimen name="text_size_caption_micro">12sp</dimen>
+
+ <dimen name="text_size_large_micro">20sp</dimen>
+ <dimen name="text_size_medium_micro">18sp</dimen>
+ <dimen name="text_size_small_micro">16sp</dimen>
+
+ <item name="line_spacing_multiplier_micro" format="float" type="dimen">1.2</item>
+</resources>
diff --git a/core/res/res/values/styles_micro.xml b/core/res/res/values/styles_micro.xml
index 341a0a4..dba1705 100644
--- a/core/res/res/values/styles_micro.xml
+++ b/core/res/res/values/styles_micro.xml
@@ -40,7 +40,7 @@
<item name="wallpaperIntraCloseExitAnimation">@anim/slide_in_exit_micro</item>
</style>
- <style name="AlertDialog.Micro" parent="AlertDialog.Material.Light">
+ <style name="AlertDialog.Micro" parent="AlertDialog.Material">
<item name="fullDark">@null</item>
<item name="topDark">@null</item>
<item name="centerDark">@null</item>
@@ -52,30 +52,79 @@
<item name="bottomMedium">@null</item>
<item name="centerMedium">@null</item>
<item name="layout">@layout/alert_dialog_micro</item>
+ <item name="controllerType">micro</item>
</style>
<style name="DialogWindowTitle.Micro">
- <item name="maxLines">1</item>
<item name="scrollHorizontally">true</item>
<item name="textAppearance">@style/TextAppearance.Micro.DialogWindowTitle</item>
+ <item name="textAlignment">viewStart</item>
</style>
<style name="TextAppearance.Micro" parent="TextAppearance.Material">
- <item name="textSize">20sp</item>
<item name="fontFamily">sans-serif-condensed-light</item>
- <item name="textColor">@color/micro_text_light</item>
+ <item name="lineSpacingMultiplier">@dimen/line_spacing_multiplier_micro</item>
+ <item name="textColor">?attr/textColorPrimary</item>
+ <item name="textSize">@dimen/text_size_medium_micro</item>
+ </style>
+
+ <style name="TextAppearance.Micro.Inverse" parent="TextAppearance.Material">
+ <item name="textColor">?attr/textColorPrimaryInverse</item>
+ <item name="textColorHint">?attr/textColorHintInverse</item>
+ <item name="textColorHighlight">?attr/textColorHighlightInverse</item>
+ <item name="textColorLink">?attr/textColorLinkInverse</item>
+ </style>
+
+ <style name="TextAppearance.Micro.Headline">
+ <item name="textSize">@dimen/text_size_headline_micro</item>
+ </style>
+
+ <style name="TextAppearance.Micro.Title">
+ <item name="textSize">@dimen/text_size_title_micro</item>
+ </style>
+
+ <style name="TextAppearance.Micro.Subtitle">
+ <item name="textSize">@dimen/text_size_subtitle_micro</item>
+ </style>
+
+ <style name="TextAppearance.Micro.Body1">
+ <item name="textSize">@dimen/text_size_body1_micro</item>
+ </style>
+
+ <style name="TextAppearance.Micro.Body2">
+ <item name="textSize">@dimen/text_size_body2_micro</item>
+ </style>
+
+ <style name="TextAppearance.Micro.Caption">
+ <item name="textSize">@dimen/text_size_caption_micro</item>
+ </style>
+
+ <style name="TextAppearance.Micro.Large">
+ <item name="textSize">@dimen/text_size_large_micro</item>
+ </style>
+
+ <style name="TextAppearance.Micro.Medium">
+ <item name="textSize">@dimen/text_size_medium_micro</item>
+ </style>
+
+ <style name="TextAppearance.Micro.Small">
+ <item name="textSize">@dimen/text_size_small_micro</item>
</style>
<style name="TextAppearance.Micro.DialogWindowTitle" parent="TextAppearance.Material.DialogWindowTitle">
- <item name="textSize">20sp</item>
<item name="fontFamily">sans-serif-condensed-light</item>
- <item name="textColor">@color/micro_text_light</item>
+ <item name="lineSpacingMultiplier">@dimen/line_spacing_multiplier_micro</item>
+ <item name="textColor">?attr/textColorPrimary</item>
+ <item name="textSize">@dimen/text_size_large_micro</item>
</style>
+ <style name="TextAppearance.Micro.AlertDialog.Message" parent="TextAppearance.Micro.Small" />
+
<style name="Widget.Micro" parent="Widget.Material" />
- <style name="Widget.Micro.TextView">
- <item name="fontFamily">sans-serif-condensed</item>
+ <style name="Widget.Micro.TextView" parent="Widget.Material.TextView" >
+ <item name="breakStrategy">balanced</item>
+ <item name="hyphenationFrequency">normal</item>
</style>
<style name="Widget.Micro.NumberPicker">
@@ -90,4 +139,14 @@
<item name="descendantFocusability">blocksDescendants</item>
</style>
+ <style name="Widget.Micro.ButtonBar">
+ <item name="background">@color/background_floating_micro_dark</item>
+ </style>
+
+ <style name="Widget.Micro.Button.ButtonBar.AlertDialog" parent="Widget.Material.Button.ButtonBar.AlertDialog">
+ <item name="textAppearance">@style/TextAppearance.Micro.Small</item>
+ <item name="maxLines">3</item>
+ <item name="textColor">?attr/textColorPrimary</item>
+ <item name="textAlignment">viewStart</item>
+ </style>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f2c3c42..ba58f15 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1104,6 +1104,7 @@
<java-symbol type="string" name="lockscreen_transport_pause_description" />
<java-symbol type="string" name="config_ethernet_tcp_buffers" />
<java-symbol type="string" name="config_wifi_tcp_buffers" />
+ <java-symbol type="string" name="config_demoModeLauncherComponent" />
<java-symbol type="plurals" name="bugreport_countdown" />
<java-symbol type="plurals" name="duration_hours" />
diff --git a/core/res/res/values/themes_micro.xml b/core/res/res/values/themes_micro.xml
index 25a6e00..92106b2 100644
--- a/core/res/res/values/themes_micro.xml
+++ b/core/res/res/values/themes_micro.xml
@@ -21,11 +21,36 @@
<item name="textViewStyle">@style/Widget.Micro.TextView</item>
<item name="numberPickerStyle">@style/Widget.Micro.NumberPicker</item>
<item name="windowAnimationStyle">@style/Animation.Micro.Activity</item>
- <item name="windowBackground">@color/black</item>
+ <item name="windowBackground">@color/background_micro_dark</item>
<item name="windowContentOverlay">@null</item>
<item name="windowIsFloating">false</item>
<!-- Required to force windowInsets dispatch through application UI. -->
<item name="windowOverscan">true</item>
+
+ <item name="textAppearance">@style/TextAppearance.Micro</item>
+ <item name="textAppearanceInverse">@style/TextAppearance.Micro.Inverse</item>
+
+ <item name="textAppearanceLarge">@style/TextAppearance.Micro.Large</item>
+ <item name="textAppearanceMedium">@style/TextAppearance.Micro.Medium</item>
+ <item name="textAppearanceSmall">@style/TextAppearance.Micro.Small</item>
+
+ <item name="textAppearanceListItem">@style/TextAppearance.Micro.Subtitle</item>
+ <item name="textAppearanceListItemSmall">@style/TextAppearance.Micro.Body1</item>
+ <item name="textAppearanceListItemSecondary">@style/TextAppearance.Micro.Body2</item>
+ <item name="listPreferredItemPaddingLeft">@dimen/list_item_padding_horizontal_micro</item>
+ <item name="listPreferredItemPaddingRight">@dimen/list_item_padding_horizontal_micro</item>
+ <item name="listPreferredItemPaddingStart">@dimen/list_item_padding_horizontal_micro</item>
+ <item name="listPreferredItemPaddingEnd">@dimen/list_item_padding_horizontal_micro</item>
+
+ <!-- Dialog styling -->
+ <item name="buttonBarStyle">@style/Widget.Micro.ButtonBar</item>
+ <item name="buttonBarButtonStyle">@style/Widget.Micro.Button.ButtonBar.AlertDialog</item>
+
+ <!-- Color palette -->
+ <item name="colorPrimaryDark">@color/primary_dark_micro_dark</item>
+ <item name="colorPrimary">@color/primary_micro_dark</item>
+ <item name="colorAccent">@color/accent_micro_dark</item>
+ <item name="colorEdgeEffect">?attr/colorPrimary</item>
</style>
<style name="Theme.Micro" parent="Theme.MicroBase">
@@ -49,12 +74,29 @@
<style name="Theme.Micro.Light" parent="Theme.Micro.LightBase">
</style>
- <style name="Theme.Micro.DialogBase" parent="Theme.Material.Light.Dialog">
- <item name="windowTitleStyle">@android:style/DialogWindowTitle.Micro</item>
+ <style name="Theme.Micro.DialogBase">
+ <item name="colorBackground">?attr/colorBackgroundFloating</item>
+
+ <item name="windowFrame">@null</item>
+ <item name="windowTitleBackgroundStyle">@style/DialogWindowTitleBackground.Material</item>
+ <item name="windowElevation">@dimen/floating_window_z</item>
+ <item name="windowContentOverlay">@null</item>
+ <item name="windowAnimationStyle">@style/Animation.Material.Dialog</item>
+ <item name="windowSoftInputMode">stateUnspecified|adjustPan</item>
+ <item name="windowActionBar">false</item>
+ <item name="windowActionModeOverlay">true</item>
+ <item name="windowCloseOnTouchOutside">@bool/config_closeDialogWhenTouchOutside</item>
+
+ <item name="colorBackgroundCacheHint">@null</item>
+
+ <item name="listDivider">@null</item>
+
+ <item name="preferencePanelStyle">@style/PreferencePanel.Dialog</item>
+
+ <item name="windowTitleStyle">@style/DialogWindowTitle.Micro</item>
<item name="windowIsFloating">false</item>
<item name="windowFullscreen">true</item>
- <item name="textAppearance">@style/TextAppearance.Micro</item>
- <item name="textAppearanceInverse">@style/TextAppearance.Micro</item>
+ <item name="windowBackground">@color/background_micro_dark</item>
<!-- Required to force windowInsets dispatch through application UI. -->
<item name="windowOverscan">true</item>
</style>
@@ -67,21 +109,17 @@
<item name="windowTitleStyle">@style/DialogWindowTitle.Micro</item>
<item name="alertDialogStyle">@style/AlertDialog.Micro</item>
<item name="windowIsFloating">false</item>
- <item name="windowBackground">@android:color/transparent</item>
<item name="windowOverscan">true</item>
<item name="windowContentOverlay">@null</item>
- <item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
- <item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
+ <item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item>
+ <item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item>
</style>
<style name="Theme.Micro.Dialog.AppError" parent="Theme.Micro.Dialog">
- <item name="windowBackground">@null</item>
<item name="alertDialogStyle">@style/AlertDialog.Micro</item>
+ <item name="textAppearance">@style/TextAppearance.Micro.Large</item>
<item name="windowOverscan">true</item>
<item name="windowCloseOnTouchOutside">false</item>
- <item name="textSize">20sp</item>
- <item name="fontFamily">sans-serif-condensed-light</item>
- <item name="textColor">@color/micro_text_light</item>
</style>
<style name="Theme.Micro.Panel" parent="Theme.Material.Panel" />
diff --git a/core/res/res/xml-watch/default_zen_mode_config.xml b/core/res/res/xml-watch/default_zen_mode_config.xml
new file mode 100644
index 0000000..26af10c
--- /dev/null
+++ b/core/res/res/xml-watch/default_zen_mode_config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<!-- Default configuration for zen mode. See android.service.notification.ZenModeConfig. -->
+<zen version="2">
+ <!-- Allow starred contacts to go through only. Repeated calls on.
+ Calls, messages, reminders, events off.-->
+ <allow from="2" repeatCallers="true" calls="false" messages="false" reminders="false"
+ events="false"/>
+</zen>
diff --git a/packages/SettingsLib/res/layout/settings_with_drawer.xml b/packages/SettingsLib/res/layout/settings_with_drawer.xml
index 67296a6..a68a44e 100644
--- a/packages/SettingsLib/res/layout/settings_with_drawer.xml
+++ b/packages/SettingsLib/res/layout/settings_with_drawer.xml
@@ -41,6 +41,11 @@
android:background="?android:attr/colorPrimary" />
</FrameLayout>
<FrameLayout
+ android:id="@+id/content_header_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="?android:attr/actionBarStyle" />
+ <FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="fill_parent"
diff --git a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
index 320cd58..76ba0a7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java
@@ -156,7 +156,7 @@
return intent;
}
- private static void addIntentParameters(Context context, Intent intent, String backupContext) {
+ public static void addIntentParameters(Context context, Intent intent, String backupContext) {
if (!intent.hasExtra(EXTRA_CONTEXT)) {
// Insert some context if none exists.
intent.putExtra(EXTRA_CONTEXT, backupContext);
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index bf75046..8df7ac5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -38,8 +38,10 @@
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.widget.AdapterView;
+import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.Toolbar;
+
import com.android.settingslib.R;
import com.android.settingslib.applications.InterestingConfigChanges;
@@ -62,6 +64,7 @@
private final List<CategoryListener> mCategoryListeners = new ArrayList<>();
private SettingsDrawerAdapter mDrawerAdapter;
+ private FrameLayout mContentHeaderContainer;
private DrawerLayout mDrawerLayout;
private boolean mShowingMenu;
@@ -78,6 +81,7 @@
requestWindowFeature(Window.FEATURE_NO_TITLE);
}
super.setContentView(R.layout.settings_with_drawer);
+ mContentHeaderContainer = (FrameLayout) findViewById(R.id.content_header_container);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
if (mDrawerLayout == null) {
return;
@@ -174,6 +178,13 @@
}
}
+ public void setContentHeaderView(View headerView) {
+ mContentHeaderContainer.removeAllViews();
+ if (headerView != null) {
+ mContentHeaderContainer.addView(headerView);
+ }
+ }
+
@Override
public void setContentView(@LayoutRes int layoutResID) {
final ViewGroup parent = (ViewGroup) findViewById(R.id.content_frame);
@@ -266,6 +277,13 @@
}
}
+ public HashMap<Pair<String, String>, Tile> getTileCache() {
+ if (sTileCache == null) {
+ getDashboardCategories();
+ }
+ return sTileCache;
+ }
+
public void onProfileTileOpen() {
finish();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index 418b138..b9c758c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -302,7 +302,7 @@
return false;
}
- private static final Comparator<Tile> TILE_COMPARATOR =
+ public static final Comparator<Tile> TILE_COMPARATOR =
new Comparator<Tile>() {
@Override
public int compare(Tile lhs, Tile rhs) {
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 978ca94..108814e 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -216,4 +216,7 @@
<!-- Default setting for ability to add users from the lock screen -->
<bool name="def_add_users_from_lockscreen">false</bool>
+
+ <!-- Default setting for disallow oem unlock. -->
+ <bool name="def_oem_unlock_disallow">false</bool>
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 774be60..950c7d3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2074,7 +2074,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 127;
+ private static final int SETTINGS_VERSION = 128;
private final int mUserId;
@@ -2329,6 +2329,18 @@
currentVersion = 127;
}
+ if (currentVersion == 127) {
+ // Version 127: Disable OEM unlock setting by default on some devices.
+ final SettingsState globalSettings = getGlobalSettingsLocked();
+ String defaultOemUnlockDisabled = (getContext().getResources()
+ .getBoolean(R.bool.def_oem_unlock_disallow) ? "1" : "0");
+ globalSettings.insertSettingLocked(
+ Settings.Global.OEM_UNLOCK_DISALLOWED,
+ defaultOemUnlockDisabled,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ currentVersion = 128;
+ }
+
// vXXX: Add new settings above this point.
// Return the current version.
diff --git a/packages/SystemUI/res/drawable/ic_qs_branded_vpn.xml b/packages/SystemUI/res/drawable/ic_qs_branded_vpn.xml
new file mode 100644
index 0000000..736a04a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_branded_vpn.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="12.0dp"
+ android:height="12.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#4DFFFFFF"
+ android:pathData="M12.700000,10.000000c-0.800000,-2.300000 -3.000000,-4.000000 -5.700000,-4.000000c-3.300000,0.000000 -6.000000,2.700000 -6.000000,6.000000s2.700000,6.000000 6.000000,6.000000c2.600000,0.000000 4.800000,-1.700000 5.700000,-4.000000L17.000000,14.000000l0.000000,4.000000l4.000000,0.000000l0.000000,-4.000000l2.000000,0.000000l0.000000,-4.000000L12.700000,10.000000zM7.000000,14.000000c-1.100000,0.000000 -2.000000,-0.900000 -2.000000,-2.000000c0.000000,-1.100000 0.900000,-2.000000 2.000000,-2.000000s2.000000,0.900000 2.000000,2.000000C9.000000,13.100000 8.100000,14.000000 7.000000,14.000000z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_branded_vpn.xml b/packages/SystemUI/res/drawable/stat_sys_branded_vpn.xml
new file mode 100644
index 0000000..a86e5b9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_branded_vpn.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17.0dp"
+ android:height="17.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12.700000,10.000000c-0.800000,-2.300000 -3.000000,-4.000000 -5.700000,-4.000000c-3.300000,0.000000 -6.000000,2.700000 -6.000000,6.000000s2.700000,6.000000 6.000000,6.000000c2.600000,0.000000 4.800000,-1.700000 5.700000,-4.000000L17.000000,14.000000l0.000000,4.000000l4.000000,0.000000l0.000000,-4.000000l2.000000,0.000000l0.000000,-4.000000L12.700000,10.000000zM7.000000,14.000000c-1.100000,0.000000 -2.000000,-0.900000 -2.000000,-2.000000c0.000000,-1.100000 0.900000,-2.000000 2.000000,-2.000000s2.000000,0.900000 2.000000,2.000000C9.000000,13.100000 8.100000,14.000000 7.000000,14.000000z"/>
+</vector>
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index cad7f64..c8e74ec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -399,8 +399,7 @@
sendUserPresentBroadcast();
synchronized (KeyguardViewMediator.this) {
// If system user is provisioned, we might want to lock now to avoid showing launcher
- if (UserManager.isSplitSystemUser()
- && KeyguardUpdateMonitor.getCurrentUser() == UserHandle.USER_SYSTEM) {
+ if (mustNotUnlockCurrentUser()) {
doKeyguardLocked(null);
}
}
@@ -599,7 +598,6 @@
return KeyguardSecurityView.PROMPT_REASON_WRONG_CREDENTIAL;
}
-
return KeyguardSecurityView.PROMPT_REASON_NONE;
}
};
@@ -608,6 +606,11 @@
mPM.userActivity(SystemClock.uptimeMillis(), false);
}
+ boolean mustNotUnlockCurrentUser() {
+ return (UserManager.isSplitSystemUser() || UserManager.isDeviceInDemoMode(mContext))
+ && KeyguardUpdateMonitor.getCurrentUser() == UserHandle.USER_SYSTEM;
+ }
+
private void setupLocked() {
mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWM = WindowManagerGlobal.getWindowManagerService();
@@ -1176,8 +1179,7 @@
}
// In split system user mode, we never unlock system user.
- if (!UserManager.isSplitSystemUser()
- || KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM
+ if (!mustNotUnlockCurrentUser()
|| !mUpdateMonitor.isDeviceProvisioned()) {
// if the setup wizard hasn't run yet, don't show
@@ -1615,8 +1617,7 @@
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleHide");
- if (UserManager.isSplitSystemUser()
- && KeyguardUpdateMonitor.getCurrentUser() == UserHandle.USER_SYSTEM) {
+ if (mustNotUnlockCurrentUser()) {
// In split system user mode, we never unlock system user. The end user has to
// switch to another user.
// TODO: We should stop it early by disabling the swipe up flow. Right now swipe up
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 51efbf0..0549afa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -57,6 +58,7 @@
private boolean mIsVisible;
private boolean mIsIconVisible;
private int mFooterTextId;
+ private int mFooterIconId;
public QSFooter(QSPanel qsPanel, Context context) {
mRootView = LayoutInflater.from(context)
@@ -64,6 +66,7 @@
mRootView.setOnClickListener(this);
mFooterText = (TextView) mRootView.findViewById(R.id.footer_text);
mFooterIcon = (ImageView) mRootView.findViewById(R.id.footer_icon);
+ mFooterIconId = R.drawable.ic_qs_vpn;
mContext = context;
mMainHandler = new Handler();
}
@@ -118,6 +121,14 @@
mIsVisible = true;
} else {
mFooterTextId = R.string.vpn_footer;
+ // Update the VPN footer icon, if needed.
+ int footerIconId = (mSecurityController.isVpnBranded()
+ ? R.drawable.ic_qs_branded_vpn
+ : R.drawable.ic_qs_vpn);
+ if (mFooterIconId != footerIconId) {
+ mFooterIcon.setImageResource(footerIconId);
+ mFooterIconId = footerIconId;
+ }
mIsVisible = mIsIconVisible;
}
mMainHandler.post(mUpdateDisplayState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 2b59c68..a2ad46a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -67,6 +67,8 @@
private boolean mNoSimsVisible = false;
private boolean mVpnVisible = false;
+ private int mVpnIconId = 0;
+ private int mLastVpnIconId = -1;
private boolean mEthernetVisible = false;
private int mEthernetIconId = 0;
private int mLastEthernetIconId = -1;
@@ -164,6 +166,7 @@
mSC = sc;
mSC.addCallback(this);
mVpnVisible = mSC.isVpnEnabled();
+ mVpnIconId = currentVpnIconId(mSC.isVpnBranded());
}
@Override
@@ -241,6 +244,7 @@
@Override
public void run() {
mVpnVisible = mSC.isVpnEnabled();
+ mVpnIconId = currentVpnIconId(mSC.isVpnBranded());
apply();
}
});
@@ -428,6 +432,15 @@
if (mWifiGroup == null) return;
mVpn.setVisibility(mVpnVisible ? View.VISIBLE : View.GONE);
+ if (mVpnVisible) {
+ if (mLastVpnIconId != mVpnIconId) {
+ setIconForView(mVpn, mVpnIconId);
+ mLastVpnIconId = mVpnIconId;
+ }
+ mVpn.setVisibility(View.VISIBLE);
+ } else {
+ mVpn.setVisibility(View.GONE);
+ }
if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE"));
if (mEthernetVisible) {
@@ -556,6 +569,10 @@
v.setImageTintList(ColorStateList.valueOf(tint));
}
+ private int currentVpnIconId(boolean isBranded) {
+ return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic;
+ }
+
private class PhoneState {
private final int mSubId;
private boolean mMobileVisible = false;
@@ -677,4 +694,3 @@
}
}
}
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index a22f988..014afae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -23,6 +23,8 @@
String getProfileOwnerName();
boolean isVpnEnabled();
boolean isVpnRestricted();
+ /** Whether the VPN app should use branded VPN iconography. */
+ boolean isVpnBranded();
String getPrimaryVpnName();
String getProfileVpnName();
void onUserSwitched(int newUserId);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 5046456..07d3b59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -18,6 +18,8 @@
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.net.ConnectivityManager;
@@ -54,10 +56,13 @@
.build();
private static final int NO_NETWORK = -1;
+ private static final String VPN_BRANDED_META_DATA = "com.android.systemui.IS_BRANDED";
+
private final Context mContext;
private final ConnectivityManager mConnectivityManager;
private final IConnectivityManager mConnectivityManagerService;
private final DevicePolicyManager mDevicePolicyManager;
+ private final PackageManager mPackageManager;
private final UserManager mUserManager;
@GuardedBy("mCallbacks")
@@ -75,6 +80,7 @@
context.getSystemService(Context.CONNECTIVITY_SERVICE);
mConnectivityManagerService = IConnectivityManager.Stub.asInterface(
ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+ mPackageManager = context.getPackageManager();
mUserManager = (UserManager)
context.getSystemService(Context.USER_SERVICE);
@@ -165,6 +171,21 @@
}
@Override
+ public boolean isVpnBranded() {
+ VpnConfig cfg = mCurrentVpns.get(mVpnUserId);
+ if (cfg == null) {
+ return false;
+ }
+
+ String packageName = getPackageNameForVpnConfig(cfg);
+ if (packageName == null) {
+ return false;
+ }
+
+ return isVpnPackageBranded(packageName);
+ }
+
+ @Override
public void removeCallback(SecurityControllerCallback callback) {
synchronized (mCallbacks) {
if (callback == null) return;
@@ -245,6 +266,28 @@
mCurrentVpns = vpns;
}
+ private String getPackageNameForVpnConfig(VpnConfig cfg) {
+ if (cfg.legacy) {
+ return null;
+ }
+ return cfg.user;
+ }
+
+ private boolean isVpnPackageBranded(String packageName) {
+ boolean isBranded;
+ try {
+ ApplicationInfo info = mPackageManager.getApplicationInfo(packageName,
+ PackageManager.GET_META_DATA);
+ if (info == null || info.metaData == null || !info.isSystemApp()) {
+ return false;
+ }
+ isBranded = info.metaData.getBoolean(VPN_BRANDED_META_DATA, false);
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ return isBranded;
+ }
+
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
@Override
public void onAvailable(Network network) {
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 46da957..1c12565 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -2206,6 +2206,39 @@
// Settings launched from expanded quick settings.
ACTION_QS_EXPANDED_SETTINGS_LAUNCH = 406;
+ // ---- End N Constants, all N constants go above this line ----
+
+ // ------- Begin N App Disambig Shade -----
+ // Application disambig shade opened or closed with a featured app.
+ // These are actually visibility events, but visible/hidden doesn't
+ // take a package, so these are being logged as actions.
+ // Package: Calling app on open, called app on close
+ ACTION_SHOW_APP_DISAMBIG_APP_FEATURED = 451;
+ ACTION_HIDE_APP_DISAMBIG_APP_FEATURED = 452;
+
+ // Application disambig shade opened or closed without a featured app.
+ // These are actually visibility events, but visible/hidden doesn't
+ // take a package, so these are being logged as actions.
+ // Package: Calling app on open, called app on close
+ ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED = 453;
+ ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED = 454;
+
+ // User opens in an app by pressing “Always” in the application disambig shade.
+ // Subtype: Index of selection
+ ACTION_APP_DISAMBIG_ALWAYS = 455;
+
+ // User opens in an app by pressing “Just Once” in the application disambig shade.
+ // Subtype: Index of selection
+ ACTION_APP_DISAMBIG_JUST_ONCE = 456;
+
+ // User opens in an app by tapping on its name in the application disambig shade.
+ // Subtype: Index of selection
+ ACTION_APP_DISAMBIG_TAP = 457;
+
+ // OPEN: Settings > Internal storage > Storage manager
+ // CATEGORY: SETTINGS
+ STORAGE_MANAGER_SETTINGS = 458;
+
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
}
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 2085f32..51037dd 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -26,6 +26,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.service.persistentdata.IPersistentDataBlockService;
import android.service.persistentdata.PersistentDataBlockManager;
import android.util.Slog;
@@ -145,6 +146,15 @@
"Only the Admin user is allowed to change OEM unlock state");
}
}
+
+ private void enforceFactoryResetAllowed() {
+ final boolean isOemUnlockRestricted = UserManager.get(mContext)
+ .hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET);
+ if (isOemUnlockRestricted) {
+ throw new SecurityException("OEM unlock is disallowed by DISALLOW_FACTORY_RESET");
+ }
+ }
+
private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
// skip over checksum
inputStream.skipBytes(DIGEST_SIZE_BYTES);
@@ -437,14 +447,23 @@
}
@Override
- public void setOemUnlockEnabled(boolean enabled) {
+ public void setOemUnlockEnabled(boolean enabled) throws SecurityException {
// do not allow monkey to flip the flag
if (ActivityManager.isUserAMonkey()) {
return;
}
+
enforceOemUnlockPermission();
enforceIsAdmin();
+ // Do not allow oem unlock modification if it has been disallowed.
+ if (Settings.Global.getInt(getContext().getContentResolver(),
+ Settings.Global.OEM_UNLOCK_DISALLOWED, 0) == 1) {
+ throw new SecurityException("OEM unlock has been disallowed.");
+ }
+ if (enabled) {
+ enforceFactoryResetAllowed();
+ }
synchronized (mLock) {
doSetOemUnlockEnabledLocked(enabled);
computeAndWriteDigestLocked();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b80487f..f4bb524 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18467,8 +18467,9 @@
/**
* Decide based on the configuration whether we should shouw the ANR,
- * crash, etc dialogs. The idea is that if there is no affordnace to
- * press the on-screen buttons, we shouldn't show the dialog.
+ * crash, etc dialogs. The idea is that if there is no affordence to
+ * press the on-screen buttons, or the user experience would be more
+ * greatly impacted than the crash itself, we shouldn't show the dialog.
*
* A thought: SystemUI might also want to get told about this, the Power
* dialog / global actions also might want different behaviors.
@@ -18477,9 +18478,10 @@
final boolean inputMethodExists = !(config.keyboard == Configuration.KEYBOARD_NOKEYS
&& config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH
&& config.navigation == Configuration.NAVIGATION_NONAV);
- final boolean uiIsNotCarType = !((config.uiMode & Configuration.UI_MODE_TYPE_MASK)
- == Configuration.UI_MODE_TYPE_CAR);
- return inputMethodExists && uiIsNotCarType && !inVrMode;
+ int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
+ final boolean uiModeSupportsDialogs = (modeType != Configuration.UI_MODE_TYPE_CAR
+ && (modeType != Configuration.UI_MODE_TYPE_WATCH || "eng".equals(Build.TYPE)));
+ return inputMethodExists && uiModeSupportsDialogs && !inVrMode;
}
@Override
@@ -20937,6 +20939,11 @@
Slog.w(TAG, "No user info for user #" + targetUserId);
return false;
}
+ if (!targetUserInfo.isDemo() && UserManager.isDeviceInDemoMode(mContext)) {
+ Slog.w(TAG, "Cannot switch to non-demo user #" + targetUserId
+ + " when device is in demo mode");
+ return false;
+ }
if (!targetUserInfo.supportsSwitchTo()) {
Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
return false;
diff --git a/services/core/java/com/android/server/am/RetailDemoModeService.java b/services/core/java/com/android/server/am/RetailDemoModeService.java
new file mode 100644
index 0000000..bf2ba93
--- /dev/null
+++ b/services/core/java/com/android/server/am/RetailDemoModeService.java
@@ -0,0 +1,179 @@
+/*
+ * 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.am;
+
+import android.app.ActivityManagerNative;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.server.ServiceThread;
+import com.android.server.SystemService;
+import com.android.server.pm.UserManagerService;
+
+public class RetailDemoModeService extends SystemService {
+ private static final boolean DEBUG = false;
+
+ private static final String TAG = RetailDemoModeService.class.getSimpleName();
+ private static final String DEMO_USER_NAME = "Demo";
+
+ private static final long SCREEN_WAKEUP_DELAY = 5000;
+
+ private ActivityManagerService mAms;
+ private UserManagerService mUms;
+ private PowerManager mPm;
+ private PowerManager.WakeLock mWakeLock;
+ private Handler mHandler;
+ private ServiceThread mHandlerThread;
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!UserManager.isDeviceInDemoMode(getContext())) {
+ return;
+ }
+ switch (intent.getAction()) {
+ case Intent.ACTION_SCREEN_OFF:
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ mWakeLock.acquire();
+ }
+ }, SCREEN_WAKEUP_DELAY);
+ break;
+ }
+ }
+ };
+
+ public RetailDemoModeService(Context context) {
+ super(context);
+ }
+
+ private void createAndSwitchToDemoUser() {
+ if (DEBUG) {
+ Slog.d(TAG, "Switching to a new demo user");
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ UserInfo demoUser = getUserManager().createUser(DEMO_USER_NAME,
+ UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
+ if (demoUser != null) {
+ getActivityManager().switchUser(demoUser.id);
+ }
+ }
+ });
+ }
+
+ private ActivityManagerService getActivityManager() {
+ if (mAms == null) {
+ mAms = (ActivityManagerService) ActivityManagerNative.getDefault();
+ }
+ return mAms;
+ }
+
+ private UserManagerService getUserManager() {
+ if (mUms == null) {
+ mUms = (UserManagerService) UserManagerService.Stub
+ .asInterface(ServiceManager.getService(Context.USER_SERVICE));
+ }
+ return mUms;
+ }
+
+ private void registerSettingsChangeObserver() {
+ final Uri deviceDemoModeUri = Settings.Global.getUriFor(Settings.Global.DEVICE_DEMO_MODE);
+ final ContentResolver cr = getContext().getContentResolver();
+ final ContentObserver deviceDemoModeSettingObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ if (deviceDemoModeUri.equals(uri)) {
+ if (UserManager.isDeviceInDemoMode(getContext())) {
+ createAndSwitchToDemoUser();
+ }
+ }
+ }
+ };
+ cr.registerContentObserver(deviceDemoModeUri, false, deviceDemoModeSettingObserver,
+ UserHandle.USER_SYSTEM);
+ }
+
+ private void registerBroadcastReceiver() {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
+ }
+
+ @Override
+ public void onStart() {
+ if (DEBUG) {
+ Slog.d(TAG, "Service starting up");
+ }
+ mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND,
+ false);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper(), null, true);
+ }
+
+ @Override
+ public void onBootPhase(int bootPhase) {
+ if (bootPhase != PHASE_THIRD_PARTY_APPS_CAN_START) {
+ return;
+ }
+ mPm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
+ mWakeLock = mPm
+ .newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
+ if (UserManager.isDeviceInDemoMode(getContext())) {
+ createAndSwitchToDemoUser();
+ }
+ registerSettingsChangeObserver();
+ registerBroadcastReceiver();
+ }
+
+ @Override
+ public void onSwitchUser(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "onSwitchUser: " + userId);
+ }
+ UserInfo ui = getUserManager().getUserInfo(userId);
+ if (!ui.isDemo()) {
+ if (UserManager.isDeviceInDemoMode(getContext())) {
+ Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode");
+ } else if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ return;
+ }
+ if (!mWakeLock.isHeld()) {
+ mWakeLock.acquire();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 595d086..4fe1740 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -223,6 +223,7 @@
private void finishUserBoot(UserState uss, IIntentReceiver resultTo) {
final int userId = uss.mHandle.getIdentifier();
+
Slog.d(TAG, "Finishing user boot " + userId);
synchronized (mService) {
// Bail if we ended up with a stale user
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index ffe8f75..43a0b91 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -745,9 +745,6 @@
@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/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 75bd35c..5e22e58 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4029,7 +4029,7 @@
file.delete();
removeCrossProfileIntentFiltersLPw(userId);
- mRuntimePermissionsPersistence.onUserRemoved(userId);
+ mRuntimePermissionsPersistence.onUserRemovedLPw(userId);
writePackageListLPr();
}
@@ -5134,7 +5134,7 @@
}
}
- private void onUserRemoved(int userId) {
+ private void onUserRemovedLPw(int userId) {
// Make sure we do not
mHandler.removeMessages(userId);
@@ -5145,6 +5145,9 @@
for (SettingBase sb : mSharedUsers.values()) {
revokeRuntimePermissionsAndClearFlags(sb, userId);
}
+
+ mDefaultPermissionsGranted.delete(userId);
+ mFingerprints.remove(userId);
}
private void revokeRuntimePermissionsAndClearFlags(SettingBase sb, int userId) {
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 76d47a8..db2b9f4 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -80,26 +80,31 @@
* Called when the new package can't receive the backup, due to signature or version mismatch.
*/
@Override
- protected void onRestoreBlocked(ShortcutService s) {
+ protected void onRestoreBlocked() {
final ArrayList<PackageWithUser> pinnedPackages =
new ArrayList<>(mPinnedShortcuts.keySet());
mPinnedShortcuts.clear();
for (int i = pinnedPackages.size() - 1; i >= 0; i--) {
final PackageWithUser pu = pinnedPackages.get(i);
- s.getPackageShortcutsLocked(pu.packageName, pu.userId)
- .refreshPinnedFlags(s);
+ final ShortcutPackage p = mShortcutUser.getPackageShortcutsIfExists(pu.packageName);
+ if (p != null) {
+ p.refreshPinnedFlags();
+ }
}
}
@Override
- protected void onRestored(ShortcutService s) {
+ protected void onRestored() {
// Nothing to do.
}
- public void pinShortcuts(@NonNull ShortcutService s, @UserIdInt int packageUserId,
+ public void pinShortcuts(@UserIdInt int packageUserId,
@NonNull String packageName, @NonNull List<String> ids) {
final ShortcutPackage packageShortcuts =
- s.getPackageShortcutsLocked(packageName, packageUserId);
+ mShortcutUser.getPackageShortcutsIfExists(packageName);
+ if (packageShortcuts == null) {
+ return; // No need to instantiate.
+ }
final PackageWithUser pu = PackageWithUser.of(packageUserId, packageName);
@@ -126,7 +131,7 @@
}
mPinnedShortcuts.put(pu, newSet);
}
- packageShortcuts.refreshPinnedFlags(s);
+ packageShortcuts.refreshPinnedFlags();
}
/**
@@ -240,7 +245,7 @@
return ret;
}
- public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
pw.println();
pw.print(prefix);
@@ -252,7 +257,7 @@
pw.print(getOwnerUserId());
pw.println();
- getPackageInfo().dump(s, pw, prefix + " ");
+ getPackageInfo().dump(pw, prefix + " ");
pw.println();
final int size = mPinnedShortcuts.size();
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 151f61e..c298683 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -61,9 +61,13 @@
private static final String ATTR_ID = "id";
private static final String ATTR_ACTIVITY = "activity";
private static final String ATTR_TITLE = "title";
+ private static final String ATTR_TITLE_RES_ID = "titleid";
private static final String ATTR_TEXT = "text";
+ private static final String ATTR_TEXT_RES_ID = "textid";
+ private static final String ATTR_DISABLED_MESSAGE = "dmessage";
+ private static final String ATTR_DISABLED_MESSAGE_RES_ID = "dmessageid";
private static final String ATTR_INTENT = "intent";
- private static final String ATTR_WEIGHT = "weight";
+ private static final String ATTR_RANK = "rank";
private static final String ATTR_TIMESTAMP = "timestamp";
private static final String ATTR_FLAGS = "flags";
private static final String ATTR_ICON_RES = "icon-res";
@@ -98,17 +102,16 @@
private long mLastKnownForegroundElapsedTime;
- private ShortcutPackage(ShortcutService s, ShortcutUser shortcutUser,
+ private ShortcutPackage(ShortcutUser shortcutUser,
int packageUserId, String packageName, ShortcutPackageInfo spi) {
super(shortcutUser, packageUserId, packageName,
spi != null ? spi : ShortcutPackageInfo.newEmpty());
- mPackageUid = s.injectGetPackageUid(packageName, packageUserId);
+ mPackageUid = shortcutUser.mService.injectGetPackageUid(packageName, packageUserId);
}
- public ShortcutPackage(ShortcutService s, ShortcutUser shortcutUser,
- int packageUserId, String packageName) {
- this(s, shortcutUser, packageUserId, packageName, null);
+ public ShortcutPackage(ShortcutUser shortcutUser, int packageUserId, String packageName) {
+ this(shortcutUser, packageUserId, packageName, null);
}
@Override
@@ -126,10 +129,12 @@
* exists (as opposed to Launcher trying to fetch shortcuts from a non-existent package), so
* we do some initialization for the package.
*/
- private void onShortcutPublish(ShortcutService s) {
+ private void onShortcutPublish() {
// Make sure we have the version code for the app. We need the version code in
// handlePackageUpdated().
if (getPackageInfo().getVersionCode() < 0) {
+ final ShortcutService s = mShortcutUser.mService;
+
final int versionCode = s.getApplicationVersionCode(getPackageName(), getOwnerUserId());
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Package %s version = %d", getPackageName(),
@@ -143,16 +148,16 @@
}
@Override
- protected void onRestoreBlocked(ShortcutService s) {
+ protected void onRestoreBlocked() {
// Can't restore due to version/signature mismatch. Remove all shortcuts.
mShortcuts.clear();
}
@Override
- protected void onRestored(ShortcutService s) {
+ protected void onRestored() {
// Because some launchers may not have been restored (e.g. allowBackup=false),
// we need to re-calculate the pinned shortcuts.
- refreshPinnedFlags(s);
+ refreshPinnedFlags();
}
/**
@@ -163,19 +168,18 @@
return mShortcuts.get(id);
}
- private ShortcutInfo deleteShortcut(@NonNull ShortcutService s,
- @NonNull String id) {
+ private ShortcutInfo deleteShortcut(@NonNull String id) {
final ShortcutInfo shortcut = mShortcuts.remove(id);
if (shortcut != null) {
- s.removeIcon(getPackageUserId(), shortcut);
+ mShortcutUser.mService.removeIcon(getPackageUserId(), shortcut);
shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED);
}
return shortcut;
}
- void addShortcut(@NonNull ShortcutService s, @NonNull ShortcutInfo newShortcut) {
- deleteShortcut(s, newShortcut.getId());
- s.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut);
+ void addShortcut(@NonNull ShortcutInfo newShortcut) {
+ deleteShortcut(newShortcut.getId());
+ mShortcutUser.mService.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut);
mShortcuts.put(newShortcut.getId(), newShortcut);
}
@@ -184,10 +188,9 @@
*
* It checks the max number of dynamic shortcuts.
*/
- public void addDynamicShortcut(@NonNull ShortcutService s,
- @NonNull ShortcutInfo newShortcut) {
+ public void addDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
- onShortcutPublish(s);
+ onShortcutPublish();
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
@@ -209,21 +212,21 @@
}
// Make sure there's still room.
- s.enforceMaxDynamicShortcuts(newDynamicCount);
+ mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount);
// Okay, make it dynamic and add.
if (wasPinned) {
newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
}
- addShortcut(s, newShortcut);
+ addShortcut(newShortcut);
mDynamicShortcutCount = newDynamicCount;
}
/**
* Remove all shortcuts that aren't pinned nor dynamic.
*/
- private void removeOrphans(@NonNull ShortcutService s) {
+ private void removeOrphans() {
ArrayList<String> removeList = null; // Lazily initialize.
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
@@ -238,7 +241,7 @@
}
if (removeList != null) {
for (int i = removeList.size() - 1; i >= 0; i--) {
- deleteShortcut(s, removeList.get(i));
+ deleteShortcut(removeList.get(i));
}
}
}
@@ -246,18 +249,18 @@
/**
* Remove all dynamic shortcuts.
*/
- public void deleteAllDynamicShortcuts(@NonNull ShortcutService s) {
+ public void deleteAllDynamicShortcuts() {
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_DYNAMIC);
}
- removeOrphans(s);
+ removeOrphans();
mDynamicShortcutCount = 0;
}
/**
* Remove a dynamic shortcut by ID.
*/
- public void deleteDynamicWithId(@NonNull ShortcutService s, @NonNull String shortcutId) {
+ public void deleteDynamicWithId(@NonNull String shortcutId) {
final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
if (oldShortcut == null) {
@@ -269,7 +272,7 @@
if (oldShortcut.isPinned()) {
oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
} else {
- deleteShortcut(s, shortcutId);
+ deleteShortcut(shortcutId);
}
}
@@ -279,14 +282,15 @@
*
* <p>Then remove all shortcuts that are not dynamic and no longer pinned either.
*/
- public void refreshPinnedFlags(@NonNull ShortcutService s) {
+ public void refreshPinnedFlags() {
// First, un-pin all shortcuts
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED);
}
// Then, for the pinned set for each launcher, set the pin flag one by one.
- s.getUserShortcutsLocked(getPackageUserId()).forAllLaunchers(launcherShortcuts -> {
+ mShortcutUser.mService.getUserShortcutsLocked(getPackageUserId())
+ .forAllLaunchers(launcherShortcuts -> {
final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
getPackageName(), getPackageUserId());
@@ -308,7 +312,7 @@
});
// Lastly, remove the ones that are no longer pinned nor dynamic.
- removeOrphans(s);
+ removeOrphans();
}
/**
@@ -317,8 +321,10 @@
* <p>This takes care of the resetting the counter for foreground apps as well as after
* locale changes.
*/
- public int getApiCallCount(@NonNull ShortcutService s) {
- mShortcutUser.resetThrottlingIfNeeded(s);
+ public int getApiCallCount() {
+ mShortcutUser.resetThrottlingIfNeeded();
+
+ final ShortcutService s = mShortcutUser.mService;
// Reset the counter if:
// - the package is in foreground now.
@@ -328,7 +334,7 @@
|| mLastKnownForegroundElapsedTime
< s.getUidLastForegroundElapsedTimeLocked(mPackageUid)) {
mLastKnownForegroundElapsedTime = s.injectElapsedRealtime();
- resetRateLimiting(s);
+ resetRateLimiting();
}
// Note resetThrottlingIfNeeded() and resetRateLimiting() will set 0 to mApiCallCount,
@@ -349,8 +355,8 @@
// If not reset yet, then reset.
if (mLastResetTime < last) {
if (ShortcutService.DEBUG) {
- Slog.d(TAG, String.format("My last reset=%d, now=%d, last=%d: resetting",
- mLastResetTime, now, last));
+ Slog.d(TAG, String.format("%s: last reset=%d, now=%d, last=%d: resetting",
+ getPackageName(), mLastResetTime, now, last));
}
mApiCallCount = 0;
mLastResetTime = last;
@@ -365,8 +371,10 @@
* <p>This takes care of the resetting the counter for foreground apps as well as after
* locale changes, which is done internally by {@link #getApiCallCount}.
*/
- public boolean tryApiCall(@NonNull ShortcutService s) {
- if (getApiCallCount(s) >= s.mMaxUpdatesPerInterval) {
+ public boolean tryApiCall() {
+ final ShortcutService s = mShortcutUser.mService;
+
+ if (getApiCallCount() >= s.mMaxUpdatesPerInterval) {
return false;
}
mApiCallCount++;
@@ -374,13 +382,13 @@
return true;
}
- public void resetRateLimiting(@NonNull ShortcutService s) {
+ public void resetRateLimiting() {
if (ShortcutService.DEBUG) {
Slog.d(TAG, "resetRateLimiting: " + getPackageName());
}
if (mApiCallCount > 0) {
mApiCallCount = 0;
- s.scheduleSaveUser(getOwnerUserId());
+ mShortcutUser.mService.scheduleSaveUser(getOwnerUserId());
}
}
@@ -392,9 +400,9 @@
/**
* Find all shortcuts that match {@code query}.
*/
- public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
+ public void findAll(@NonNull List<ShortcutInfo> result,
@Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
- findAll(s, result, query, cloneFlag, null, 0);
+ findAll(result, query, cloneFlag, null, 0);
}
/**
@@ -404,7 +412,7 @@
* by the calling launcher will not be included in the result, and also "isPinned" will be
* adjusted for the caller too.
*/
- public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
+ public void findAll(@NonNull List<ShortcutInfo> result,
@Nullable Predicate<ShortcutInfo> query, int cloneFlag,
@Nullable String callingLauncher, int launcherUserId) {
if (getPackageInfo().isShadow()) {
@@ -412,6 +420,8 @@
return;
}
+ final ShortcutService s = mShortcutUser.mService;
+
// Set of pinned shortcuts by the calling launcher.
final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
: s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
@@ -452,10 +462,34 @@
}
/**
+ * Return the filenames (excluding path names) of icon bitmap files from this package.
+ */
+ public ArraySet<String> getUsedBitmapFiles() {
+ final ArraySet<String> usedFiles = new ArraySet<>(mShortcuts.size());
+
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+ if (si.getBitmapPath() != null) {
+ usedFiles.add(getFileName(si.getBitmapPath()));
+ }
+ }
+ return usedFiles;
+ }
+
+ private static String getFileName(@NonNull String path) {
+ final int sep = path.lastIndexOf(File.separatorChar);
+ if (sep == -1) {
+ return path;
+ } else {
+ return path.substring(sep + 1);
+ }
+ }
+
+ /**
* Called when the package is updated. If there are shortcuts with resource icons, update
* their timestamps.
*/
- public void handlePackageUpdated(ShortcutService s, int newVersionCode) {
+ public void handlePackageUpdated(int newVersionCode) {
if (getPackageInfo().getVersionCode() >= newVersionCode) {
// Version hasn't changed; nothing to do.
return;
@@ -467,11 +501,13 @@
getPackageInfo().setVersionCode(newVersionCode);
+ final ShortcutService s = mShortcutUser.mService;
+
boolean changed = false;
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
- if (si.hasIconResource()) {
+ if (si.hasAnyResources()) {
changed = true;
si.setTimestamp(s.injectCurrentTimeMillis());
}
@@ -485,7 +521,7 @@
}
}
- public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
pw.println();
pw.print(prefix);
@@ -498,7 +534,7 @@
pw.print(prefix);
pw.print(" ");
pw.print("Calls: ");
- pw.print(getApiCallCount(s));
+ pw.print(getApiCallCount());
pw.println();
// getApiCallCount() may have updated mLastKnownForegroundElapsedTime.
@@ -514,10 +550,10 @@
pw.print("Last reset: [");
pw.print(mLastResetTime);
pw.print("] ");
- pw.print(s.formatTime(mLastResetTime));
+ pw.print(ShortcutService.formatTime(mLastResetTime));
pw.println();
- getPackageInfo().dump(s, pw, prefix + " ");
+ getPackageInfo().dump(pw, prefix + " ");
pw.println();
pw.print(prefix);
@@ -545,7 +581,7 @@
pw.print("Total bitmap size: ");
pw.print(totalBitmapSize);
pw.print(" (");
- pw.print(Formatter.formatFileSize(s.mContext, totalBitmapSize));
+ pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize));
pw.println(")");
}
@@ -586,9 +622,13 @@
ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent());
// writeAttr(out, "icon", si.getIcon()); // We don't save it.
ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
+ ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId());
ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
+ ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId());
+ ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage());
+ ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID, si.getDisabledMessageResId());
ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras());
- ShortcutService.writeAttr(out, ATTR_WEIGHT, si.getWeight());
+ ShortcutService.writeAttr(out, ATTR_RANK, si.getRank());
ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
si.getLastChangedTimestamp());
if (forBackup) {
@@ -627,7 +667,7 @@
final String packageName = ShortcutService.parseStringAttribute(parser,
ATTR_NAME);
- final ShortcutPackage ret = new ShortcutPackage(s, shortcutUser,
+ final ShortcutPackage ret = new ShortcutPackage(shortcutUser,
shortcutUser.getUserId(), packageName);
ret.mDynamicShortcutCount =
@@ -671,10 +711,14 @@
ComponentName activityComponent;
// Icon icon;
String title;
+ int titleResId;
String text;
+ int textResId;
+ String disabledMessage;
+ int disabledMessageResId;
Intent intent;
PersistableBundle intentPersistableExtras = null;
- int weight;
+ int rank;
PersistableBundle extras = null;
long lastChangedTimestamp;
int flags;
@@ -686,9 +730,14 @@
activityComponent = ShortcutService.parseComponentNameAttribute(parser,
ATTR_ACTIVITY);
title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
+ titleResId = ShortcutService.parseIntAttribute(parser, ATTR_TITLE_RES_ID);
text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT);
+ textResId = ShortcutService.parseIntAttribute(parser, ATTR_TEXT_RES_ID);
+ disabledMessage = ShortcutService.parseStringAttribute(parser, ATTR_DISABLED_MESSAGE);
+ disabledMessageResId = ShortcutService.parseIntAttribute(parser,
+ ATTR_DISABLED_MESSAGE_RES_ID);
intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT);
- weight = (int) ShortcutService.parseLongAttribute(parser, ATTR_WEIGHT);
+ rank = (int) ShortcutService.parseLongAttribute(parser, ATTR_RANK);
lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP);
flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS);
iconRes = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES);
@@ -733,9 +782,10 @@
}
return new ShortcutInfo(
- userId, id, packageName, activityComponent, /* icon =*/ null, title, text,
+ userId, id, packageName, activityComponent, /* icon =*/ null,
+ title, titleResId, text, textResId, disabledMessage, disabledMessageResId,
categories, intent,
- intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
+ intentPersistableExtras, rank, extras, lastChangedTimestamp, flags,
iconRes, bitmapPath);
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index 74969f0..ae9709e 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -189,7 +189,7 @@
mSigHashes = hashes;
}
- public void dump(ShortcutService s, PrintWriter pw, String prefix) {
+ public void dump(PrintWriter pw, String prefix) {
pw.println();
pw.print(prefix);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 6fbdb82..0c2417c 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -69,18 +69,20 @@
return mPackageInfo;
}
- public void refreshPackageInfoAndSave(ShortcutService s) {
+ public void refreshPackageInfoAndSave() {
if (mPackageInfo.isShadow()) {
return; // Don't refresh for shadow user.
}
+ final ShortcutService s = mShortcutUser.mService;
mPackageInfo.refresh(s, this);
s.scheduleSaveUser(getOwnerUserId());
}
- public void attemptToRestoreIfNeededAndSave(ShortcutService s) {
+ public void attemptToRestoreIfNeededAndSave() {
if (!mPackageInfo.isShadow()) {
return; // Already installed, nothing to do.
}
+ final ShortcutService s = mShortcutUser.mService;
if (!s.isPackageInstalled(mPackageName, mPackageUserId)) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Package still not installed: %s user=%d",
@@ -91,14 +93,14 @@
if (!mPackageInfo.hasSignatures()) {
s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId
+ " but signatures not found in the restore data.");
- onRestoreBlocked(s);
+ onRestoreBlocked();
return;
}
final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId);
if (!mPackageInfo.canRestoreTo(s, pi)) {
// Package is now installed, but can't restore. Let the subclass do the cleanup.
- onRestoreBlocked(s);
+ onRestoreBlocked();
return;
}
if (ShortcutService.DEBUG) {
@@ -106,7 +108,7 @@
mPackageUserId, getOwnerUserId()));
}
- onRestored(s);
+ onRestored();
// Now the package is not shadow.
mPackageInfo.setShadow(false);
@@ -118,12 +120,12 @@
* Called when the new package can't be restored because it has a lower version number
* or different signatures.
*/
- protected abstract void onRestoreBlocked(ShortcutService s);
+ protected abstract void onRestoreBlocked();
/**
* Called when the new package is successfully restored.
*/
- protected abstract void onRestored(ShortcutService s);
+ protected abstract void onRestored();
public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 8268776..6a10f41 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -32,6 +32,7 @@
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
@@ -74,7 +75,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
@@ -104,19 +104,26 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* TODO:
+ * - Manifest shortcuts.
+ *
+ * - Implement disableShortcuts().
+ *
+ * - Implement reportShortcutUsed().
+ *
+ * - Ranks should be recalculated after each update.
+ *
+ * - When the system locale changes, update timestamps for shortcuts with string resources,
+ * and notify the launcher.
*
* - Default launcher check does take a few ms. Worth caching.
*
- * - Clear data -> remove all dynamic? but not the pinned?
- *
- * - Scan and remove orphan bitmaps (just in case).
- *
* - Detect when already registered instances are passed to APIs again, which might break
* internal bitmap handling.
*
@@ -125,8 +132,6 @@
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
@@ -280,6 +285,8 @@
*/
private final AtomicLong mLocaleChangeSequenceNumber = new AtomicLong();
+ private final AtomicBoolean mBootCompleted = new AtomicBoolean();
+
private static final int PACKAGE_MATCH_FLAGS =
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
@@ -293,8 +300,9 @@
int GET_PACKAGE_INFO_WITH_SIG = 2;
int GET_APPLICATION_INFO = 3;
int LAUNCHER_PERMISSION_CHECK = 4;
+ int CLEANUP_DANGLING_BITMAPS = 5;
- int COUNT = LAUNCHER_PERMISSION_CHECK + 1;
+ int COUNT = CLEANUP_DANGLING_BITMAPS + 1;
}
final Object mStatLock = new Object();
@@ -321,9 +329,6 @@
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
@@ -333,7 +338,7 @@
void logDurationStat(int statId, long start) {
synchronized (mStatLock) {
mCountStats[statId]++;
- mDurationStats[statId] += (System.currentTimeMillis() - start);
+ mDurationStats[statId] += (injectElapsedRealtime() - start);
}
}
@@ -424,7 +429,6 @@
/** 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);
}
@@ -432,14 +436,14 @@
case SystemService.PHASE_LOCK_SETTINGS_READY:
initialize();
break;
+ case SystemService.PHASE_BOOT_COMPLETED:
+ mBootCompleted.set(true);
+ break;
}
}
/** lifecycle event */
void handleUnlockUser(int userId) {
- if (!FEATURE_ENABLED) {
- return;
- }
synchronized (mLock) {
// Preload
getUserShortcutsLocked(userId);
@@ -450,9 +454,6 @@
/** lifecycle event */
void handleCleanupUser(int userId) {
- if (!FEATURE_ENABLED) {
- return;
- }
synchronized (mLock) {
unloadUserLocked(userId);
}
@@ -782,7 +783,7 @@
out.setOutput(bos, StandardCharsets.UTF_8.name());
out.startDocument(null, true);
- getUserShortcutsLocked(userId).saveToXml(this, out, forBackup);
+ getUserShortcutsLocked(userId).saveToXml(out, forBackup);
out.endDocument();
@@ -816,7 +817,9 @@
return null;
}
try {
- return loadUserInternal(userId, in, /* forBackup= */ false);
+ final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
+ cleanupDanglingBitmapDirectoriesLocked(userId, ret);
+ return ret;
} catch (IOException|XmlPullParserException e) {
Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
return null;
@@ -958,7 +961,7 @@
if (userPackages == null) {
userPackages = loadUserLocked(userId);
if (userPackages == null) {
- userPackages = new ShortcutUser(userId);
+ userPackages = new ShortcutUser(this, userId);
}
mUsers.put(userId, userPackages);
}
@@ -976,7 +979,7 @@
@NonNull
ShortcutPackage getPackageShortcutsLocked(
@NonNull String packageName, @UserIdInt int userId) {
- return getUserShortcutsLocked(userId).getPackageShortcuts(this, packageName);
+ return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
}
@GuardedBy("mLock")
@@ -985,7 +988,7 @@
@NonNull String packageName, @UserIdInt int ownerUserId,
@UserIdInt int launcherUserId) {
return getUserShortcutsLocked(ownerUserId)
- .getLauncherShortcuts(this, packageName, launcherUserId);
+ .getLauncherShortcuts(packageName, launcherUserId);
}
// === Caller validation ===
@@ -1013,6 +1016,57 @@
}
}
+ private void cleanupDanglingBitmapDirectoriesLocked(
+ @UserIdInt int userId, @NonNull ShortcutUser user) {
+ if (DEBUG) {
+ Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId);
+ }
+ final long start = injectElapsedRealtime();
+
+ final File bitmapDir = getUserBitmapFilePath(userId);
+ final File[] children = bitmapDir.listFiles();
+ if (children == null) {
+ return;
+ }
+ for (File child : children) {
+ if (!child.isDirectory()) {
+ continue;
+ }
+ final String packageName = child.getName();
+ if (DEBUG) {
+ Slog.d(TAG, "cleanupDanglingBitmaps: Found directory=" + packageName);
+ }
+ if (!user.hasPackage(packageName)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Removing dangling bitmap directory: " + packageName);
+ }
+ cleanupBitmapsForPackage(userId, packageName);
+ } else {
+ cleanupDanglingBitmapFilesLocked(userId, user, packageName, child);
+ }
+ }
+ logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start);
+ }
+
+ private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user,
+ @NonNull String packageName, @NonNull File path) {
+ final ArraySet<String> usedFiles =
+ user.getPackageShortcuts(packageName).getUsedBitmapFiles();
+
+ for (File child : path.listFiles()) {
+ if (!child.isFile()) {
+ continue;
+ }
+ final String name = child.getName();
+ if (!usedFiles.contains(name)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath());
+ }
+ child.delete();
+ }
+ }
+ }
+
@VisibleForTesting
static class FileOutputStreamWithPath extends FileOutputStream {
private final File mFile;
@@ -1268,8 +1322,13 @@
}
private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) {
- if (!mUserManager.isUserRunning(userId)) {
- return;
+ final long token = injectClearCallingIdentity();
+ try {
+ if (!mUserManager.isUserRunning(userId)) {
+ return;
+ }
+ } finally {
+ injectRestoreCallingIdentity(token);
}
postToHandler(() -> {
final ArrayList<ShortcutChangeListener> copy;
@@ -1369,7 +1428,7 @@
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
// Throttling.
- if (!ps.tryApiCall(this)) {
+ if (!ps.tryApiCall()) {
return false;
}
enforceMaxDynamicShortcuts(size);
@@ -1380,12 +1439,12 @@
}
// First, remove all un-pinned; dynamic shortcuts
- ps.deleteAllDynamicShortcuts(this);
+ ps.deleteAllDynamicShortcuts();
// Then, add/update all. We need to make sure to take over "pinned" flag.
for (int i = 0; i < size; i++) {
final ShortcutInfo newShortcut = newShortcuts.get(i);
- ps.addDynamicShortcut(this, newShortcut);
+ ps.addDynamicShortcut(newShortcut);
}
}
packageShortcutsChanged(packageName, userId);
@@ -1404,7 +1463,7 @@
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
// Throttling.
- if (!ps.tryApiCall(this)) {
+ if (!ps.tryApiCall()) {
return false;
}
@@ -1444,7 +1503,7 @@
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
// Throttling.
- if (!ps.tryApiCall(this)) {
+ if (!ps.tryApiCall()) {
return false;
}
for (int i = 0; i < size; i++) {
@@ -1454,7 +1513,7 @@
fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
// Add it.
- ps.addDynamicShortcut(this, newShortcut);
+ ps.addDynamicShortcut(newShortcut);
}
}
packageShortcutsChanged(packageName, userId);
@@ -1463,6 +1522,18 @@
}
@Override
+ public void disableShortcuts(String packageName, List shortcutIds,
+ String disabledMessage, int disabledMessageResId, @UserIdInt int userId) {
+ verifyCaller(packageName, userId);
+ Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
+
+ synchronized (mLock) {
+ // TODO implement it.
+ }
+ packageShortcutsChanged(packageName, userId);
+ }
+
+ @Override
public void removeDynamicShortcuts(String packageName, List shortcutIds,
@UserIdInt int userId) {
verifyCaller(packageName, userId);
@@ -1470,7 +1541,7 @@
synchronized (mLock) {
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
- getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(this,
+ getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(
Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
}
}
@@ -1482,7 +1553,7 @@
verifyCaller(packageName, userId);
synchronized (mLock) {
- getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(this);
+ getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts();
}
packageShortcutsChanged(packageName, userId);
}
@@ -1514,7 +1585,7 @@
final ArrayList<ShortcutInfo> ret = new ArrayList<>();
- getPackageShortcutsLocked(packageName, userId).findAll(this, ret, query, cloneFlags);
+ getPackageShortcutsLocked(packageName, userId).findAll(ret, query, cloneFlags);
return new ParceledListSlice<>(ret);
}
@@ -1533,7 +1604,7 @@
synchronized (mLock) {
return mMaxUpdatesPerInterval
- - getPackageShortcutsLocked(packageName, userId).getApiCallCount(this);
+ - getPackageShortcutsLocked(packageName, userId).getApiCallCount();
}
}
@@ -1547,7 +1618,7 @@
}
@Override
- public int getIconMaxDimensions(String packageName, int userId) throws RemoteException {
+ public int getIconMaxDimensions(String packageName, int userId) {
verifyCaller(packageName, userId);
synchronized (mLock) {
@@ -1555,6 +1626,13 @@
}
}
+ @Override
+ public void reportShortcutUsed(String packageName, String shortcutId, int userId) {
+ verifyCaller(packageName, userId);
+
+ // TODO Implement it.
+ }
+
/**
* Reset all throttling, for developer options and command line. Only system/shell can call it.
*/
@@ -1608,14 +1686,14 @@
@VisibleForTesting
boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
synchronized (mLock) {
- final long start = System.currentTimeMillis();
+ final long start = injectElapsedRealtime();
final ShortcutUser user = getUserShortcutsLocked(userId);
final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
// Default launcher from package manager.
- final long startGetHomeActivitiesAsUser = System.currentTimeMillis();
+ final long startGetHomeActivitiesAsUser = injectElapsedRealtime();
final ComponentName defaultLauncher = injectPackageManagerInternal()
.getHomeActivitiesAsUser(allHomeCandidates, userId);
logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser);
@@ -1627,7 +1705,7 @@
Slog.v(TAG, "Default launcher from PM: " + detected);
}
} else {
- detected = user.getLauncherComponent();
+ detected = user.getDefaultLauncherComponent();
// TODO: Make sure it's still enabled.
if (DEBUG) {
@@ -1667,7 +1745,7 @@
if (DEBUG) {
Slog.v(TAG, "Detected launcher: " + detected);
}
- user.setLauncherComponent(this, detected);
+ user.setDefaultLauncherComponent(detected);
return detected.getPackageName().equals(callingPackage);
} else {
// Default launcher not found.
@@ -1701,7 +1779,7 @@
// First, remove the package from the package list (if the package is a publisher).
if (packageUserId == owningUserId) {
- if (user.removePackage(this, packageName) != null) {
+ if (user.removePackage(packageName) != null) {
doNotify = true;
}
}
@@ -1714,7 +1792,7 @@
// Now there may be orphan shortcuts because we removed pinned shortcuts at the previous
// step. Remove them too.
- user.forAllPackages(p -> p.refreshPinnedFlags(this));
+ user.forAllPackages(p -> p.refreshPinnedFlags());
scheduleSaveUser(owningUserId);
@@ -1740,17 +1818,17 @@
@Nullable ComponentName componentName,
int queryFlags, int userId) {
final ArrayList<ShortcutInfo> ret = new ArrayList<>();
- final int cloneFlag =
- ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) == 0)
- ? ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER
- : ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO;
+ final boolean cloneKeyFieldOnly =
+ ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0);
+ final int cloneFlag = cloneKeyFieldOnly ? ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO
+ : ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER;
if (packageName == null) {
shortcutIds = null; // LauncherAppsService already threw for it though.
}
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
- .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+ .attemptToRestoreIfNeededAndSave();
if (packageName != null) {
getShortcutsInnerLocked(launcherUserId,
@@ -1765,6 +1843,21 @@
});
}
}
+ // Resolve all strings if needed.
+ if (!cloneKeyFieldOnly) {
+ final long token = injectClearCallingIdentity();
+ try {
+ for (int i = ret.size() - 1; i >= 0; i--) {
+ try {
+ ret.get(i).resolveStringsRequiringCrossUser(mContext);
+ } catch (NameNotFoundException e) {
+ continue;
+ }
+ }
+ } finally {
+ injectRestoreCallingIdentity(token);
+ }
+ }
return ret;
}
@@ -1775,7 +1868,13 @@
final ArraySet<String> ids = shortcutIds == null ? null
: new ArraySet<>(shortcutIds);
- getPackageShortcutsLocked(packageName, userId).findAll(ShortcutService.this, ret,
+ final ShortcutPackage p = getUserShortcutsLocked(userId)
+ .getPackageShortcutsIfExists(packageName);
+ if (p == null) {
+ return; // No need to instantiate ShortcutPackage.
+ }
+
+ p.findAll(ret,
(ShortcutInfo si) -> {
if (si.getLastChangedTimestamp() < changedSince) {
return false;
@@ -1783,9 +1882,11 @@
if (ids != null && !ids.contains(si.getId())) {
return false;
}
- if (componentName != null
- && !componentName.equals(si.getActivityComponent())) {
- return false;
+ if (componentName != null) {
+ if (si.getActivityComponent() != null
+ && !si.getActivityComponent().equals(componentName)) {
+ return false;
+ }
}
final boolean matchDynamic =
((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
@@ -1805,7 +1906,7 @@
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
- .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+ .attemptToRestoreIfNeededAndSave();
final ShortcutInfo si = getShortcutInfoLocked(
launcherUserId, callingPackage, packageName, shortcutId, userId);
@@ -1819,9 +1920,14 @@
Preconditions.checkStringNotEmpty(packageName, "packageName");
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
+ final ShortcutPackage p = getUserShortcutsLocked(userId)
+ .getPackageShortcutsIfExists(packageName);
+ if (p == null) {
+ return null;
+ }
+
final ArrayList<ShortcutInfo> list = new ArrayList<>(1);
- getPackageShortcutsLocked(packageName, userId).findAll(
- ShortcutService.this, list,
+ p.findAll(list,
(ShortcutInfo si) -> shortcutId.equals(si.getId()),
/* clone flags=*/ 0, callingPackage, launcherUserId);
return list.size() == 0 ? null : list.get(0);
@@ -1838,10 +1944,9 @@
synchronized (mLock) {
final ShortcutLauncher launcher =
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
- launcher.attemptToRestoreIfNeededAndSave(ShortcutService.this);
+ launcher.attemptToRestoreIfNeededAndSave();
- launcher.pinShortcuts(
- ShortcutService.this, userId, packageName, shortcutIds);
+ launcher.pinShortcuts(userId, packageName, shortcutIds);
}
packageShortcutsChanged(packageName, userId);
}
@@ -1856,7 +1961,7 @@
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
- .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+ .attemptToRestoreIfNeededAndSave();
// Make sure the shortcut is actually visible to the launcher.
final ShortcutInfo si = getShortcutInfoLocked(
@@ -1885,10 +1990,15 @@
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
- .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+ .attemptToRestoreIfNeededAndSave();
- final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
- packageName, userId).findShortcutById(shortcutId);
+ final ShortcutPackage p = getUserShortcutsLocked(userId)
+ .getPackageShortcutsIfExists(packageName);
+ if (p == null) {
+ return 0;
+ }
+
+ final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
return (shortcutInfo != null && shortcutInfo.hasIconResource())
? shortcutInfo.getIconResourceId() : 0;
}
@@ -1904,10 +2014,15 @@
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
- .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+ .attemptToRestoreIfNeededAndSave();
- final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
- packageName, userId).findShortcutById(shortcutId);
+ final ShortcutPackage p = getUserShortcutsLocked(userId)
+ .getPackageShortcutsIfExists(packageName);
+ if (p == null) {
+ return null;
+ }
+
+ final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
return null;
}
@@ -1938,9 +2053,6 @@
*/
@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
@@ -1953,8 +2065,15 @@
//
// This allows ShortcutUser's to detect the system locale change, so they can reset
// counters.
- mLocaleChangeSequenceNumber.incrementAndGet();
- postToHandler(() -> scheduleSaveBaseState());
+
+ // Ignore all callback during system boot.
+ if (mBootCompleted.get()) {
+ mLocaleChangeSequenceNumber.incrementAndGet();
+ if (DEBUG) {
+ Slog.d(TAG, "onSystemLocaleChangedNoLock: " + mLocaleChangeSequenceNumber.get());
+ }
+ postToHandler(() -> scheduleSaveBaseState());
+ }
}
}
@@ -2009,7 +2128,7 @@
if (versionCode >= 0) {
// Package still installed, see if it's updated.
getUserShortcutsLocked(ownerUserId).handlePackageUpdated(
- this, spi.getPackageName(), versionCode);
+ spi.getPackageName(), versionCode);
} else {
gonePackages.add(PackageWithUser.of(spi));
}
@@ -2046,7 +2165,7 @@
if (versionCode < 0) {
return; // shouldn't happen
}
- getUserShortcutsLocked(userId).handlePackageUpdated(this, packageName, versionCode);
+ getUserShortcutsLocked(userId).handlePackageUpdated(packageName, versionCode);
}
}
@@ -2089,7 +2208,7 @@
@VisibleForTesting
PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
boolean getSignatures) {
- final long start = System.currentTimeMillis();
+ final long start = injectElapsedRealtime();
final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
@@ -2110,7 +2229,7 @@
@VisibleForTesting
ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
- final long start = System.currentTimeMillis();
+ final long start = injectElapsedRealtime();
final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
@@ -2168,7 +2287,7 @@
return null;
}
- user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave(this));
+ user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave());
// Then save.
final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
@@ -2283,11 +2402,13 @@
dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()");
dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)");
dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
+
+ dumpStatLS(pw, p, Stats.CLEANUP_DANGLING_BITMAPS, "cleanupDanglingBitmaps");
}
for (int i = 0; i < mUsers.size(); i++) {
pw.println();
- mUsers.valueAt(i).dump(this, pw, " ");
+ mUsers.valueAt(i).dump(pw, " ");
}
pw.println();
@@ -2499,8 +2620,7 @@
private void clearLauncher() {
synchronized (mLock) {
- getUserShortcutsLocked(mUserId).setLauncherComponent(
- ShortcutService.this, null);
+ getUserShortcutsLocked(mUserId).setDefaultLauncherComponent(null);
}
}
@@ -2510,7 +2630,7 @@
hasShortcutHostPermissionInner("-", mUserId);
getOutPrintWriter().println("Launcher: "
- + getUserShortcutsLocked(mUserId).getLauncherComponent());
+ + getUserShortcutsLocked(mUserId).getDefaultLauncherComponent());
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 7d19a78..d38cfba 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.text.format.Formatter;
@@ -87,6 +88,8 @@
}
}
+ final ShortcutService mService;
+
@UserIdInt
private final int mUserId;
@@ -97,11 +100,12 @@
private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
/** Default launcher that can access the launcher apps APIs. */
- private ComponentName mLauncherComponent;
+ private ComponentName mDefaultLauncherComponent;
private long mKnownLocaleChangeSequenceNumber;
- public ShortcutUser(int userId) {
+ public ShortcutUser(ShortcutService service, int userId) {
+ mService = service;
mUserId = userId;
}
@@ -116,10 +120,14 @@
return mPackages;
}
- public ShortcutPackage removePackage(@NonNull ShortcutService s, @NonNull String packageName) {
+ public boolean hasPackage(@NonNull String packageName) {
+ return mPackages.containsKey(packageName);
+ }
+
+ public ShortcutPackage removePackage(@NonNull String packageName) {
final ShortcutPackage removed = mPackages.remove(packageName);
- s.cleanupBitmapsForPackage(mUserId, packageName);
+ mService.cleanupBitmapsForPackage(mUserId, packageName);
return removed;
}
@@ -136,23 +144,33 @@
launcher.getPackageName()), launcher);
}
+ @Nullable
public ShortcutLauncher removeLauncher(
@UserIdInt int packageUserId, @NonNull String packageName) {
return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
}
- public ShortcutPackage getPackageShortcuts(ShortcutService s, @NonNull String packageName) {
- ShortcutPackage ret = mPackages.get(packageName);
- if (ret == null) {
- ret = new ShortcutPackage(s, this, mUserId, packageName);
- mPackages.put(packageName, ret);
- } else {
- ret.attemptToRestoreIfNeededAndSave(s);
+ @Nullable
+ public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) {
+ final ShortcutPackage ret = mPackages.get(packageName);
+ if (ret != null) {
+ ret.attemptToRestoreIfNeededAndSave();
}
return ret;
}
- public ShortcutLauncher getLauncherShortcuts(ShortcutService s, @NonNull String packageName,
+ @NonNull
+ public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
+ ShortcutPackage ret = getPackageShortcutsIfExists(packageName);
+ if (ret == null) {
+ ret = new ShortcutPackage(this, mUserId, packageName);
+ mPackages.put(packageName, ret);
+ }
+ return ret;
+ }
+
+ @NonNull
+ public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
@UserIdInt int launcherUserId) {
final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
ShortcutLauncher ret = mLaunchers.get(key);
@@ -160,7 +178,7 @@
ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
mLaunchers.put(key, ret);
} else {
- ret.attemptToRestoreIfNeededAndSave(s);
+ ret.attemptToRestoreIfNeededAndSave();
}
return ret;
}
@@ -197,8 +215,8 @@
/**
* Reset all throttling counters for all packages, if there has been a system locale change.
*/
- public void resetThrottlingIfNeeded(ShortcutService s) {
- final long currentNo = s.getLocaleChangeSequenceNumber();
+ public void resetThrottlingIfNeeded() {
+ final long currentNo = mService.getLocaleChangeSequenceNumber();
if (mKnownLocaleChangeSequenceNumber < currentNo) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, "LocaleChange detected for user " + mUserId);
@@ -206,31 +224,35 @@
mKnownLocaleChangeSequenceNumber = currentNo;
- forAllPackages(p -> p.resetRateLimiting(s));
+ forAllPackages(p -> p.resetRateLimiting());
- s.scheduleSaveUser(mUserId);
+ mService.scheduleSaveUser(mUserId);
}
}
/**
* Called when a package is updated.
*/
- public void handlePackageUpdated(ShortcutService s, @NonNull String packageName,
+ public void handlePackageUpdated(@NonNull String packageName,
int newVersionCode) {
if (!mPackages.containsKey(packageName)) {
return;
}
- getPackageShortcuts(s, packageName).handlePackageUpdated(s, newVersionCode);
+ final ShortcutPackage p = getPackageShortcutsIfExists(packageName);
+ if (p == null) {
+ return; // No need to instantiate ShortcutPackage.
+ }
+ p.handlePackageUpdated(newVersionCode);
}
public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
@UserIdInt int packageUserId) {
forPackageItem(packageName, packageUserId, spi -> {
- spi.attemptToRestoreIfNeededAndSave(s);
+ spi.attemptToRestoreIfNeededAndSave();
});
}
- public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup)
+ public void saveToXml(XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
out.startTag(null, TAG_ROOT);
@@ -238,29 +260,29 @@
mKnownLocaleChangeSequenceNumber);
ShortcutService.writeTagValue(out, TAG_LAUNCHER,
- mLauncherComponent);
+ mDefaultLauncherComponent);
// Can't use forEachPackageItem due to the checked exceptions.
{
final int size = mLaunchers.size();
for (int i = 0; i < size; i++) {
- saveShortcutPackageItem(s, out, mLaunchers.valueAt(i), forBackup);
+ saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup);
}
}
{
final int size = mPackages.size();
for (int i = 0; i < size; i++) {
- saveShortcutPackageItem(s, out, mPackages.valueAt(i), forBackup);
+ saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup);
}
}
out.endTag(null, TAG_ROOT);
}
- private void saveShortcutPackageItem(ShortcutService s, XmlSerializer out,
+ private void saveShortcutPackageItem(XmlSerializer out,
ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
if (forBackup) {
- if (!s.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
+ if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
return; // Don't save.
}
if (spi.getPackageUserId() != spi.getOwnerUserId()) {
@@ -272,7 +294,7 @@
public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
boolean fromBackup) throws IOException, XmlPullParserException {
- final ShortcutUser ret = new ShortcutUser(userId);
+ final ShortcutUser ret = new ShortcutUser(s, userId);
ret.mKnownLocaleChangeSequenceNumber = ShortcutService.parseLongAttribute(parser,
ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER);
@@ -290,7 +312,7 @@
if (depth == outerDepth + 1) {
switch (tag) {
case TAG_LAUNCHER: {
- ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute(
+ ret.mDefaultLauncherComponent = ShortcutService.parseComponentNameAttribute(
parser, ATTR_VALUE);
continue;
}
@@ -315,16 +337,16 @@
return ret;
}
- public ComponentName getLauncherComponent() {
- return mLauncherComponent;
+ public ComponentName getDefaultLauncherComponent() {
+ return mDefaultLauncherComponent;
}
- public void setLauncherComponent(ShortcutService s, ComponentName launcherComponent) {
- if (Objects.equal(mLauncherComponent, launcherComponent)) {
+ public void setDefaultLauncherComponent(ComponentName launcherComponent) {
+ if (Objects.equal(mDefaultLauncherComponent, launcherComponent)) {
return;
}
- mLauncherComponent = launcherComponent;
- s.scheduleSaveUser(mUserId);
+ mDefaultLauncherComponent = launcherComponent;
+ mService.scheduleSaveUser(mUserId);
}
public void resetThrottling() {
@@ -333,7 +355,7 @@
}
}
- public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
pw.print(prefix);
pw.print("User: ");
pw.print(mUserId);
@@ -345,24 +367,24 @@
pw.print(prefix);
pw.print("Default launcher: ");
- pw.print(mLauncherComponent);
+ pw.print(mDefaultLauncherComponent);
pw.println();
for (int i = 0; i < mLaunchers.size(); i++) {
- mLaunchers.valueAt(i).dump(s, pw, prefix);
+ mLaunchers.valueAt(i).dump(pw, prefix);
}
for (int i = 0; i < mPackages.size(); i++) {
- mPackages.valueAt(i).dump(s, pw, prefix);
+ mPackages.valueAt(i).dump(pw, prefix);
}
pw.println();
pw.print(prefix);
pw.println("Bitmap directories: ");
- dumpDirectorySize(s, pw, prefix + " ", s.getUserBitmapFilePath(mUserId));
+ dumpDirectorySize(pw, prefix + " ", mService.getUserBitmapFilePath(mUserId));
}
- private void dumpDirectorySize(@NonNull ShortcutService s, @NonNull PrintWriter pw,
+ private void dumpDirectorySize(@NonNull PrintWriter pw,
@NonNull String prefix, File path) {
int numFiles = 0;
long size = 0;
@@ -373,7 +395,7 @@
numFiles++;
size += child.length();
} else if (child.isDirectory()) {
- dumpDirectorySize(s, pw, prefix + " ", child);
+ dumpDirectorySize(pw, prefix + " ", child);
}
}
}
@@ -385,7 +407,7 @@
pw.print(" files, size=");
pw.print(size);
pw.print(" (");
- pw.print(Formatter.formatFileSize(s.mContext, size));
+ pw.print(Formatter.formatFileSize(mService.mContext, size));
pw.println(")");
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 33ccf16..4541e2e 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -28,6 +28,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
import android.app.KeyguardManager;
@@ -70,6 +71,7 @@
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
+import android.text.TextUtils;
import android.util.AtomicFile;
import android.util.IntArray;
import android.util.Log;
@@ -414,7 +416,9 @@
// user restriction was not a default guest restriction.
setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, currentGuestUser.id);
}
- }
+
+ maybeInitializeDemoMode(UserHandle.USER_SYSTEM);
+}
@Override
public String getUserAccount(int userId) {
@@ -2070,7 +2074,8 @@
return null;
}
}
- if (!UserManager.isSplitSystemUser() && (flags & UserInfo.FLAG_EPHEMERAL) != 0) {
+ if (!UserManager.isSplitSystemUser() && (flags & UserInfo.FLAG_EPHEMERAL) != 0
+ && (flags & UserInfo.FLAG_DEMO) == 0) {
Log.e(LOG_TAG,
"Ephemeral users are supported on split-system-user systems only.");
return null;
@@ -2710,6 +2715,8 @@
mPm.onBeforeUserStartUninitialized(userId);
}
}
+
+ maybeInitializeDemoMode(userId);
}
/**
@@ -2724,6 +2731,7 @@
/**
* Make a note of the last started time of a user and do some cleanup.
+ * This is called with ActivityManagerService lock held.
* @param userId the user that was just foregrounded
*/
public void onUserLoggedIn(@UserIdInt int userId) {
@@ -2741,6 +2749,24 @@
scheduleWriteUser(userData);
}
+ private void maybeInitializeDemoMode(int userId) {
+ if (UserManager.isDeviceInDemoMode(mContext)) {
+ String demoLauncher =
+ mContext.getResources().getString(
+ com.android.internal.R.string.config_demoModeLauncherComponent);
+ if (!TextUtils.isEmpty(demoLauncher)) {
+ ComponentName componentToEnable = ComponentName.unflattenFromString(demoLauncher);
+ try {
+ AppGlobals.getPackageManager().setComponentEnabledSetting(componentToEnable,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0,
+ /* userId= */ userId);
+ } catch (RemoteException re) {
+ // Internal, shouldn't happen
+ }
+ }
+ }
+ }
+
/**
* Returns the next available user id, filling in any holes in the ids.
* TODO: May not be a good idea to recycle ids, in case it results in confusion
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 38a3f42..414d165 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -33,6 +33,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.service.persistentdata.PersistentDataBlockManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.util.Log;
@@ -424,6 +425,14 @@
android.provider.Settings.Global.SAFE_BOOT_DISALLOWED,
newValue ? 1 : 0);
break;
+ case UserManager.DISALLOW_FACTORY_RESET:
+ if (newValue) {
+ PersistentDataBlockManager manager = (PersistentDataBlockManager) context
+ .getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+ if (manager != null) {
+ manager.setOemUnlockEnabled(false);
+ }
+ }
}
} finally {
Binder.restoreCallingIdentity(id);
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index 5ef518e..6fc15f0 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -1145,7 +1145,7 @@
public GlobalActionsDialog(Context context, AlertParams params) {
super(context, getDialogTheme(context));
mContext = getContext();
- mAlert = new AlertController(mContext, this, getWindow());
+ mAlert = AlertController.create(mContext, this, getWindow());
mAdapter = (MyAdapter) params.mAdapter;
mWindowTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
params.apply(mAlert);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2f72607..11448bb 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5646,6 +5646,17 @@
break;
}
+ case KeyEvent.KEYCODE_FP_NAV_DOWN:
+ // fall through
+ case KeyEvent.KEYCODE_FP_NAV_UP:
+ // fall through
+ case KeyEvent.KEYCODE_FP_NAV_LEFT:
+ // fall through
+ case KeyEvent.KEYCODE_FP_NAV_RIGHT: {
+ interceptStatusBarKey(event);
+ break;
+ }
+
case KeyEvent.KEYCODE_SLEEP: {
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false;
@@ -5767,6 +5778,32 @@
}
/**
+ * Handle statusbar expansion events.
+ * @param event
+ */
+ private void interceptStatusBarKey(KeyEvent event) {
+ final int e = event.getKeyCode();
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ boolean doOpen = false;
+ boolean doClose = false;
+ doOpen = (e == KeyEvent.KEYCODE_FP_NAV_DOWN);
+ doClose = (e == KeyEvent.KEYCODE_FP_NAV_UP);
+ IStatusBarService sbar = getStatusBarService();
+ if (sbar != null) {
+ try {
+ if (doOpen) {
+ sbar.expandNotificationsPanel();
+ } else if (doClose) {
+ sbar.collapsePanels();
+ }
+ } catch (RemoteException e1) {
+ // oops, no statusbar. Ignore event.
+ }
+ }
+ }
+ }
+
+ /**
* Returns true if the key can have global actions attached to it.
* We reserve all power management keys for the system since they require
* very careful handling.
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e306d89..8b3dc02 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -42,7 +42,9 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.storage.IMountService;
+import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Slog;
@@ -55,6 +57,7 @@
import com.android.internal.widget.ILockSettings;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.am.ActivityManagerService;
+import com.android.server.am.RetailDemoModeService;
import com.android.server.audio.AudioService;
import com.android.server.camera.CameraService;
import com.android.server.clipboard.ClipboardService;
@@ -154,6 +157,8 @@
"com.google.android.clockwork.ThermalObserver";
private static final String WEAR_BLUETOOTH_SERVICE_CLASS =
"com.google.android.clockwork.bluetooth.WearBluetoothService";
+ private static final String WEAR_TIME_SERVICE_CLASS =
+ "com.google.android.clockwork.time.WearTimeService";
private static final String ACCOUNT_SERVICE_CLASS =
"com.android.server.accounts.AccountManagerService$Lifecycle";
private static final String CONTENT_SERVICE_CLASS =
@@ -1158,6 +1163,9 @@
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
mSystemServiceManager.startService(WEAR_BLUETOOTH_SERVICE_CLASS);
+ if (!disableNonCoreServices) {
+ mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
+ }
}
// Before things start rolling, be sure we have decided whether
@@ -1175,6 +1183,11 @@
// MMS service broker
mmsService = mSystemServiceManager.startService(MmsServiceBroker.class);
+ if (Settings.Global.getInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED, 0) == 0 ||
+ UserManager.isDeviceInDemoMode(mSystemContext)) {
+ mSystemServiceManager.startService(RetailDemoModeService.class);
+ }
+
// It is now time to start up the app processes...
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "MakeVibratorServiceReady");
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 0437e1d..59c6970 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -19,7 +19,8 @@
easymocklib \
guava \
android-support-test \
- mockito-target
+ mockito-target \
+ ShortcutManagerTestUtils
LOCAL_JAVA_LIBRARIES := android.test.runner
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
new file mode 100644
index 0000000..8a2a58c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
@@ -0,0 +1,6852 @@
+/*
+ * 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.assertAllStringsResolved;
+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 org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+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
+
+ private static final String[] EMPTY_STRINGS = new String[0]; // Just for readability.
+
+ // 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))).thenAnswer(new AnswerIsUserRunning(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();
+ }
+
+ /**
+ * Returns a boolean but also checks if the current UID is SYSTEM_UID.
+ */
+ private class AnswerIsUserRunning implements Answer<Boolean> {
+ private final boolean mAnswer;
+
+ private AnswerIsUserRunning(boolean answer) {
+ mAnswer = answer;
+ }
+
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ assertEquals("isUserRunning() must be called on SYSTEM UID.",
+ Process.SYSTEM_UID, mInjectedCallingUid);
+ return mAnswer;
+ }
+ }
+
+ 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, a timestamp and an activity component
+ */
+ private ShortcutInfo makeShortcutWithTimestampWithActivity(String id, long timestamp,
+ ComponentName activity) {
+ final ShortcutInfo s = makeShortcut(
+ id, "Title-" + id, activity, /* 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 rank) {
+ final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext)
+ .setId(id)
+ .setActivityComponent(new ComponentName(mClientContext.getPackageName(), "dummy"))
+ .setTitle(title)
+ .setRank(rank)
+ .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 void assertBitmapDirectories(int userId, String... expectedDirectories) {
+ final Set<String> expected = hashSet(set(expectedDirectories));
+
+ final Set<String> actual = new HashSet<>();
+
+ final File[] files = mService.getUserBitmapFilePath(userId).listFiles();
+ if (files != null) {
+ for (File child : files) {
+ if (child.isDirectory()) {
+ actual.add(child.getName());
+ }
+ }
+ }
+
+ assertEquals(expected, actual);
+ }
+
+ private void assertBitmapFiles(int userId, String packageName, String... expectedFiles) {
+ final Set<String> expected = hashSet(set(expectedFiles));
+
+ final Set<String> actual = new HashSet<>();
+
+ final File[] files = new File(mService.getUserBitmapFilePath(userId), packageName)
+ .listFiles();
+ if (files != null) {
+ for (File child : files) {
+ if (child.isFile()) {
+ actual.add(child.getName());
+ }
+ }
+ }
+
+ assertEquals(expected, actual);
+ }
+
+ private String getBitmapFilename(int userId, String packageName, String shortcutId) {
+ final ShortcutInfo si = mService.getPackageShortcutForTest(packageName, shortcutId, userId);
+ if (si == null) {
+ return null;
+ }
+ return new File(si.getBitmapPath()).getName();
+ }
+
+ 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);
+ }
+
+ private File makeFile(File baseDirectory, String... paths) {
+ File ret = baseDirectory;
+
+ for (String path : paths) {
+ ret = new File(ret, path);
+ }
+
+ return ret;
+ }
+
+ public void testCleanupDanglingBitmaps() throws Exception {
+ assertBitmapDirectories(USER_0, EMPTY_STRINGS);
+ assertBitmapDirectories(USER_10, EMPTY_STRINGS);
+
+ // Make some shortcuts with bitmap icons.
+ final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.black_32x32));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ mManager.setDynamicShortcuts(list(
+ makeShortcutWithIcon("s1", bmp32x32),
+ makeShortcutWithIcon("s2", bmp32x32),
+ makeShortcutWithIcon("s3", bmp32x32)
+ ));
+ });
+
+ // Increment the time (which actually we don't have to), which is used for filenames.
+ mInjectedCurrentTimeLillis++;
+
+ runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
+ mManager.setDynamicShortcuts(list(
+ makeShortcutWithIcon("s4", bmp32x32),
+ makeShortcutWithIcon("s5", bmp32x32),
+ makeShortcutWithIcon("s6", bmp32x32)
+ ));
+ });
+
+ // Increment the time, which is used for filenames.
+ mInjectedCurrentTimeLillis++;
+
+ runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
+ mManager.setDynamicShortcuts(list(
+ ));
+ });
+
+ // For USER-10, let's try without updating the times.
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ mManager.setDynamicShortcuts(list(
+ makeShortcutWithIcon("10s1", bmp32x32),
+ makeShortcutWithIcon("10s2", bmp32x32),
+ makeShortcutWithIcon("10s3", bmp32x32)
+ ));
+ });
+ runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
+ mManager.setDynamicShortcuts(list(
+ makeShortcutWithIcon("10s4", bmp32x32),
+ makeShortcutWithIcon("10s5", bmp32x32),
+ makeShortcutWithIcon("10s6", bmp32x32)
+ ));
+ });
+ runWithCaller(CALLING_PACKAGE_3, USER_10, () -> {
+ mManager.setDynamicShortcuts(list(
+ ));
+ });
+
+ dumpsysOnLogcat();
+
+ // Check files and directories.
+ // Package 3 has no bitmaps, so we don't create a directory.
+ assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
+ assertBitmapDirectories(USER_10, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
+
+ assertBitmapFiles(USER_0, CALLING_PACKAGE_1,
+ getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s1"),
+ getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s2"),
+ getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s3")
+ );
+ assertBitmapFiles(USER_0, CALLING_PACKAGE_2,
+ getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s4"),
+ getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s5"),
+ getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s6")
+ );
+ assertBitmapFiles(USER_0, CALLING_PACKAGE_3,
+ EMPTY_STRINGS
+ );
+ assertBitmapFiles(USER_10, CALLING_PACKAGE_1,
+ getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s1"),
+ getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s2"),
+ getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s3")
+ );
+ assertBitmapFiles(USER_10, CALLING_PACKAGE_2,
+ getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s4"),
+ getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s5"),
+ getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s6")
+ );
+ assertBitmapFiles(USER_10, CALLING_PACKAGE_3,
+ EMPTY_STRINGS
+ );
+
+ // Then create random directories and files.
+ makeFile(mService.getUserBitmapFilePath(USER_0), "a.b.c").mkdir();
+ makeFile(mService.getUserBitmapFilePath(USER_0), "d.e.f").mkdir();
+ makeFile(mService.getUserBitmapFilePath(USER_0), "d.e.f", "123").createNewFile();
+ makeFile(mService.getUserBitmapFilePath(USER_0), "d.e.f", "456").createNewFile();
+
+ makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_3).mkdir();
+
+ makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_1, "1").createNewFile();
+ makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_1, "2").createNewFile();
+ makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_1, "3").createNewFile();
+ makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_1, "4").createNewFile();
+
+ makeFile(mService.getUserBitmapFilePath(USER_10), "10a.b.c").mkdir();
+ makeFile(mService.getUserBitmapFilePath(USER_10), "10d.e.f").mkdir();
+ makeFile(mService.getUserBitmapFilePath(USER_10), "10d.e.f", "123").createNewFile();
+ makeFile(mService.getUserBitmapFilePath(USER_10), "10d.e.f", "456").createNewFile();
+
+ makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "1").createNewFile();
+ makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "2").createNewFile();
+ makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "3").createNewFile();
+ makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "4").createNewFile();
+
+ assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3,
+ "a.b.c", "d.e.f");
+
+ // Save and load. When a user is loaded, we do the cleanup.
+ mService.saveDirtyInfo();
+ initService();
+
+ mService.handleUnlockUser(USER_0);
+ mService.handleUnlockUser(USER_10);
+ mService.handleUnlockUser(20); // Make sure the logic will still work for nonexistent user.
+
+ // The below check is the same as above, except this time USER_0 use the CALLING_PACKAGE_3
+ // directory.
+
+ assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3);
+ assertBitmapDirectories(USER_10, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
+
+ assertBitmapFiles(USER_0, CALLING_PACKAGE_1,
+ getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s1"),
+ getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s2"),
+ getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s3")
+ );
+ assertBitmapFiles(USER_0, CALLING_PACKAGE_2,
+ getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s4"),
+ getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s5"),
+ getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s6")
+ );
+ assertBitmapFiles(USER_0, CALLING_PACKAGE_3,
+ EMPTY_STRINGS
+ );
+ assertBitmapFiles(USER_10, CALLING_PACKAGE_1,
+ getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s1"),
+ getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s2"),
+ getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s3")
+ );
+ assertBitmapFiles(USER_10, CALLING_PACKAGE_2,
+ getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s4"),
+ getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s5"),
+ getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s6")
+ );
+ assertBitmapFiles(USER_10, CALLING_PACKAGE_3,
+ EMPTY_STRINGS
+ );
+ }
+
+ 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 = makeShortcutWithTimestampWithActivity("s3", 3000,
+ makeComponent(ShortcutActivity2.class));
+ final ShortcutInfo s2_4 = makeShortcutWithTimestampWithActivity("s4", 500,
+ makeComponent(ShortcutActivity.class));
+ 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(assertAllStringsResolved(
+ 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"))));
+
+ // Filter by activity
+ assertAllDynamic(assertAllHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+ assertAllNotKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+ /* time =*/ 0, CALLING_PACKAGE_2,
+ new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
+ ShortcutQuery.FLAG_GET_PINNED | ShortcutQuery.FLAG_GET_DYNAMIC),
+ getCallingUser())),
+ "s4"))));
+
+ // 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.
+ }
+
+ public void testGetShortcuts_resolveStrings() throws Exception {
+ doAnswer(pmInvocation -> {
+ assertEquals(Process.SYSTEM_UID, mInjectedCallingUid);
+
+ final String packageName = (String) pmInvocation.getArguments()[0];
+ final int userId = (Integer) pmInvocation.getArguments()[1];
+
+ final Resources res = mock(Resources.class);
+ doAnswer(resInvocation -> {
+ final int resId = (Integer) resInvocation.getArguments()[0];
+
+ return "string-" + packageName + "-user:" + userId + "-res:" + resId;
+ }).when(res).getString(anyInt());
+ return res;
+ }).when(mMockPackageManager).getResourcesForApplicationAsUser(anyString(), anyInt());
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(mClientContext)
+ .setId("id")
+ .setActivityComponent(new ComponentName(mClientContext, "dummy"))
+ .setTitleResId(10)
+ .setTextResId(11)
+ .setDisabledMessageResId(12)
+ .setIntent(makeIntent("action", ShortcutActivity.class))
+ .build();
+ mManager.setDynamicShortcuts(list(si));
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+ ShortcutInfo si = new ShortcutInfo.Builder(mClientContext)
+ .setId("id")
+ .setActivityComponent(new ComponentName(mClientContext, "dummy"))
+ .setTitleResId(10)
+ .setTextResId(11)
+ .setDisabledMessageResId(12)
+ .setIntent(makeIntent("action", ShortcutActivity.class))
+ .build();
+ mManager.setDynamicShortcuts(list(si));
+ });
+
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ final ShortcutQuery q = new ShortcutQuery();
+ q.setQueryFlags(ShortcutQuery.FLAG_GET_DYNAMIC);
+
+ // USER 0
+ List<ShortcutInfo> ret = assertShortcutIds(
+ assertAllStringsResolved(mLauncherApps.getShortcuts(q, HANDLE_USER_0)),
+ "id");
+ assertEquals("string-com.android.test.1-user:0-res:10", ret.get(0).getTitle());
+ assertEquals("string-com.android.test.1-user:0-res:11", ret.get(0).getText());
+ assertEquals("string-com.android.test.1-user:0-res:12",
+ ret.get(0).getDisabledMessage());
+
+ // USER P0
+ ret = assertShortcutIds(
+ assertAllStringsResolved(mLauncherApps.getShortcuts(q, HANDLE_USER_P0)),
+ "id");
+ assertEquals("string-com.android.test.1-user:20-res:10", ret.get(0).getTitle());
+ assertEquals("string-com.android.test.1-user:20-res:11", ret.get(0).getText());
+ assertEquals("string-com.android.test.1-user:20-res:12",
+ ret.get(0).getDisabledMessage());
+ });
+ }
+
+ // TODO resource
+ 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.
+ doAnswer(new AnswerIsUserRunning(false)).when(mMockUserManager).isUserRunning(anyInt());
+ doAnswer(new AnswerIsUserRunning(true)).when(mMockUserManager).isUserRunning(eq(USER_P0));
+
+ 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.
+
+ doAnswer(new AnswerIsUserRunning(false)).when(mMockUserManager).isUserRunning(anyInt());
+ doAnswer(new AnswerIsUserRunning(true)).when(mMockUserManager).isUserRunning(eq(USER_10));
+
+ 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).setDefaultLauncherComponent(
+ 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)
+ .getDefaultLauncherComponent().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).getDefaultLauncherComponent());
+
+ // 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;
+
+ doAnswer(new AnswerIsUserRunning(true)).when(mMockUserManager).isUserRunning(eq(USER_10));
+
+ 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();
+
+ // onSystemLocaleChangedNoLock before boot completed will be ignored.
+ mInternal.onSystemLocaleChangedNoLock();
+ assertEquals(origSequenceNumber, mService.getLocaleChangeSequenceNumber());
+
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ 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(
+ NullPointerException.class,
+ "Intent action must be set",
+ () -> new ShortcutInfo.Builder(getTestContext()).setIntent(new Intent()));
+ assertExpectException(
+ NullPointerException.class,
+ "activityComponent must be provided",
+ () -> new ShortcutInfo.Builder(getTestContext()).setId("id").build()
+ .enforceMandatoryFields());
+ assertExpectException(
+ IllegalArgumentException.class,
+ "title must be provided",
+ () -> new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setActivityComponent(
+ new ComponentName(getTestContext().getPackageName(), "s"))
+ .build()
+ .enforceMandatoryFields());
+ assertExpectException(
+ NullPointerException.class,
+ "Intent must be provided",
+ () -> new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setActivityComponent(
+ new ComponentName(getTestContext().getPackageName(), "s"))
+ .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")
+ .setDisabledMessage("dismes")
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+ .setRank(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("dismes", si.getDisabledMessage());
+ assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getRank());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertEquals("abc", si.getBitmapPath());
+ assertEquals(456, si.getIconResourceId());
+ }
+
+ public void testShortcutInfoParcel_resId() {
+ setCaller(CALLING_PACKAGE_1, USER_10);
+ ShortcutInfo si;
+
+ 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))
+ .setTitleResId(10)
+ .setTextResId(11)
+ .setDisabledMessageResId(12)
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+ .setRank(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(10, si.getTitleResId());
+ assertEquals(11, si.getTextResId());
+ assertEquals(12, si.getDisabledMessageResId());
+ assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getRank());
+ 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")
+ .setDisabledMessage("dismes")
+ .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(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("dismes", si.getDisabledMessage());
+ assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getRank());
+ 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("dismes", si.getDisabledMessage());
+ assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getRank());
+ 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("dismes", si.getDisabledMessage());
+ assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+ assertEquals(null, si.getIntent());
+ assertEquals(123, si.getRank());
+ 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.getDisabledMessage());
+ assertEquals(null, si.getCategories());
+ assertEquals(null, si.getIntent());
+ assertEquals(0, si.getRank());
+ 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_resId() {
+ 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))
+ .setTitleResId(10)
+ .setTextResId(11)
+ .setDisabledMessageResId(12)
+ .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(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(10, si.getTitleResId());
+ assertEquals(11, si.getTextResId());
+ assertEquals(12, si.getDisabledMessageResId());
+ assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getRank());
+ 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(10, si.getTitleResId());
+ assertEquals(11, si.getTextResId());
+ assertEquals(12, si.getDisabledMessageResId());
+ assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getRank());
+ 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(10, si.getTitleResId());
+ assertEquals(11, si.getTextResId());
+ assertEquals(12, si.getDisabledMessageResId());
+ assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+ assertEquals(null, si.getIntent());
+ assertEquals(123, si.getRank());
+ 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(0, si.getTitleResId());
+ assertEquals(0, si.getTextResId());
+ assertEquals(0, si.getDisabledMessageResId());
+ assertEquals(null, si.getCategories());
+ assertEquals(null, si.getIntent());
+ assertEquals(0, si.getRank());
+ 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")
+ .setDisabledMessage("dismes")
+ .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(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());
+ assertEquals(0, si.getTitleResId());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setTitleResId(123).build());
+ assertEquals("text", si.getText());
+ assertEquals(null, si.getTitle());
+ assertEquals(123, si.getTitleResId());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setText("xxx").build());
+ assertEquals(123, si.getRank());
+ assertEquals("xxx", si.getText());
+ assertEquals(0, si.getTextResId());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setTextResId(1111).build());
+ assertEquals(123, si.getRank());
+ assertEquals(null, si.getText());
+ assertEquals(1111, si.getTextResId());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setDisabledMessage("xxx").build());
+ assertEquals(123, si.getRank());
+ assertEquals("xxx", si.getDisabledMessage());
+ assertEquals(0, si.getDisabledMessageResId());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setDisabledMessageResId(11111).build());
+ assertEquals(123, si.getRank());
+ assertEquals(null, si.getDisabledMessage());
+ assertEquals(11111, si.getDisabledMessageResId());
+
+ 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")
+ .setRank(999).build());
+ assertEquals("text", si.getText());
+ assertEquals(999, si.getRank());
+
+
+ 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 testShortcutInfoCopyNonNullFieldsFrom_resId() 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))
+ .setTitleResId(10)
+ .setTextResId(11)
+ .setDisabledMessageResId(12)
+ .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(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(11, si.getTextResId());
+ 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(11, si.getTextResId());
+ assertEquals(456, si.getIcon().getResId());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setTitle("xyz").build());
+ assertEquals(11, si.getTextResId());
+ assertEquals("xyz", si.getTitle());
+ assertEquals(0, si.getTitleResId());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setTitleResId(123).build());
+ assertEquals(11, si.getTextResId());
+ assertEquals(null, si.getTitle());
+ assertEquals(123, si.getTitleResId());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setText("xxx").build());
+ assertEquals(123, si.getRank());
+ assertEquals("xxx", si.getText());
+ assertEquals(0, si.getTextResId());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setTextResId(1111).build());
+ assertEquals(123, si.getRank());
+ assertEquals(null, si.getText());
+ assertEquals(1111, si.getTextResId());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setDisabledMessage("xxx").build());
+ assertEquals(123, si.getRank());
+ assertEquals("xxx", si.getDisabledMessage());
+ assertEquals(0, si.getDisabledMessageResId());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setDisabledMessageResId(11111).build());
+ assertEquals(123, si.getRank());
+ assertEquals(null, si.getDisabledMessage());
+ assertEquals(11111, si.getDisabledMessageResId());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setCategories(set()).build());
+ assertEquals(11, si.getTextResId());
+ assertEquals(set(), si.getCategories());
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setCategories(set("x")).build());
+ assertEquals(11, si.getTextResId());
+ 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(11, si.getTextResId());
+ 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(11, si.getTextResId());
+ assertEquals("action3", si.getIntent().getAction());
+ assertEquals("x", si.getIntent().getStringExtra("key"));
+
+ si = sorig.clone(/* flags=*/ 0);
+ si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
+ .setRank(999).build());
+ assertEquals(11, si.getTextResId());
+ assertEquals(999, si.getRank());
+
+
+ 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(11, si.getTextResId());
+ 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")
+ .setDisabledMessage("dismes")
+ .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(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("dismes", si.getDisabledMessage());
+ assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getRank());
+ 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_resId() 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)
+ .setTitleResId(10)
+ .setTextResId(11)
+ .setDisabledMessageResId(12)
+ .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(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(10, si.getTitleResId());
+ assertEquals(11, si.getTextResId());
+ assertEquals(12, si.getDisabledMessageResId());
+ assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getRank());
+ 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")
+ .setDisabledMessage("dismes")
+ .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(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("dismes", si.getDisabledMessage());
+ assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getRank());
+ assertEquals(1, si.getExtras().getInt("k"));
+
+ assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
+ assertNull(si.getBitmapPath()); // No icon.
+ assertEquals(0, si.getIconResourceId());
+ }
+
+ public void testShortcutInfoSaveAndLoad_forBackup_resId() {
+ 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)
+ .setTitleResId(10)
+ .setTextResId(11)
+ .setDisabledMessageResId(12)
+ .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"))
+ .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
+ .setRank(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(10, si.getTitleResId());
+ assertEquals(11, si.getTextResId());
+ assertEquals(12, si.getDisabledMessageResId());
+ assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
+ assertEquals("action", si.getIntent().getAction());
+ assertEquals("val", si.getIntent().getStringExtra("key"));
+ assertEquals(123, si.getRank());
+ 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
new file mode 100644
index 0000000..701e058
--- /dev/null
+++ b/services/tests/shortcutmanagerutils/Android.mk
@@ -0,0 +1,31 @@
+# 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
new file mode 100644
index 0000000..c23f9e6
--- /dev/null
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -0,0 +1,524 @@
+/*
+ * 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 List<ShortcutInfo> assertAllStringsResolved(
+ List<ShortcutInfo> actualShortcuts) {
+ for (ShortcutInfo s : actualShortcuts) {
+ assertTrue("ID " + s.getId(), s.hasStringResourcesResolved());
+ }
+ 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/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 7d7e1eb..1ccacc2 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -23,6 +23,7 @@
import java.lang.String;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -104,7 +105,6 @@
* An {@link InCallService} will only see this state if it has the
* {@link TelecomManager#METADATA_INCLUDE_EXTERNAL_CALLS} metadata set to {@code true} in its
* manifest.
- * @hide
*/
public static final int STATE_PULLING_CALL = 11;
@@ -252,7 +252,6 @@
* <p>
* See {@link Connection#CAPABILITY_CAN_PULL_CALL} and
* {@link Connection#PROPERTY_IS_EXTERNAL_CALL}.
- * @hide
*/
public static final int CAPABILITY_CAN_PULL_CALL = 0x00800000;
@@ -305,7 +304,6 @@
* in its manifest.
* <p>
* See {@link Connection#PROPERTY_IS_EXTERNAL_CALL}.
- * @hide
*/
public static final int PROPERTY_IS_EXTERNAL_CALL = 0x00000040;
@@ -786,7 +784,6 @@
* @param call The {@code Call} receiving the event.
* @param event The event.
* @param extras Extras associated with the connection event.
- * @hide
*/
public void onConnectionEvent(Call call, String event, Bundle extras) {}
}
@@ -965,7 +962,6 @@
* An {@link InCallService} will only see calls which support this method if it has the
* {@link TelecomManager#METADATA_INCLUDE_EXTERNAL_CALLS} metadata set to {@code true}
* in its manifest.
- * @hide
*/
public void pullExternalCall() {
// If this isn't an external call, ignore the request.
@@ -988,7 +984,6 @@
*
* @param event The connection event.
* @param extras Bundle containing extra information associated with the event.
- * @hide
*/
public void sendCallEvent(String event, Bundle extras) {
mInCallAdapter.sendCallEvent(mTelecomCallId, event, extras);
@@ -1002,7 +997,6 @@
* extras. Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
*
* @param extras The extras to add.
- * @hide
*/
public final void putExtras(Bundle extras) {
if (extras == null) {
@@ -1032,7 +1026,7 @@
}
/**
- * Adds an integer extra to this {@code Connection}.
+ * Adds an integer extra to this {@link Call}.
*
* @param key The extra key.
* @param value The value.
@@ -1047,7 +1041,7 @@
}
/**
- * Adds a string extra to this {@code Connection}.
+ * Adds a string extra to this {@link Call}.
*
* @param key The extra key.
* @param value The value.
@@ -1062,10 +1056,9 @@
}
/**
- * Removes extras from this {@code Connection}.
+ * Removes extras from this {@link Call}.
*
* @param keys The keys of the extras to remove.
- * @hide
*/
public final void removeExtras(List<String> keys) {
if (mExtras != null) {
@@ -1080,6 +1073,15 @@
}
/**
+ * Removes extras from this {@link Call}.
+ *
+ * @param keys The keys of the extras to remove.
+ */
+ public final void removeExtras(String ... keys) {
+ removeExtras(Arrays.asList(keys));
+ }
+
+ /**
* Obtains the parent of this {@code Call} in a conference, if any.
*
* @return The parent {@code Call}, or {@code null} if this {@code Call} is not a
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 9fcbfe3..be04c90 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -24,6 +24,7 @@
import android.util.ArraySet;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -163,7 +164,6 @@
* {@link Connection} for valid values.
*
* @return A bitmask of the properties of the conference call.
- * @hide
*/
public final int getConnectionProperties() {
return mConnectionProperties;
@@ -395,7 +395,6 @@
* {@link Connection} for valid values.
*
* @param connectionProperties A bitmask of the {@code Properties} of the conference call.
- * @hide
*/
public final void setConnectionProperties(int connectionProperties) {
if (connectionProperties != mConnectionProperties) {
@@ -684,6 +683,8 @@
* Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
*
* @param extras The extras associated with this {@code Conference}.
+ * @deprecated Use {@link #putExtras(Bundle)} to add extras. Use {@link #removeExtras(List)}
+ * to remove extras.
*/
public final void setExtras(@Nullable Bundle extras) {
// Add/replace any new or changed extras values.
@@ -723,7 +724,6 @@
* Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
*
* @param extras The extras to add.
- * @hide
*/
public final void putExtras(@NonNull Bundle extras) {
if (extras == null) {
@@ -780,10 +780,9 @@
}
/**
- * Removes an extra from this {@link Conference}.
+ * Removes extras from this {@link Conference}.
*
- * @param keys The key of the extra key to remove.
- * @hide
+ * @param keys The keys of the extras to remove.
*/
public final void removeExtras(List<String> keys) {
if (keys == null || keys.isEmpty()) {
@@ -805,7 +804,25 @@
}
/**
+ * Removes extras from this {@link Conference}.
+ *
+ * @param keys The keys of the extras to remove.
+ */
+ public final void removeExtras(String ... keys) {
+ removeExtras(Arrays.asList(keys));
+ }
+
+ /**
* Returns the extras associated with this conference.
+ * <p>
+ * Extras should be updated using {@link #putExtras(Bundle)} and {@link #removeExtras(List)}.
+ * <p>
+ * Telecom or an {@link InCallService} can also update the extras via
+ * {@link android.telecom.Call#putExtras(Bundle)}, and
+ * {@link Call#removeExtras(List)}.
+ * <p>
+ * The conference is notified of changes to the extras made by Telecom or an
+ * {@link InCallService} by {@link #onExtrasChanged(Bundle)}.
*
* @return The extras associated with this connection.
*/
@@ -822,7 +839,6 @@
* {@link Call#removeExtras(List)}.
*
* @param extras The new extras bundle.
- * @hide
*/
public void onExtrasChanged(Bundle extras) {}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index ef314f3..a5e3c461 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -35,6 +35,7 @@
import android.view.Surface;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -100,7 +101,6 @@
* <p>
* A connection can only be in this state if the {@link #PROPERTY_IS_EXTERNAL_CALL} property and
* {@link #CAPABILITY_CAN_PULL_CALL} capability bits are set on the connection.
- * @hide
*/
public static final int STATE_PULLING_CALL = 7;
@@ -284,7 +284,6 @@
* <p>
* Should only be set on a {@code Connection} where {@link #PROPERTY_IS_EXTERNAL_CALL}
* is set.
- * @hide
*/
public static final int CAPABILITY_CAN_PULL_CALL = 0x01000000;
@@ -332,7 +331,6 @@
* external connections. Only those {@link InCallService}s which have the
* {@link TelecomManager#METADATA_INCLUDE_EXTERNAL_CALLS} metadata set to {@code true} in its
* manifest will see external connections.
- * @hide
*/
public static final int PROPERTY_IS_EXTERNAL_CALL = 1<<4;
@@ -391,7 +389,6 @@
* {@link Call.Details#PROPERTY_IS_EXTERNAL_CALL} and
* {@link Call.Details#CAPABILITY_CAN_PULL_CALL}, but the {@link ConnectionService} could not
* pull the external call due to an error condition.
- * @hide
*/
public static final String EVENT_CALL_PULL_FAILED = "android.telecom.event.CALL_PULL_FAILED";
@@ -510,13 +507,6 @@
return builder.toString();
}
- /**
- * Builds a string representation of a properties bit-mask.
- *
- * @param properties The properties bit-mask.
- * @return String representation.
- * @hide
- */
public static String propertiesToString(int properties) {
StringBuilder builder = new StringBuilder();
builder.append("[Properties:");
@@ -1384,6 +1374,15 @@
/**
* Returns the extras associated with this connection.
+ * <p>
+ * Extras should be updated using {@link #putExtras(Bundle)}.
+ * <p>
+ * Telecom or an {@link InCallService} can also update the extras via
+ * {@link android.telecom.Call#putExtras(Bundle)}, and
+ * {@link Call#removeExtras(List)}.
+ * <p>
+ * The connection is notified of changes to the extras made by Telecom or an
+ * {@link InCallService} by {@link #onExtrasChanged(Bundle)}.
*
* @return The extras associated with this connection.
*/
@@ -1486,7 +1485,6 @@
/**
* Returns the connection's properties, as a bit mask of the {@code PROPERTY_*} constants.
- * @hide
*/
public final int getConnectionProperties() {
return mConnectionProperties;
@@ -1692,7 +1690,6 @@
* Sets the connection's properties as a bit mask of the {@code PROPERTY_*} constants.
*
* @param connectionProperties The new connection properties.
- * @hide
*/
public final void setConnectionProperties(int connectionProperties) {
checkImmutable();
@@ -1877,6 +1874,8 @@
* Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
*
* @param extras The extras associated with this {@code Connection}.
+ * @deprecated Use {@link #putExtras(Bundle)} to add extras. Use {@link #removeExtras(List)}
+ * to remove extras.
*/
public final void setExtras(@Nullable Bundle extras) {
checkImmutable();
@@ -1917,7 +1916,6 @@
* Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
*
* @param extras The extras to add.
- * @hide
*/
public final void putExtras(@NonNull Bundle extras) {
checkImmutable();
@@ -1975,10 +1973,9 @@
}
/**
- * Removes an extra from this {@code Connection}.
+ * Removes extras from this {@code Connection}.
*
- * @param keys The key of the extra key to remove.
- * @hide
+ * @param keys The keys of the extras to remove.
*/
public final void removeExtras(List<String> keys) {
if (mExtras != null) {
@@ -1997,6 +1994,15 @@
}
/**
+ * Removes extras from this {@code Connection}.
+ *
+ * @param keys The keys of the extras to remove.
+ */
+ public final void removeExtras(String ... keys) {
+ removeExtras(Arrays.asList(keys));
+ }
+
+ /**
* Notifies this Connection that the {@link #getAudioState()} property has a new value.
*
* @param state The new connection audio state.
@@ -2118,7 +2124,6 @@
* capability and {@link Connection#PROPERTY_IS_EXTERNAL_CALL} property bits must be set.
* <p>
* For more information on external calls, see {@link Connection#PROPERTY_IS_EXTERNAL_CALL}.
- * @hide
*/
public void onPullExternalCall() {}
@@ -2131,7 +2136,6 @@
*
* @param event The call event.
* @param extras Extras associated with the call event.
- * @hide
*/
public void onCallEvent(String event, Bundle extras) {}
@@ -2144,7 +2148,6 @@
* {@link Call#removeExtras(List)}.
*
* @param extras The new extras bundle.
- * @hide
*/
public void onExtrasChanged(Bundle extras) {}
@@ -2323,7 +2326,6 @@
*
* @param event The connection event.
* @param extras Bundle containing extra information associated with the event.
- * @hide
*/
public void sendConnectionEvent(String event, Bundle extras) {
for (Listener l : mListeners) {
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 65437d9..cf73d4f 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -67,13 +67,11 @@
/**
* Disconnected because the user did not locally answer the incoming call, but it was answered
* on another device where the call was ringing.
- * @hide
*/
public static final int ANSWERED_ELSEWHERE = 11;
/**
* Disconnected because the call was pulled from the current device to another device.
- * @hide
*/
public static final int CALL_PULLED = 12;
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index e2399ff..df6715d 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -457,7 +457,6 @@
* @param call The call the event is associated with.
* @param event The event.
* @param extras Any associated extras.
- * @hide
*/
public void onConnectionEvent(Call call, String event, Bundle extras) {
}
diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java
index bf6038a..943da6d 100644
--- a/telecomm/java/android/telecom/RemoteConference.java
+++ b/telecomm/java/android/telecom/RemoteConference.java
@@ -97,7 +97,6 @@
*
* @param conference The {@code RemoteConference} invoking this method.
* @param connectionProperties The new properties of the {@code RemoteConference}.
- * @hide
*/
public void onConnectionPropertiesChanged(
RemoteConference conference,
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index 8e06659db..dc8eaf6 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -95,7 +95,6 @@
*
* @param connection The {@code RemoteConnection} invoking this method.
* @param connectionProperties The new properties of the {@code RemoteConnection}.
- * @hide
*/
public void onConnectionPropertiesChanged(
RemoteConnection connection,
@@ -230,7 +229,6 @@
* @param connection The {@code RemoteConnection} invoking this method.
* @param event The connection event.
* @param extras Extras associated with the event.
- * @hide
*/
public void onConnectionEvent(RemoteConnection connection, String event, Bundle extras) {}
}
@@ -738,7 +736,6 @@
*
* @return A bitmask of the properties of the {@code RemoteConnection}, as defined in the
* {@code PROPERTY_*} constants in class {@link Connection}.
- * @hide
*/
public int getConnectionProperties() {
return mConnectionProperties;
@@ -993,7 +990,6 @@
* Instructs this {@link RemoteConnection} to pull itself to the local device.
* <p>
* See {@link Call#pullExternalCall()} for more information.
- * @hide
*/
public void pullExternalCall() {
try {
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index da0d048..ff5daaf 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -348,7 +348,6 @@
* informed of external calls should set this meta-data to {@code true} in the manifest
* registration of their {@link InCallService}. By default, the {@link InCallService} will NOT
* be informed of external calls.
- * @hide
*/
public static final String METADATA_INCLUDE_EXTERNAL_CALLS =
"android.telecom.INCLUDE_EXTERNAL_CALLS";
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 78efd05..b7701c6 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -530,6 +530,17 @@
public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
/**
+ * Determines whether video conference calls are supported by a carrier. When {@code true},
+ * video calls can be merged into conference calls, {@code false} otherwiwse.
+ * <p>
+ * Note: even if video conference calls are not supported, audio calls may be merged into a
+ * conference if {@link #KEY_SUPPORT_CONFERENCE_CALL_BOOL} is {@code true}.
+ * @hide
+ */
+ public static final String KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL =
+ "support_video_conference_call_bool";
+
+ /**
* Determine whether user can toggle Enhanced 4G LTE Mode in Settings.
*/
public static final String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
@@ -582,6 +593,14 @@
public static final String KEY_WFC_DATA_SPN_FORMAT_IDX_INT = "wfc_data_spn_format_idx_int";
/**
+ * The Component Name of the activity that can setup the emergency addrees for WiFi Calling
+ * as per carrier requirement.
+ * @hide
+ */
+ public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING =
+ "wfc_emergency_address_carrier_app_string";
+
+ /**
* If this is true, the SIM card (through Customer Service Profile EF file) will be able to
* prevent manual operator selection. If false, this SIM setting will be ignored and manual
* operator selection will always be available. See CPHS4_2.WW6, CPHS B.4.7.1 for more
@@ -601,6 +620,13 @@
public static final String KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL =
"broadcast_emergency_call_state_changes_bool";
+ /**
+ * Cell broadcast additional channels enbled by the carrier
+ * @hide
+ */
+ public static final String KEY_CARRIER_ADDITIONAL_CBS_CHANNELS_STRINGS =
+ "carrier_additional_cbs_channels_strings";
+
// These variables are used by the MMS service and exposed through another API, {@link
// SmsManager}. The variable names and string values are copied from there.
public static final String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
@@ -776,6 +802,7 @@
sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0);
sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100);
sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
+ sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false);
sDefaults.putBoolean(KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false);
sDefaults.putBoolean(KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false);
@@ -784,6 +811,7 @@
sDefaults.putStringArray(KEY_WFC_OPERATOR_ERROR_CODES_STRING_ARRAY, null);
sDefaults.putInt(KEY_WFC_SPN_FORMAT_IDX_INT, 0);
sDefaults.putInt(KEY_WFC_DATA_SPN_FORMAT_IDX_INT, 0);
+ sDefaults.putString(KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING, "");
sDefaults.putBoolean(KEY_CONFIG_WIFI_DISABLE_IN_ECBM, false);
// MMS defaults
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 9eb1304..7a9170f 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -194,6 +194,12 @@
*/
public static final int VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED = 50;
+ /**
+ * The call was terminated because it was pulled to another device.
+ * {@hide}
+ */
+ public static final int CALL_PULLED = 51;
+
//*********************************************************************************************
// When adding a disconnect type:
// 1) Please assign the new type the next id value below.
@@ -318,7 +324,9 @@
case CDMA_ALREADY_ACTIVATED:
return "CDMA_ALREADY_ACTIVATED";
case VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED:
- return "VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED";
+ return "VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED";
+ case CALL_PULLED:
+ return "CALL_PULLED";
default:
return "INVALID: " + cause;
}
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 3c30251..6b2ae3e 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -30,6 +30,7 @@
import android.os.SystemProperties;
import android.provider.Contacts;
import android.provider.ContactsContract;
+import android.telecom.PhoneAccount;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
@@ -2599,6 +2600,48 @@
}
/**
+ * Given a {@link Uri} with a {@code sip} scheme, attempts to build an equivalent {@code tel}
+ * scheme {@link Uri}. If the source {@link Uri} does not contain a valid number, or is not
+ * using the {@code sip} scheme, the original {@link Uri} is returned.
+ *
+ * @param source The {@link Uri} to convert.
+ * @return The equivalent {@code tel} scheme {@link Uri}.
+ *
+ * @hide
+ */
+ public static Uri convertSipUriToTelUri(Uri source) {
+ // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers
+ // Per RFC3261, the "user" can be a telephone number.
+ // For example: sip:1650555121;phone-context=blah.com@host.com
+ // In this case, the phone number is in the user field of the URI, and the parameters can be
+ // ignored.
+ //
+ // A SIP URI can also specify a phone number in a format similar to:
+ // sip:+1-212-555-1212@something.com;user=phone
+ // In this case, the phone number is again in user field and the parameters can be ignored.
+ // We can get the user field in these instances by splitting the string on the @, ;, or :
+ // and looking at the first found item.
+
+ String scheme = source.getScheme();
+
+ if (!PhoneAccount.SCHEME_SIP.equals(scheme)) {
+ // Not a sip URI, bail.
+ return source;
+ }
+
+ String number = source.getSchemeSpecificPart();
+ String numberParts[] = number.split("[@;:]");
+
+ if (numberParts.length == 0) {
+ // Number not found, bail.
+ return source;
+ }
+ number = numberParts[0];
+
+ return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
+ }
+
+ /**
* This function handles the plus code conversion
* If the number format is
* 1)+1NANP,remove +,
diff --git a/telephony/java/android/telephony/TelephonyHistogram.aidl b/telephony/java/android/telephony/TelephonyHistogram.aidl
new file mode 100644
index 0000000..8de81cf
--- /dev/null
+++ b/telephony/java/android/telephony/TelephonyHistogram.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony;
+/**
+ * @hide
+ */
+parcelable TelephonyHistogram;
diff --git a/telephony/java/android/telephony/TelephonyHistogram.java b/telephony/java/android/telephony/TelephonyHistogram.java
new file mode 100644
index 0000000..985102c
--- /dev/null
+++ b/telephony/java/android/telephony/TelephonyHistogram.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Parcelable class to store Telephony histogram.
+ * @hide
+ */
+@SystemApi
+public final class TelephonyHistogram implements Parcelable {
+ // Type of Telephony histogram Eg: RIL histogram will have all timing data associated with
+ // RIL calls. Similarly we can have any other Telephony histogram.
+ private final int category;
+
+ // Unique Id identifying a sample within particular category of histogram
+ private final int id;
+
+ // Min time taken in ms
+ private int minTimeMs;
+
+ // Max time taken in ms
+ private int maxTimeMs;
+
+ // Average time taken in ms
+ private int averageTimeMs;
+
+ // Total count of samples
+ private int sampleCount;
+
+ // Array storing time taken for first #RANGE_CALCULATION_COUNT samples of histogram.
+ private int[] initialTimings;
+
+ // Total number of time ranges expected (must be greater than 1)
+ private final int bucketCount;
+
+ // Array storing endpoints of range buckets. Calculated based on values of minTime & maxTime
+ // after totalTimeCount is #RANGE_CALCULATION_COUNT.
+ private final int[] bucketEndPoints;
+
+ // Array storing counts for each time range starting from smallest value range
+ private final int[] bucketCounters;
+
+ /**
+ * Constant for Telephony category
+ */
+ public static final int TELEPHONY_CATEGORY_RIL = 1;
+
+ // Count of Histogram samples after which time buckets are created.
+ private static final int RANGE_CALCULATION_COUNT = 10;
+
+
+ // Constant used to indicate #initialTimings is null while parceling
+ private static final int ABSENT = 0;
+
+ // Constant used to indicate #initialTimings is not null while parceling
+ private static final int PRESENT = 1;
+
+ // Throws exception if #totalBuckets is not greater than one.
+ public TelephonyHistogram (int category, int id, int bucketCount) {
+ if (bucketCount <= 1) {
+ throw new IllegalArgumentException("Invalid number of buckets");
+ }
+ this.category = category;
+ this.id = id;
+ this.minTimeMs = Integer.MAX_VALUE;
+ this.maxTimeMs = 0;
+ this.averageTimeMs = 0;
+ this.sampleCount = 0;
+ initialTimings = new int[RANGE_CALCULATION_COUNT];
+ this.bucketCount = bucketCount;
+ bucketEndPoints = new int[bucketCount - 1];
+ bucketCounters = new int[bucketCount];
+ }
+
+ public TelephonyHistogram(TelephonyHistogram th) {
+ category = th.getCategory();
+ id = th.getId();
+ minTimeMs = th.getMinTime();
+ maxTimeMs = th.getMaxTime();
+ averageTimeMs = th.getAverageTime();
+ sampleCount = th.getSampleCount();
+ initialTimings = th.getInitialTimings();
+ bucketCount = th.getBucketCount();
+ bucketEndPoints = th.getBucketEndPoints();
+ bucketCounters = th.getBucketCounters();
+ }
+
+ public int getCategory() {
+ return category;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public int getMinTime() {
+ return minTimeMs;
+ }
+
+ public int getMaxTime() {
+ return maxTimeMs;
+ }
+
+ public int getAverageTime() {
+ return averageTimeMs;
+ }
+
+ public int getSampleCount () {
+ return sampleCount;
+ }
+
+ private int[] getInitialTimings() {
+ return initialTimings;
+ }
+
+ public int getBucketCount() {
+ return bucketCount;
+ }
+
+ public int[] getBucketEndPoints() {
+ return getDeepCopyOfArray(bucketEndPoints);
+ }
+
+ public int[] getBucketCounters() {
+ return getDeepCopyOfArray(bucketCounters);
+ }
+
+ private int[] getDeepCopyOfArray(int[] array) {
+ int[] clone = new int[array.length];
+ System.arraycopy(array, 0, clone, 0, array.length);
+ return clone;
+ }
+
+ private void addToBucketCounter(int time) {
+ int i;
+ for (i = 0; i < bucketEndPoints.length; i++) {
+ if (time <= bucketEndPoints[i]) {
+ bucketCounters[i]++;
+ return;
+ }
+ }
+ bucketCounters[i]++;
+ }
+
+ // Add new value of time taken
+ // This function updates minTime, maxTime, averageTime & totalTimeCount every time it is
+ // called. initialTimings[] is updated if totalTimeCount <= #RANGE_CALCULATION_COUNT. When
+ // totalTimeCount = RANGE_CALCULATION_COUNT, based on the min, max time & the number of buckets
+ // expected, bucketEndPoints[] would be calculated. Then bucketCounters[] would be filled up
+ // using values stored in initialTimings[]. Thereafter bucketCounters[] will always be updated.
+ public void addTimeTaken(int time) {
+ // Initialize all fields if its first entry or if integer overflow is going to occur while
+ // trying to calculate averageTime
+ if (sampleCount == 0 || (sampleCount == Integer.MAX_VALUE)) {
+ if (sampleCount == 0) {
+ minTimeMs = time;
+ maxTimeMs = time;
+ averageTimeMs = time;
+ } else {
+ initialTimings = new int[RANGE_CALCULATION_COUNT];
+ }
+ sampleCount = 1;
+ Arrays.fill(initialTimings, 0);
+ initialTimings[0] = time;
+ Arrays.fill(bucketEndPoints, 0);
+ Arrays.fill(bucketCounters, 0);
+ } else {
+ if (time < minTimeMs) {
+ minTimeMs = time;
+ }
+ if (time > maxTimeMs) {
+ maxTimeMs = time;
+ }
+ long totalTime = ((long)averageTimeMs) * sampleCount + time;
+ averageTimeMs = (int)(totalTime/++sampleCount);
+
+ if (sampleCount < RANGE_CALCULATION_COUNT) {
+ initialTimings[sampleCount - 1] = time;
+ } else if (sampleCount == RANGE_CALCULATION_COUNT) {
+ initialTimings[sampleCount - 1] = time;
+
+ // Calculate bucket endpoints based on bucketCount expected
+ for (int i = 1; i < bucketCount; i++) {
+ int endPt = minTimeMs + (i * (maxTimeMs - minTimeMs)) / bucketCount;
+ bucketEndPoints[i - 1] = endPt;
+ }
+
+ // Use values stored in initialTimings[] to update bucketCounters
+ for (int j = 0; j < RANGE_CALCULATION_COUNT; j++) {
+ addToBucketCounter(initialTimings[j]);
+ }
+ initialTimings = null;
+ } else {
+ addToBucketCounter(time);
+ }
+
+ }
+ }
+
+ public String toString() {
+ String basic = " Histogram id = " + id + " Time(ms): min = " + minTimeMs + " max = "
+ + maxTimeMs + " avg = " + averageTimeMs + " Count = " + sampleCount;
+ if (sampleCount < RANGE_CALCULATION_COUNT) {
+ return basic;
+ } else {
+ StringBuffer intervals = new StringBuffer(" Interval Endpoints:");
+ for (int i = 0; i < bucketEndPoints.length; i++) {
+ intervals.append(" " + bucketEndPoints[i]);
+ }
+ intervals.append(" Interval counters:");
+ for (int i = 0; i < bucketCounters.length; i++) {
+ intervals.append(" " + bucketCounters[i]);
+ }
+ return basic + intervals;
+ }
+ }
+
+ public static final Parcelable.Creator<TelephonyHistogram> CREATOR =
+ new Parcelable.Creator<TelephonyHistogram> () {
+
+ @Override
+ public TelephonyHistogram createFromParcel(Parcel in) {
+ return new TelephonyHistogram(in);
+ }
+
+ @Override
+ public TelephonyHistogram[] newArray(int size) {
+ return new TelephonyHistogram[size];
+ }
+ };
+
+ public TelephonyHistogram(Parcel in) {
+ category = in.readInt();
+ id = in.readInt();
+ minTimeMs = in.readInt();
+ maxTimeMs = in.readInt();
+ averageTimeMs = in.readInt();
+ sampleCount = in.readInt();
+ if (in.readInt() == PRESENT) {
+ initialTimings = new int[RANGE_CALCULATION_COUNT];
+ in.readIntArray(initialTimings);
+ }
+ bucketCount = in.readInt();
+ bucketEndPoints = new int[bucketCount - 1];
+ in.readIntArray(bucketEndPoints);
+ bucketCounters = new int[bucketCount];
+ in.readIntArray(bucketCounters);
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(category);
+ out.writeInt(id);
+ out.writeInt(minTimeMs);
+ out.writeInt(maxTimeMs);
+ out.writeInt(averageTimeMs);
+ out.writeLong(sampleCount);
+ if (initialTimings == null) {
+ out.writeInt(ABSENT);
+ } else {
+ out.writeInt(PRESENT);
+ out.writeIntArray(initialTimings);
+ }
+ out.writeInt(bucketCount);
+ out.writeIntArray(bucketEndPoints);
+ out.writeIntArray(bucketCounters);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 94f6a23..d474d99 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -34,8 +34,10 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.service.carrier.CarrierIdentifier;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
+import android.telephony.TelephonyHistogram;
import android.util.Log;
import com.android.internal.telecom.ITelecomService;
@@ -50,6 +52,7 @@
import java.io.FileInputStream;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
@@ -720,6 +723,21 @@
*/
public static final String VVM_TYPE_CVVM = "vvm_type_cvvm";
+ /* Visual voicemail SMS filter constants */
+
+ /**
+ * The visual voicemail SMS message does not have to be a data SMS, and can be directed to any
+ * port.
+ * @hide
+ */
+ public static final int VVM_SMS_FILTER_DESTINATION_PORT_ANY = -1;
+
+ /**
+ * The visual voicemail SMS message can be directed to any port, but must be a data SMS.
+ * @hide
+ */
+ public static final int VVM_SMS_FILTER_DESTINATION_PORT_DATA_SMS = -2;
+
//
//
// Device Info
@@ -1879,7 +1897,7 @@
return getSimOperatorNumericForPhone(phoneId);
}
- /**
+ /**
* Returns the MCC+MNC (mobile country code + mobile network code) of the
* provider of the SIM for a particular subscription. 5 or 6 decimal digits.
* <p>
@@ -2421,6 +2439,181 @@
}
/**
+ * Enables or disables the visual voicemail SMS filter for a phone account. When the filter is
+ * enabled, Incoming SMS messages matching the OMTP VVM SMS interface will be redirected to the
+ * visual voicemail client with
+ * {@link android.provider.VoicemailContract.ACTION_VOICEMAIL_SMS_RECEIVED}.
+ * @see #setVisualVoicemailSmsFilterPrefix(int, String)
+ * @see #setVisualVoicemailSmsFilterOriginatingNumbers(int, String[])
+ * @see #setVisualVoicemailSmsFilterDestinationPort(int, int)
+ *
+ * <p>This takes effect only when the caller is the default dialer.
+ *
+ * @param subId The subscription id of the phone account.
+ * @param value The new state of the filter
+ */
+ /** @hide */
+ public void setVisualVoicemailSmsFilterEnabled(int subId, boolean value){
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null)
+ telephony.setVisualVoicemailSmsFilterEnabled(subId, value);
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ }
+
+ /**
+ * Returns whether the visual voicemail SMS filter is enabled for a phone account.
+ *
+ * @param packageName The visual voicemail client to read the settings from
+ * @param subId The subscription id of the phone account.
+ */
+ /** @hide */
+ public boolean isVisualVoicemailSmsFilterEnabled(String packageName, int subId){
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.isVisualVoicemailSmsFilterEnabled(packageName, subId);
+ }
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+
+ return false;
+ }
+
+ /**
+ * Sets the client prefix for the visual voicemail SMS filter of a phone account. The client
+ * prefix will appear at the start of a visual voicemail SMS message, followed by a colon(:).
+ *
+ * <p>This takes effect only when the caller is the default dialer.
+ *
+ * @param subId The subscription id of the phone account.
+ * @param prefix The client prefix
+ */
+ /** @hide */
+ public void setVisualVoicemailSmsFilterClientPrefix(int subId, String prefix){
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.setVisualVoicemailSmsFilterClientPrefix(subId, prefix);
+ }
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ }
+
+ /**
+ * Returns the client prefix for the visual voicemail SMS filter of a phone account. The client
+ * prefix will appear at the start of a visual voicemail SMS message, followed by a colon(:).
+ *
+ * @param packageName The visual voicemail client to read the settings from
+ * @param subId The subscription id of the phone account.
+ */
+ /** @hide */
+ public String getVisualVoicemailSmsFilterClientPrefix(String packageName, int subId){
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getVisualVoicemailSmsFilterClientPrefix(packageName, subId);
+ }
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ return null;
+ }
+
+ /**
+ * Sets the originating number whitelist for the visual voicemail SMS filter of a phone
+ * account. If the list is not null only the SMS messages from a number in the list can be
+ * considered as a visual voicemail SMS. Otherwise, messages from any address will be
+ * considered.
+ *
+ * <p>This takes effect only when the caller is the default dialer.
+ *
+ * @param subId The subscription id of the phone account.
+ * @param numbers A array representing the white list, or null to disable number filtering.
+ */
+ /** @hide */
+ public void setVisualVoicemailSmsFilterOriginatingNumbers(int subId,
+ @Nullable String[] numbers) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.setVisualVoicemailSmsFilterOriginatingNumbers(subId, numbers);
+ }
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ }
+
+ /**
+ * Returns the originating number whitelist for the visual voicemail SMS filter of a phone
+ * account. If the list is not null only the SMS messages from a number in the list can be
+ * considered as a visual voicemail SMS. Otherwise, messages from any address will be
+ * considered.
+ *
+ * @param packageName The visual voicemail client to read the settings from
+ * @param subId The subscription id of the phone account.
+ */
+ /** @hide */
+ public String[] getVisualVoicemailSmsFilterOriginatingNumbers(String packageName, int subId){
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getVisualVoicemailSmsFilterOriginatingNumbers(packageName, subId);
+ }
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ return null;
+ }
+
+ /**
+ * Sets the destination port for the visual voicemail SMS filter of a phone
+ * account.
+ *
+ * <p>This takes effect only when the caller is the default dialer.
+ *
+ * @param subId The subscription id of the phone account.
+ * @param port The destination port, or {@link #VVM_SMS_FILTER_DESTINATION_PORT_ANY}, or
+ * {@link #VVM_SMS_FILTER_DESTINATION_PORT_DATA_SMS}
+ */
+ /** @hide */
+ public void setVisualVoicemailSmsFilterDestinationPort(int subId, int port){
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.setVisualVoicemailSmsFilterDestinationPort(subId, port);
+ }
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ }
+
+ /**
+ * Returns the destination port for the visual voicemail SMS filter of a phone
+ * account.
+ *
+ * @param packageName The visual voicemail client to read the settings from
+ * @param subId The subscription id of the phone account.
+ * @returns port The destination port, or {@link #VVM_SMS_FILTER_DESTINATION_PORT_ANY}, or
+ * {@link #VVM_SMS_FILTER_DESTINATION_PORT_DATA_SMS}
+ */
+ /** @hide */
+ public int getVisualVoicemailSmsFilterDestinationPort(String packageName, int subId){
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getVisualVoicemailSmsFilterDestinationPort(packageName, subId);
+ }
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ return VVM_SMS_FILTER_DESTINATION_PORT_ANY;
+ }
+ /**
* Returns the voice mail count. Return 0 if unavailable, -1 if there are unread voice messages
* but the count is unknown.
* <p>
@@ -5178,4 +5371,133 @@
}
return false;
}
+
+ /**
+ * Return the application ID for the app type like {@link APPTYPE_CSIM}.
+ *
+ * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
+ *
+ * @param appType the uicc app type like {@link APPTYPE_CSIM}
+ * @return Application ID for specificied app type or null if no uicc or error.
+ * @hide
+ */
+ public String getAidForAppType(int appType) {
+ return getAidForAppType(getDefaultSubscription(), appType);
+ }
+
+ /**
+ * Return the application ID for the app type like {@link APPTYPE_CSIM}.
+ *
+ * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
+ *
+ * @param subId the subscription ID that this request applies to.
+ * @param appType the uicc app type, like {@link APPTYPE_CSIM}
+ * @return Application ID for specificied app type or null if no uicc or error.
+ * @hide
+ */
+ public String getAidForAppType(int subId, int appType) {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.getAidForAppType(subId, appType);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#getAidForAppType", e);
+ }
+ return null;
+ }
+
+ /**
+ * Return the Electronic Serial Number.
+ *
+ * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
+ *
+ * @return ESN or null if error.
+ * @hide
+ */
+ public String getEsn() {
+ return getEsn(getDefaultSubscription());
+ }
+
+ /**
+ * Return the Electronic Serial Number.
+ *
+ * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
+ *
+ * @param subId the subscription ID that this request applies to.
+ * @return ESN or null if error.
+ * @hide
+ */
+ public String getEsn(int subId) {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.getEsn(subId);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#getEsn", e);
+ }
+ return null;
+ }
+
+ /*
+ * Get snapshot of Telephony histograms
+ * @return List of Telephony histograms
+ * Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ * Or the calling app has carrier privileges.
+ * @hide
+ */
+ @SystemApi
+ public List<TelephonyHistogram> getTelephonyHistograms() {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.getTelephonyHistograms();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#getTelephonyHistograms", e);
+ }
+ return null;
+ }
+
+ /**
+ * Set the allowed carrier list for slotId
+ * Require system privileges. In the future we may add this to carrier APIs.
+ *
+ * @return The number of carriers set successfully. Should be length of
+ * carrierList on success; -1 on error.
+ * @hide
+ */
+ public int setAllowedCarriers(int slotId, List<CarrierIdentifier> carriers) {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.setAllowedCarriers(slotId, carriers);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#setAllowedCarriers", e);
+ }
+ return -1;
+ }
+
+ /**
+ * Get the allowed carrier list for slotId.
+ * Require system privileges. In the future we may add this to carrier APIs.
+ *
+ * @return List of {@link android.telephony.CarrierIdentifier}; empty list
+ * means all carriers are allowed.
+ * @hide
+ */
+ public List<CarrierIdentifier> getAllowedCarriers(int slotId) {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.getAllowedCarriers(slotId);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#getAllowedCarriers", e);
+ }
+ return new ArrayList<CarrierIdentifier>(0);
+ }
}
diff --git a/telephony/java/com/android/ims/internal/IImsService.aidl b/telephony/java/com/android/ims/internal/IImsService.aidl
index a9614a6..406d22d 100644
--- a/telephony/java/com/android/ims/internal/IImsService.aidl
+++ b/telephony/java/com/android/ims/internal/IImsService.aidl
@@ -38,8 +38,19 @@
void close(int serviceId);
boolean isConnected(int serviceId, int serviceType, int callType);
boolean isOpened(int serviceId);
+
+ /**
+ * Replace existing registration listener
+ *
+ */
void setRegistrationListener(int serviceId, in IImsRegistrationListener listener);
+ /**
+ * Add new registration listener
+ */
+ void addRegistrationListener(int phoneId, int serviceClass,
+ in IImsRegistrationListener listener);
+
ImsCallProfile createCallProfile(int serviceId, int serviceType, int callType);
IImsCallSession createCallSession(int serviceId, in ImsCallProfile profile,
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index bb8aaad5..74b6e14 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -20,6 +20,7 @@
import android.os.Bundle;
import android.os.ResultReceiver;
import android.net.Uri;
+import android.service.carrier.CarrierIdentifier;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telephony.CellInfo;
@@ -28,8 +29,10 @@
import android.telephony.NeighboringCellInfo;
import android.telephony.RadioAccessFamily;
import android.telephony.ServiceState;
+import android.telephony.TelephonyHistogram;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.OperatorInfo;
+
import java.util.List;
@@ -450,6 +453,26 @@
*/
int getVoiceMessageCountForSubscriber(int subId);
+ // Not oneway, caller needs to make sure the vaule is set before receiving a SMS
+ void setVisualVoicemailSmsFilterEnabled(int subId, boolean value);
+
+ boolean isVisualVoicemailSmsFilterEnabled(String packageName, int subId);
+
+ // Not oneway, caller needs to make sure the vaule is set before receiving a SMS
+ void setVisualVoicemailSmsFilterClientPrefix(int subId, String prefix);
+
+ String getVisualVoicemailSmsFilterClientPrefix(String packageName, int subId);
+
+ // Not oneway, caller needs to make sure the vaule is set before receiving a SMS
+ void setVisualVoicemailSmsFilterOriginatingNumbers(int subId, in String[] numbers);
+
+ String[] getVisualVoicemailSmsFilterOriginatingNumbers(String packageName, int subId);
+
+ // Not oneway, caller needs to make sure the vaule is set before receiving a SMS
+ void setVisualVoicemailSmsFilterDestinationPort(int subId, int port);
+
+ int getVisualVoicemailSmsFilterDestinationPort(String packageName, int subId);
+
/**
* Returns the network type for data transmission
* Legacy call, permission-free
@@ -1067,4 +1090,51 @@
* Returns a list of packages that have carrier privileges.
*/
List<String> getPackagesWithCarrierPrivileges();
+
+ /**
+ * Return the application ID for the app type.
+ *
+ * @param subId the subscription ID that this request applies to.
+ * @param appType the uicc app type,
+ * @return Application ID for specificied app type or null if no uicc or error.
+ */
+ String getAidForAppType(int subId, int appType);
+
+ /**
+ * Return the Electronic Serial Number.
+ *
+ * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
+ *
+ * @param subId the subscription ID that this request applies to.
+ * @return ESN or null if error.
+ * @hide
+ */
+ String getEsn(int subId);
+
+ /**
+ * Get snapshot of Telephony histograms
+ * @return List of Telephony histograms
+ * Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ * Or the calling app has carrier privileges.
+ */
+ List<TelephonyHistogram> getTelephonyHistograms();
+
+ /**
+ * Set the allowed carrier list for slotId
+ * Require system privileges. In the future we may add this to carrier APIs.
+ *
+ * @return The number of carriers set successfully. Should match length of
+ * carriers on success.
+ */
+ int setAllowedCarriers(int slotId, in List<CarrierIdentifier> carriers);
+
+ /**
+ * Get the allowed carrier list for slotId.
+ * Require system privileges. In the future we may add this to carrier APIs.
+ *
+ * @return List of {@link android.service.carrier.CarrierIdentifier}; empty list
+ * means all carriers are allowed.
+ */
+ List<CarrierIdentifier> getAllowedCarriers(int slotId);
}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index cfc5305..6462654 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -409,6 +409,8 @@
int RIL_REQUEST_STOP_LCE = 133;
int RIL_REQUEST_PULL_LCEDATA = 134;
int RIL_REQUEST_GET_ACTIVITY_INFO = 135;
+ int RIL_REQUEST_SET_ALLOWED_CARRIERS = 136;
+ int RIL_REQUEST_GET_ALLOWED_CARRIERS = 137;
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 3a1e2bb..4b5ea65 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -115,6 +115,7 @@
toolSources := \
compile/Compile.cpp \
+ diff/Diff.cpp \
dump/Dump.cpp \
link/Link.cpp
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index a2fadd9..00d8aae 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -24,6 +24,7 @@
extern int compile(const std::vector<StringPiece>& args);
extern int link(const std::vector<StringPiece>& args);
extern int dump(const std::vector<StringPiece>& args);
+extern int diff(const std::vector<StringPiece>& args);
} // namespace aapt
@@ -44,12 +45,14 @@
return aapt::link(args);
} else if (command == "dump" || command == "d") {
return aapt::dump(args);
+ } else if (command == "diff") {
+ return aapt::diff(args);
}
std::cerr << "unknown command '" << command << "'\n";
} else {
std::cerr << "no command specified\n";
}
- std::cerr << "\nusage: aapt2 [compile|link|dump] ..." << std::endl;
+ std::cerr << "\nusage: aapt2 [compile|link|dump|diff] ..." << std::endl;
return 1;
}
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 9704d970..a84c306 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -152,7 +152,7 @@
break;
}
- spanStack.back().lastChar = builder.str().size();
+ spanStack.back().lastChar = builder.str().size() - 1;
outStyleString->spans.push_back(spanStack.back());
spanStack.pop_back();
@@ -1058,6 +1058,16 @@
std::unique_ptr<Array> array = util::make_unique<Array>();
+ bool translateable = mOptions.translatable;
+ if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) {
+ if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) {
+ mDiag->error(DiagMessage(outResource->source)
+ << "invalid value for 'translatable'. Must be a boolean");
+ return false;
+ }
+ }
+ array->setTranslateable(translateable);
+
bool error = false;
const size_t depth = parser->getDepth();
while (xml::XmlPullParser::nextChildNode(parser, depth)) {
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 8d734f3..e700ed9 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -189,6 +189,17 @@
return results;
}
+std::vector<ResourceConfigValue*> ResourceEntry::findValuesIf(
+ const std::function<bool(ResourceConfigValue*)>& f) {
+ std::vector<ResourceConfigValue*> results;
+ for (auto& configValue : values) {
+ if (f(configValue.get())) {
+ results.push_back(configValue.get());
+ }
+ }
+ return results;
+}
+
/**
* The default handler for collisions. A return value of -1 means keep the
* existing value, 0 means fail, and +1 means take the incoming value.
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 7f5c2b8..5690ea6 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -26,6 +26,7 @@
#include "io/File.h"
#include <android-base/macros.h>
+#include <functional>
#include <map>
#include <memory>
#include <string>
@@ -109,6 +110,9 @@
ResourceConfigValue* findOrCreateValue(const ConfigDescription& config,
const StringPiece& product);
std::vector<ResourceConfigValue*> findAllValues(const ConfigDescription& config);
+ std::vector<ResourceConfigValue*> findValuesIf(
+ const std::function<bool(ResourceConfigValue*)>& f);
+
private:
DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 74c48b0..a0a7efc 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -289,7 +289,7 @@
std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr,
const StringPiece16& str) {
android::Res_value flags = { };
- flags.dataType = android::Res_value::TYPE_INT_DEC;
+ flags.dataType = android::Res_value::TYPE_INT_HEX;
flags.data = 0u;
if (util::trimWhitespace(str).empty()) {
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index dd7ff01..c10b134 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -39,6 +39,14 @@
RawString::RawString(const StringPool::Ref& ref) : value(ref) {
}
+bool RawString::equals(const Value* value) const {
+ const RawString* other = valueCast<RawString>(value);
+ if (!other) {
+ return false;
+ }
+ return *this->value == *other->value;
+}
+
RawString* RawString::clone(StringPool* newPool) const {
RawString* rs = new RawString(newPool->makeRef(*value));
rs->mComment = mComment;
@@ -66,6 +74,15 @@
Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) {
}
+bool Reference::equals(const Value* value) const {
+ const Reference* other = valueCast<Reference>(value);
+ if (!other) {
+ return false;
+ }
+ return referenceType == other->referenceType && privateReference == other->privateReference &&
+ id == other->id && name == other->name;
+}
+
bool Reference::flatten(android::Res_value* outValue) const {
outValue->dataType = (referenceType == Reference::Type::kResource) ?
android::Res_value::TYPE_REFERENCE : android::Res_value::TYPE_ATTRIBUTE;
@@ -97,6 +114,10 @@
}
}
+bool Id::equals(const Value* value) const {
+ return valueCast<Id>(value) != nullptr;
+}
+
bool Id::flatten(android::Res_value* out) const {
out->dataType = android::Res_value::TYPE_INT_BOOLEAN;
out->data = util::hostToDevice32(0);
@@ -111,15 +132,15 @@
*out << "(id)";
}
-String::String(const StringPool::Ref& ref) : value(ref), mTranslateable(true) {
+String::String(const StringPool::Ref& ref) : value(ref) {
}
-void String::setTranslateable(bool val) {
- mTranslateable = val;
-}
-
-bool String::isTranslateable() const {
- return mTranslateable;
+bool String::equals(const Value* value) const {
+ const String* other = valueCast<String>(value);
+ if (!other) {
+ return false;
+ }
+ return *this->value == *other->value;
}
bool String::flatten(android::Res_value* outValue) const {
@@ -144,15 +165,24 @@
*out << "(string) \"" << *value << "\"";
}
-StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref), mTranslateable(true) {
+StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {
}
-void StyledString::setTranslateable(bool val) {
- mTranslateable = val;
-}
+bool StyledString::equals(const Value* value) const {
+ const StyledString* other = valueCast<StyledString>(value);
+ if (!other) {
+ return false;
+ }
-bool StyledString::isTranslateable() const {
- return mTranslateable;
+ if (*this->value->str == *other->value->str) {
+ const std::vector<StringPool::Span>& spansA = this->value->spans;
+ const std::vector<StringPool::Span>& spansB = other->value->spans;
+ return std::equal(spansA.begin(), spansA.end(), spansB.begin(),
+ [](const StringPool::Span& a, const StringPool::Span& b) -> bool {
+ return *a.name == *b.name && a.firstChar == b.firstChar && a.lastChar == b.lastChar;
+ });
+ }
+ return false;
}
bool StyledString::flatten(android::Res_value* outValue) const {
@@ -174,11 +204,22 @@
void StyledString::print(std::ostream* out) const {
*out << "(styled string) \"" << *value->str << "\"";
+ for (const StringPool::Span& span : value->spans) {
+ *out << " "<< *span.name << ":" << span.firstChar << "," << span.lastChar;
+ }
}
FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
}
+bool FileReference::equals(const Value* value) const {
+ const FileReference* other = valueCast<FileReference>(value);
+ if (!other) {
+ return false;
+ }
+ return *path == *other->path;
+}
+
bool FileReference::flatten(android::Res_value* outValue) const {
if (path.getIndex() > std::numeric_limits<uint32_t>::max()) {
return false;
@@ -209,6 +250,14 @@
value.data = data;
}
+bool BinaryPrimitive::equals(const Value* value) const {
+ const BinaryPrimitive* other = valueCast<BinaryPrimitive>(value);
+ if (!other) {
+ return false;
+ }
+ return this->value.dataType == other->value.dataType && this->value.data == other->value.data;
+}
+
bool BinaryPrimitive::flatten(android::Res_value* outValue) const {
outValue->dataType = value.dataType;
outValue->data = util::hostToDevice32(value.data);
@@ -228,7 +277,7 @@
*out << "(integer) " << static_cast<int32_t>(value.data);
break;
case android::Res_value::TYPE_INT_HEX:
- *out << "(integer) " << std::hex << value.data << std::dec;
+ *out << "(integer) 0x" << std::hex << value.data << std::dec;
break;
case android::Res_value::TYPE_INT_BOOLEAN:
*out << "(boolean) " << (value.data != 0 ? "true" : "false");
@@ -253,6 +302,21 @@
mWeak = w;
}
+bool Attribute::equals(const Value* value) const {
+ const Attribute* other = valueCast<Attribute>(value);
+ if (!other) {
+ return false;
+ }
+
+ return this->typeMask == other->typeMask && this->minInt == other->minInt &&
+ this->maxInt == other->maxInt &&
+ std::equal(this->symbols.begin(), this->symbols.end(),
+ other->symbols.begin(),
+ [](const Symbol& a, const Symbol& b) -> bool {
+ return a.symbol.equals(&b.symbol) && a.value == b.value;
+ });
+}
+
Attribute* Attribute::clone(StringPool* /*newPool*/) const {
return new Attribute(*this);
}
@@ -365,6 +429,14 @@
<< "]";
}
+ if (minInt != std::numeric_limits<int32_t>::min()) {
+ *out << " min=" << minInt;
+ }
+
+ if (maxInt != std::numeric_limits<int32_t>::max()) {
+ *out << " max=" << maxInt;
+ }
+
if (isWeak()) {
*out << " [weak]";
}
@@ -445,6 +517,21 @@
return true;
}
+bool Style::equals(const Value* value) const {
+ const Style* other = valueCast<Style>(value);
+ if (!other) {
+ return false;
+ }
+ if (bool(parent) != bool(other->parent) ||
+ (parent && other->parent && !parent.value().equals(&other->parent.value()))) {
+ return false;
+ }
+ return std::equal(entries.begin(), entries.end(), other->entries.begin(),
+ [](const Entry& a, const Entry& b) -> bool {
+ return a.key.equals(&b.key) && a.value->equals(b.value.get());
+ });
+}
+
Style* Style::clone(StringPool* newPool) const {
Style* style = new Style();
style->parent = parent;
@@ -484,6 +571,18 @@
return out;
}
+bool Array::equals(const Value* value) const {
+ const Array* other = valueCast<Array>(value);
+ if (!other) {
+ return false;
+ }
+
+ return std::equal(items.begin(), items.end(), other->items.begin(),
+ [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool {
+ return a->equals(b.get());
+ });
+}
+
Array* Array::clone(StringPool* newPool) const {
Array* array = new Array();
array->mComment = mComment;
@@ -500,6 +599,21 @@
<< "]";
}
+bool Plural::equals(const Value* value) const {
+ const Plural* other = valueCast<Plural>(value);
+ if (!other) {
+ return false;
+ }
+
+ return std::equal(values.begin(), values.end(), other->values.begin(),
+ [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool {
+ if (bool(a) != bool(b)) {
+ return false;
+ }
+ return bool(a) == bool(b) || a->equals(b.get());
+ });
+}
+
Plural* Plural::clone(StringPool* newPool) const {
Plural* p = new Plural();
p->mComment = mComment;
@@ -515,12 +629,42 @@
void Plural::print(std::ostream* out) const {
*out << "(plural)";
+ if (values[Zero]) {
+ *out << " zero=" << *values[Zero];
+ }
+
+ if (values[One]) {
+ *out << " one=" << *values[One];
+ }
+
+ if (values[Two]) {
+ *out << " two=" << *values[Two];
+ }
+
+ if (values[Few]) {
+ *out << " few=" << *values[Few];
+ }
+
+ if (values[Many]) {
+ *out << " many=" << *values[Many];
+ }
}
static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) {
return out << *item;
}
+bool Styleable::equals(const Value* value) const {
+ const Styleable* other = valueCast<Styleable>(value);
+ if (!other) {
+ return false;
+ }
+ return std::equal(entries.begin(), entries.end(), other->entries.begin(),
+ [](const Reference& a, const Reference& b) -> bool {
+ return a.equals(&b);
+ });
+}
+
Styleable* Styleable::clone(StringPool* /*newPool*/) const {
return new Styleable(*this);
}
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 43354ac..aa1b550 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -54,6 +54,18 @@
mWeak = val;
}
+ // Whether the value is marked as translateable.
+ // This does not persist when flattened.
+ // It is only used during compilation phase.
+ void setTranslateable(bool val) {
+ mTranslateable = val;
+ }
+
+ // Default true.
+ bool isTranslateable() const {
+ return mTranslateable;
+ }
+
/**
* Returns the source where this value was defined.
*/
@@ -84,6 +96,8 @@
mComment = std::move(str);
}
+ virtual bool equals(const Value* value) const = 0;
+
/**
* Calls the appropriate overload of ValueVisitor.
*/
@@ -103,6 +117,7 @@
Source mSource;
std::u16string mComment;
bool mWeak = false;
+ bool mTranslateable = true;
};
/**
@@ -158,6 +173,7 @@
explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
explicit Reference(const ResourceId& i, Type type = Type::kResource);
+ bool equals(const Value* value) const override;
bool flatten(android::Res_value* outValue) const override;
Reference* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
@@ -168,6 +184,7 @@
*/
struct Id : public BaseItem<Id> {
Id() { mWeak = true; }
+ bool equals(const Value* value) const override;
bool flatten(android::Res_value* out) const override;
Id* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
@@ -183,6 +200,7 @@
RawString(const StringPool::Ref& ref);
+ bool equals(const Value* value) const override;
bool flatten(android::Res_value* outValue) const override;
RawString* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
@@ -193,17 +211,10 @@
String(const StringPool::Ref& ref);
- // Whether the string is marked as translateable. This does not persist when flattened.
- // It is only used during compilation phase.
- void setTranslateable(bool val);
- bool isTranslateable() const;
-
+ bool equals(const Value* value) const override;
bool flatten(android::Res_value* outValue) const override;
String* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
-
-private:
- bool mTranslateable;
};
struct StyledString : public BaseItem<StyledString> {
@@ -211,17 +222,10 @@
StyledString(const StringPool::StyleRef& ref);
- // Whether the string is marked as translateable. This does not persist when flattened.
- // It is only used during compilation phase.
- void setTranslateable(bool val);
- bool isTranslateable() const;
-
+ bool equals(const Value* value) const override;
bool flatten(android::Res_value* outValue) const override;
StyledString* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
-
-private:
- bool mTranslateable;
};
struct FileReference : public BaseItem<FileReference> {
@@ -235,6 +239,7 @@
FileReference() = default;
FileReference(const StringPool::Ref& path);
+ bool equals(const Value* value) const override;
bool flatten(android::Res_value* outValue) const override;
FileReference* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
@@ -250,6 +255,7 @@
BinaryPrimitive(const android::Res_value& val);
BinaryPrimitive(uint8_t dataType, uint32_t data);
+ bool equals(const Value* value) const override;
bool flatten(android::Res_value* outValue) const override;
BinaryPrimitive* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
@@ -268,6 +274,7 @@
Attribute(bool w, uint32_t t = 0u);
+ bool equals(const Value* value) const override;
Attribute* clone(StringPool* newPool) const override;
void printMask(std::ostream* out) const;
void print(std::ostream* out) const override;
@@ -290,6 +297,7 @@
std::vector<Entry> entries;
+ bool equals(const Value* value) const override;
Style* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
};
@@ -297,6 +305,7 @@
struct Array : public BaseValue<Array> {
std::vector<std::unique_ptr<Item>> items;
+ bool equals(const Value* value) const override;
Array* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
};
@@ -314,6 +323,7 @@
std::array<std::unique_ptr<Item>, Count> values;
+ bool equals(const Value* value) const override;
Plural* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
};
@@ -321,6 +331,7 @@
struct Styleable : public BaseValue<Styleable> {
std::vector<Reference> entries;
+ bool equals(const Value* value) const override;
Styleable* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
};
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
index ea2aa55..b8bc5db 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -127,6 +127,11 @@
}
};
+template <typename T>
+const T* valueCast(const Value* value) {
+ return valueCast<T>(const_cast<Value*>(value));
+}
+
/**
* Returns a valid pointer to T if the Value is of subtype T.
* Otherwise, returns nullptr.
@@ -141,7 +146,6 @@
return visitor.value;
}
-
inline void visitAllValuesInPackage(ResourceTablePackage* pkg, RawValueVisitor* visitor) {
for (auto& type : pkg->types) {
for (auto& entry : type->entries) {
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index 99c2077..d080e16 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -128,23 +128,6 @@
mPool(pool), mMethod(method), mLocalizer(method) {
}
- void visit(Array* array) override {
- std::unique_ptr<Array> localized = util::make_unique<Array>();
- localized->items.resize(array->items.size());
- for (size_t i = 0; i < array->items.size(); i++) {
- Visitor subVisitor(mPool, mMethod);
- array->items[i]->accept(&subVisitor);
- if (subVisitor.mItem) {
- localized->items[i] = std::move(subVisitor.mItem);
- } else {
- localized->items[i] = std::unique_ptr<Item>(array->items[i]->clone(mPool));
- }
- }
- localized->setSource(array->getSource());
- localized->setWeak(true);
- mValue = std::move(localized);
- }
-
void visit(Plural* plural) override {
std::unique_ptr<Plural> localized = util::make_unique<Plural>();
for (size_t i = 0; i < plural->values.size(); i++) {
@@ -164,10 +147,6 @@
}
void visit(String* string) override {
- if (!string->isTranslateable()) {
- return;
- }
-
std::u16string result = mLocalizer.start() + mLocalizer.text(*string->value) +
mLocalizer.end();
std::unique_ptr<String> localized = util::make_unique<String>(mPool->makeRef(result));
@@ -177,10 +156,6 @@
}
void visit(StyledString* string) override {
- if (!string->isTranslateable()) {
- return;
- }
-
mItem = pseudolocalizeStyledString(string, mMethod, mPool);
mItem->setWeak(true);
}
@@ -238,14 +213,26 @@
}
}
+/**
+ * A value is pseudolocalizable if it does not define a locale (or is the default locale)
+ * and is translateable.
+ */
+static bool isPseudolocalizable(ResourceConfigValue* configValue) {
+ const int diff = configValue->config.diff(ConfigDescription::defaultConfig());
+ if (diff & ConfigDescription::CONFIG_LOCALE) {
+ return false;
+ }
+ return configValue->value->isTranslateable();
+}
+
} // namespace
bool PseudolocaleGenerator::consume(IAaptContext* context, ResourceTable* table) {
for (auto& package : table->packages) {
for (auto& type : package->types) {
for (auto& entry : type->entries) {
- std::vector<ResourceConfigValue*> values = entry->findAllValues(
- ConfigDescription::defaultConfig());
+ std::vector<ResourceConfigValue*> values = entry->findValuesIf(isPseudolocalizable);
+
for (ResourceConfigValue* value : values) {
pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
&table->stringPool, entry.get());
diff --git a/tools/aapt2/diff/Diff.cpp b/tools/aapt2/diff/Diff.cpp
new file mode 100644
index 0000000..20b7b59
--- /dev/null
+++ b/tools/aapt2/diff/Diff.cpp
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Flags.h"
+#include "ResourceTable.h"
+#include "io/ZipArchive.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "unflatten/BinaryResourceParser.h"
+
+#include <android-base/macros.h>
+
+namespace aapt {
+
+class DiffContext : public IAaptContext {
+public:
+ const std::u16string& getCompilationPackage() override {
+ return mEmpty;
+ }
+
+ uint8_t getPackageId() override {
+ return 0x0;
+ }
+
+ IDiagnostics* getDiagnostics() override {
+ return &mDiagnostics;
+ }
+
+ NameMangler* getNameMangler() override {
+ return &mNameMangler;
+ }
+
+ SymbolTable* getExternalSymbols() override {
+ return &mSymbolTable;
+ }
+
+ bool verbose() override {
+ return false;
+ }
+
+private:
+ std::u16string mEmpty;
+ StdErrDiagnostics mDiagnostics;
+ NameMangler mNameMangler = NameMangler(NameManglerPolicy{});
+ SymbolTable mSymbolTable;
+};
+
+class LoadedApk {
+public:
+ LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
+ std::unique_ptr<ResourceTable> table) :
+ mSource(source), mApk(std::move(apk)), mTable(std::move(table)) {
+ }
+
+ io::IFileCollection* getFileCollection() {
+ return mApk.get();
+ }
+
+ ResourceTable* getResourceTable() {
+ return mTable.get();
+ }
+
+ const Source& getSource() {
+ return mSource;
+ }
+
+private:
+ Source mSource;
+ std::unique_ptr<io::IFileCollection> mApk;
+ std::unique_ptr<ResourceTable> mTable;
+
+ DISALLOW_COPY_AND_ASSIGN(LoadedApk);
+};
+
+static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context, const StringPiece& path) {
+ Source source(path);
+ std::string error;
+ std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::create(path, &error);
+ if (!apk) {
+ context->getDiagnostics()->error(DiagMessage(source) << error);
+ return {};
+ }
+
+ io::IFile* file = apk->findFile("resources.arsc");
+ if (!file) {
+ context->getDiagnostics()->error(DiagMessage(source) << "no resources.arsc found");
+ return {};
+ }
+
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ context->getDiagnostics()->error(DiagMessage(source) << "could not open resources.arsc");
+ return {};
+ }
+
+ std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+ BinaryResourceParser parser(context, table.get(), source, data->data(), data->size());
+ if (!parser.parse()) {
+ return {};
+ }
+
+ return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
+}
+
+static void emitDiffLine(const Source& source, const StringPiece& message) {
+ std::cerr << source << ": " << message << "\n";
+}
+
+static bool isSymbolVisibilityDifferent(const Symbol& symbolA, const Symbol& symbolB) {
+ return symbolA.state != symbolB.state;
+}
+
+template <typename Id>
+static bool isIdDiff(const Symbol& symbolA, const Maybe<Id>& idA,
+ const Symbol& symbolB, const Maybe<Id>& idB) {
+ if (symbolA.state == SymbolState::kPublic || symbolB.state == SymbolState::kPublic) {
+ return idA != idB;
+ }
+ return false;
+}
+
+static bool emitResourceConfigValueDiff(IAaptContext* context,
+ LoadedApk* apkA,
+ ResourceTablePackage* pkgA,
+ ResourceTableType* typeA,
+ ResourceEntry* entryA,
+ ResourceConfigValue* configValueA,
+ LoadedApk* apkB,
+ ResourceTablePackage* pkgB,
+ ResourceTableType* typeB,
+ ResourceEntry* entryB,
+ ResourceConfigValue* configValueB) {
+ Value* valueA = configValueA->value.get();
+ Value* valueB = configValueB->value.get();
+ if (!valueA->equals(valueB)) {
+ std::stringstream strStream;
+ strStream << "value " << pkgA->name << ":" << typeA->type << "/" << entryA->name
+ << " config=" << configValueA->config << " does not match:\n";
+ valueA->print(&strStream);
+ strStream << "\n vs \n";
+ valueB->print(&strStream);
+ emitDiffLine(apkB->getSource(), strStream.str());
+ return true;
+ }
+ return false;
+}
+
+static bool emitResourceEntryDiff(IAaptContext* context,
+ LoadedApk* apkA,
+ ResourceTablePackage* pkgA,
+ ResourceTableType* typeA,
+ ResourceEntry* entryA,
+ LoadedApk* apkB,
+ ResourceTablePackage* pkgB,
+ ResourceTableType* typeB,
+ ResourceEntry* entryB) {
+ bool diff = false;
+ for (std::unique_ptr<ResourceConfigValue>& configValueA : entryA->values) {
+ ResourceConfigValue* configValueB = entryB->findValue(configValueA->config);
+ if (!configValueB) {
+ std::stringstream strStream;
+ strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name
+ << " config=" << configValueA->config;
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ } else {
+ diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA,
+ configValueA.get(), apkB, pkgB, typeB, entryB,
+ configValueB);
+ }
+ }
+
+ // Check for any newly added config values.
+ for (std::unique_ptr<ResourceConfigValue>& configValueB : entryB->values) {
+ ResourceConfigValue* configValueA = entryA->findValue(configValueB->config);
+ if (!configValueA) {
+ std::stringstream strStream;
+ strStream << "new config " << pkgB->name << ":" << typeB->type << "/" << entryB->name
+ << " config=" << configValueB->config;
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ }
+ }
+ return false;
+}
+
+static bool emitResourceTypeDiff(IAaptContext* context,
+ LoadedApk* apkA,
+ ResourceTablePackage* pkgA,
+ ResourceTableType* typeA,
+ LoadedApk* apkB,
+ ResourceTablePackage* pkgB,
+ ResourceTableType* typeB) {
+ bool diff = false;
+ for (std::unique_ptr<ResourceEntry>& entryA : typeA->entries) {
+ ResourceEntry* entryB = typeB->findEntry(entryA->name);
+ if (!entryB) {
+ std::stringstream strStream;
+ strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name;
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ } else {
+ if (isSymbolVisibilityDifferent(entryA->symbolStatus, entryB->symbolStatus)) {
+ std::stringstream strStream;
+ strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
+ << " has different visibility (";
+ if (entryB->symbolStatus.state == SymbolState::kPublic) {
+ strStream << "PUBLIC";
+ } else {
+ strStream << "PRIVATE";
+ }
+ strStream << " vs ";
+ if (entryA->symbolStatus.state == SymbolState::kPublic) {
+ strStream << "PUBLIC";
+ } else {
+ strStream << "PRIVATE";
+ }
+ strStream << ")";
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ } else if (isIdDiff(entryA->symbolStatus, entryA->id,
+ entryB->symbolStatus, entryB->id)) {
+ std::stringstream strStream;
+ strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
+ << " has different public ID (";
+ if (entryB->id) {
+ strStream << "0x" << std::hex << entryB->id.value();
+ } else {
+ strStream << "none";
+ }
+ strStream << " vs ";
+ if (entryA->id) {
+ strStream << "0x " << std::hex << entryA->id.value();
+ } else {
+ strStream << "none";
+ }
+ strStream << ")";
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ }
+ diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(),
+ apkB, pkgB, typeB, entryB);
+ }
+ }
+
+ // Check for any newly added entries.
+ for (std::unique_ptr<ResourceEntry>& entryB : typeB->entries) {
+ ResourceEntry* entryA = typeA->findEntry(entryB->name);
+ if (!entryA) {
+ std::stringstream strStream;
+ strStream << "new entry " << pkgB->name << ":" << typeB->type << "/" << entryB->name;
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ }
+ }
+ return diff;
+}
+
+static bool emitResourcePackageDiff(IAaptContext* context, LoadedApk* apkA,
+ ResourceTablePackage* pkgA,
+ LoadedApk* apkB, ResourceTablePackage* pkgB) {
+ bool diff = false;
+ for (std::unique_ptr<ResourceTableType>& typeA : pkgA->types) {
+ ResourceTableType* typeB = pkgB->findType(typeA->type);
+ if (!typeB) {
+ std::stringstream strStream;
+ strStream << "missing " << pkgA->name << ":" << typeA->type;
+ emitDiffLine(apkA->getSource(), strStream.str());
+ diff = true;
+ } else {
+ if (isSymbolVisibilityDifferent(typeA->symbolStatus, typeB->symbolStatus)) {
+ std::stringstream strStream;
+ strStream << pkgA->name << ":" << typeA->type << " has different visibility (";
+ if (typeB->symbolStatus.state == SymbolState::kPublic) {
+ strStream << "PUBLIC";
+ } else {
+ strStream << "PRIVATE";
+ }
+ strStream << " vs ";
+ if (typeA->symbolStatus.state == SymbolState::kPublic) {
+ strStream << "PUBLIC";
+ } else {
+ strStream << "PRIVATE";
+ }
+ strStream << ")";
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ } else if (isIdDiff(typeA->symbolStatus, typeA->id, typeB->symbolStatus, typeB->id)) {
+ std::stringstream strStream;
+ strStream << pkgA->name << ":" << typeA->type << " has different public ID (";
+ if (typeB->id) {
+ strStream << "0x" << std::hex << typeB->id.value();
+ } else {
+ strStream << "none";
+ }
+ strStream << " vs ";
+ if (typeA->id) {
+ strStream << "0x " << std::hex << typeA->id.value();
+ } else {
+ strStream << "none";
+ }
+ strStream << ")";
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ }
+ diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB, typeB);
+ }
+ }
+
+ // Check for any newly added types.
+ for (std::unique_ptr<ResourceTableType>& typeB : pkgB->types) {
+ ResourceTableType* typeA = pkgA->findType(typeB->type);
+ if (!typeA) {
+ std::stringstream strStream;
+ strStream << "new type " << pkgB->name << ":" << typeB->type;
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ }
+ }
+ return diff;
+}
+
+static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA, LoadedApk* apkB) {
+ ResourceTable* tableA = apkA->getResourceTable();
+ ResourceTable* tableB = apkB->getResourceTable();
+
+ bool diff = false;
+ for (std::unique_ptr<ResourceTablePackage>& pkgA : tableA->packages) {
+ ResourceTablePackage* pkgB = tableB->findPackage(pkgA->name);
+ if (!pkgB) {
+ std::stringstream strStream;
+ strStream << "missing package " << pkgA->name;
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ } else {
+ if (pkgA->id != pkgB->id) {
+ std::stringstream strStream;
+ strStream << "package '" << pkgA->name << "' has different id (";
+ if (pkgB->id) {
+ strStream << "0x" << std::hex << pkgB->id.value();
+ } else {
+ strStream << "none";
+ }
+ strStream << " vs ";
+ if (pkgA->id) {
+ strStream << "0x" << std::hex << pkgA->id.value();
+ } else {
+ strStream << "none";
+ }
+ strStream << ")";
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ }
+ diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB);
+ }
+ }
+
+ // Check for any newly added packages.
+ for (std::unique_ptr<ResourceTablePackage>& pkgB : tableB->packages) {
+ ResourceTablePackage* pkgA = tableA->findPackage(pkgB->name);
+ if (!pkgA) {
+ std::stringstream strStream;
+ strStream << "new package " << pkgB->name;
+ emitDiffLine(apkB->getSource(), strStream.str());
+ diff = true;
+ }
+ }
+ return diff;
+}
+
+int diff(const std::vector<StringPiece>& args) {
+ DiffContext context;
+
+ Flags flags;
+ if (!flags.parse("aapt2 diff", args, &std::cerr)) {
+ return 1;
+ }
+
+ if (flags.getArgs().size() != 2u) {
+ std::cerr << "must have two apks as arguments.\n\n";
+ flags.usage("aapt2 diff", &std::cerr);
+ return 1;
+ }
+
+ std::unique_ptr<LoadedApk> apkA = loadApkFromPath(&context, flags.getArgs()[0]);
+ std::unique_ptr<LoadedApk> apkB = loadApkFromPath(&context, flags.getArgs()[1]);
+ if (!apkA || !apkB) {
+ return 1;
+ }
+
+ if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) {
+ // We emitted a diff, so return 1 (failure).
+ return 1;
+ }
+ return 0;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
index 82e4fb0..1ec48f0 100644
--- a/tools/aapt2/proto/TableProtoDeserializer.cpp
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -234,6 +234,8 @@
const pb::Attribute& pbAttr = pbCompoundValue.attr();
std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
attr->typeMask = pbAttr.format_flags();
+ attr->minInt = pbAttr.min_int();
+ attr->maxInt = pbAttr.max_int();
for (const pb::Attribute_Symbol& pbSymbol : pbAttr.symbols()) {
Attribute::Symbol symbol;
deserializeItemCommon(pbSymbol, &symbol.symbol);