diff options
156 files changed, 5095 insertions, 3184 deletions
diff --git a/Android.mk b/Android.mk index 137ef85dc594..51dfa575b77f 100644 --- a/Android.mk +++ b/Android.mk @@ -226,6 +226,7 @@ LOCAL_SRC_FILES += \ core/java/android/service/carrier/ICarrierMessagingService.aidl \ core/java/android/service/gatekeeper/IGateKeeperService.aidl \ core/java/android/service/notification/INotificationListener.aidl \ + core/java/android/service/notification/INotificationAssistant.aidl \ core/java/android/service/notification/IStatusBarNotificationHolder.aidl \ core/java/android/service/notification/IConditionListener.aidl \ core/java/android/service/notification/IConditionProvider.aidl \ @@ -308,7 +309,7 @@ LOCAL_SRC_FILES += \ core/java/com/android/internal/textservice/ISpellCheckerSessionListener.aidl \ core/java/com/android/internal/textservice/ITextServicesManager.aidl \ core/java/com/android/internal/textservice/ITextServicesSessionListener.aidl \ - core/java/com/android/internal/view/IDropPermissionHolder.aidl \ + core/java/com/android/internal/view/IDropPermissions.aidl \ core/java/com/android/internal/view/IInputContext.aidl \ core/java/com/android/internal/view/IInputContextCallback.aidl \ core/java/com/android/internal/view/IInputMethod.aidl \ diff --git a/api/current.txt b/api/current.txt index 557afcd6ab20..36430bb1e42d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5741,6 +5741,7 @@ package android.app.admin { method public java.lang.String[] getAccountTypesWithManagementDisabled(); method public java.util.List<android.content.ComponentName> getActiveAdmins(); method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String); + method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName); method public boolean getAutoTimeRequired(); method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName); method public boolean getCameraDisabled(android.content.ComponentName); @@ -5784,6 +5785,7 @@ package android.app.admin { method public boolean isActivePasswordSufficient(); method public boolean isAdminActive(android.content.ComponentName); method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String); + method public boolean isCallerApplicationRestrictionsManagingPackage(); method public boolean isDeviceOwnerApp(java.lang.String); method public boolean isLockTaskPermitted(java.lang.String); method public boolean isMasterVolumeMuted(android.content.ComponentName); @@ -5791,6 +5793,7 @@ package android.app.admin { method public boolean isProvisioningAllowed(java.lang.String); method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String); method public void lockNow(); + method public void reboot(android.content.ComponentName); method public void removeActiveAdmin(android.content.ComponentName); method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String); method public boolean removeKeyPair(android.content.ComponentName, java.lang.String); @@ -5799,6 +5802,7 @@ package android.app.admin { method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean); method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean); method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle); + method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String); method public void setAutoTimeRequired(android.content.ComponentName, boolean); method public void setBluetoothContactSharingDisabled(android.content.ComponentName, boolean); method public void setCameraDisabled(android.content.ComponentName, boolean); @@ -29310,6 +29314,7 @@ package android.provider { field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location"; field public static final int INCOMING_TYPE = 1; // 0x1 field public static final java.lang.String IS_READ = "is_read"; + field public static final java.lang.String LAST_MODIFIED = "last_modified"; field public static final java.lang.String LIMIT_PARAM_KEY = "limit"; field public static final int MISSED_TYPE = 3; // 0x3 field public static final java.lang.String NEW = "new"; @@ -30918,6 +30923,7 @@ package android.provider { field public static final java.lang.String ACTION_LOCATION_SOURCE_SETTINGS = "android.settings.LOCATION_SOURCE_SETTINGS"; field public static final java.lang.String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS"; field public static final java.lang.String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS"; + field public static final java.lang.String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS"; field public static final java.lang.String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION"; field public static final java.lang.String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS"; field public static final java.lang.String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS"; @@ -31579,6 +31585,7 @@ package android.provider { public static final class VoicemailContract.Status implements android.provider.BaseColumns { method public static android.net.Uri buildSourceUri(java.lang.String); + method public static void setQuota(android.content.Context, android.telecom.PhoneAccountHandle, int, int); field public static final java.lang.String CONFIGURATION_STATE = "configuration_state"; field public static final int CONFIGURATION_STATE_CAN_BE_CONFIGURED = 2; // 0x2 field public static final int CONFIGURATION_STATE_NOT_CONFIGURED = 1; // 0x1 @@ -31595,6 +31602,9 @@ package android.provider { field public static final int NOTIFICATION_CHANNEL_STATE_OK = 0; // 0x0 field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name"; field public static final java.lang.String PHONE_ACCOUNT_ID = "phone_account_id"; + field public static final java.lang.String QUOTA_OCCUPIED = "quota_occupied"; + field public static final java.lang.String QUOTA_TOTAL = "quota_total"; + field public static final int QUOTA_UNAVAILABLE = -1; // 0xffffffff field public static final java.lang.String SETTINGS_URI = "settings_uri"; field public static final java.lang.String SOURCE_PACKAGE = "source_package"; field public static final java.lang.String VOICEMAIL_ACCESS_URI = "voicemail_access_uri"; @@ -31611,6 +31621,7 @@ package android.provider { field public static final java.lang.String HAS_CONTENT = "has_content"; field public static final java.lang.String IS_READ = "is_read"; field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail"; + field public static final java.lang.String LAST_MODIFIED = "last_modified"; field public static final java.lang.String MIME_TYPE = "mime_type"; field public static final java.lang.String NUMBER = "number"; field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name"; @@ -33194,13 +33205,20 @@ package android.service.notification { field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService"; } + public class NotificationAdjustment implements android.os.Parcelable { + ctor public NotificationAdjustment(int, java.lang.CharSequence, android.net.Uri); + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.notification.NotificationAdjustment> CREATOR; + } + public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService { ctor public NotificationAssistantService(); - method public final void adjustImportance(java.lang.String, android.service.notification.NotificationAssistantService.Adjustment); + method public final void adjustImportance(java.lang.String, android.service.notification.NotificationAdjustment); method public final void clearAnnotation(java.lang.String); method public void onNotificationActionClick(java.lang.String, long, int); method public void onNotificationClick(java.lang.String, long); - method public abstract android.service.notification.NotificationAssistantService.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean); + method public abstract android.service.notification.NotificationAdjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean); method public void onNotificationRemoved(java.lang.String, long, int); method public void onNotificationVisibilityChanged(java.lang.String, long, boolean); method public final void setAnnotation(java.lang.String, android.app.Notification); @@ -33217,10 +33235,7 @@ package android.service.notification { field public static final int REASON_PACKAGE_BANNED = 7; // 0x7 field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5 field public static final int REASON_USER_STOPPED = 6; // 0x6 - } - - public class NotificationAssistantService.Adjustment { - ctor public NotificationAssistantService.Adjustment(int, java.lang.CharSequence, android.net.Uri); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; } public abstract class NotificationListenerService extends android.app.Service { @@ -39302,11 +39317,11 @@ package android.view { method public int getAction(); method public android.content.ClipData getClipData(); method public android.content.ClipDescription getClipDescription(); - method public android.view.DropPermissionHolder getDropPermissionHolder(); method public java.lang.Object getLocalState(); method public boolean getResult(); method public float getX(); method public float getY(); + method public android.view.DropPermissions requestDropPermissions(); method public void writeToParcel(android.os.Parcel, int); field public static final int ACTION_DRAG_ENDED = 4; // 0x4 field public static final int ACTION_DRAG_ENTERED = 5; // 0x5 @@ -39317,12 +39332,8 @@ package android.view { field public static final android.os.Parcelable.Creator<android.view.DragEvent> CREATOR; } - public class DropPermissionHolder implements android.os.Parcelable { - method public int describeContents(); - method public void grant(); - method public void revoke(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.view.DropPermissionHolder> CREATOR; + public final class DropPermissions { + method public void release(); } public class FocusFinder { diff --git a/api/system-current.txt b/api/system-current.txt index 57ae4329ae32..1a1555579e07 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5866,6 +5866,7 @@ package android.app.admin { method public java.lang.String[] getAccountTypesWithManagementDisabled(); method public java.util.List<android.content.ComponentName> getActiveAdmins(); method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String); + method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName); method public boolean getAutoTimeRequired(); method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName); method public boolean getCameraDisabled(android.content.ComponentName); @@ -5916,6 +5917,7 @@ package android.app.admin { method public boolean isActivePasswordSufficient(); method public boolean isAdminActive(android.content.ComponentName); method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String); + method public boolean isCallerApplicationRestrictionsManagingPackage(); method public boolean isDeviceOwnerApp(java.lang.String); method public boolean isLockTaskPermitted(java.lang.String); method public boolean isMasterVolumeMuted(android.content.ComponentName); @@ -5924,6 +5926,7 @@ package android.app.admin { method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String); method public void lockNow(); method public void notifyPendingSystemUpdate(long); + method public void reboot(android.content.ComponentName); method public void removeActiveAdmin(android.content.ComponentName); method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String); method public boolean removeKeyPair(android.content.ComponentName, java.lang.String); @@ -5933,6 +5936,7 @@ package android.app.admin { method public deprecated boolean setActiveProfileOwner(android.content.ComponentName, java.lang.String) throws java.lang.IllegalArgumentException; method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean); method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle); + method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String); method public void setAutoTimeRequired(android.content.ComponentName, boolean); method public void setBluetoothContactSharingDisabled(android.content.ComponentName, boolean); method public void setCameraDisabled(android.content.ComponentName, boolean); @@ -9654,7 +9658,6 @@ package android.content.pm { method public void setAppLabel(java.lang.CharSequence); method public void setAppPackageName(java.lang.String); method public void setGrantedRuntimePermissions(java.lang.String[]); - method public void setInstallFlagsQuick(); method public void setInstallLocation(int); method public void setOriginatingUid(int); method public void setOriginatingUri(android.net.Uri); @@ -23794,6 +23797,7 @@ package android.media.tv { public class TvStreamConfig implements android.os.Parcelable { method public int describeContents(); + method public int getFlags(); method public int getGeneration(); method public int getMaxHeight(); method public int getMaxWidth(); @@ -23801,6 +23805,7 @@ package android.media.tv { method public int getType(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.media.tv.TvStreamConfig> CREATOR; + field public static final int FLAG_MASK_SIGNAL_DETECTION = 1; // 0x1 field public static final int STREAM_TYPE_BUFFER_PRODUCER = 2; // 0x2 field public static final int STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE = 1; // 0x1 } @@ -23808,6 +23813,7 @@ package android.media.tv { public static final class TvStreamConfig.Builder { ctor public TvStreamConfig.Builder(); method public android.media.tv.TvStreamConfig build(); + method public android.media.tv.TvStreamConfig.Builder flags(int); method public android.media.tv.TvStreamConfig.Builder generation(int); method public android.media.tv.TvStreamConfig.Builder maxHeight(int); method public android.media.tv.TvStreamConfig.Builder maxWidth(int); @@ -31318,6 +31324,7 @@ package android.provider { field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location"; field public static final int INCOMING_TYPE = 1; // 0x1 field public static final java.lang.String IS_READ = "is_read"; + field public static final java.lang.String LAST_MODIFIED = "last_modified"; field public static final java.lang.String LIMIT_PARAM_KEY = "limit"; field public static final int MISSED_TYPE = 3; // 0x3 field public static final java.lang.String NEW = "new"; @@ -33058,6 +33065,7 @@ package android.provider { field public static final java.lang.String ACTION_LOCATION_SOURCE_SETTINGS = "android.settings.LOCATION_SOURCE_SETTINGS"; field public static final java.lang.String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS"; field public static final java.lang.String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS"; + field public static final java.lang.String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS"; field public static final java.lang.String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION"; field public static final java.lang.String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS"; field public static final java.lang.String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS"; @@ -33720,6 +33728,7 @@ package android.provider { public static final class VoicemailContract.Status implements android.provider.BaseColumns { method public static android.net.Uri buildSourceUri(java.lang.String); + method public static void setQuota(android.content.Context, android.telecom.PhoneAccountHandle, int, int); field public static final java.lang.String CONFIGURATION_STATE = "configuration_state"; field public static final int CONFIGURATION_STATE_CAN_BE_CONFIGURED = 2; // 0x2 field public static final int CONFIGURATION_STATE_NOT_CONFIGURED = 1; // 0x1 @@ -33736,6 +33745,9 @@ package android.provider { field public static final int NOTIFICATION_CHANNEL_STATE_OK = 0; // 0x0 field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name"; field public static final java.lang.String PHONE_ACCOUNT_ID = "phone_account_id"; + field public static final java.lang.String QUOTA_OCCUPIED = "quota_occupied"; + field public static final java.lang.String QUOTA_TOTAL = "quota_total"; + field public static final int QUOTA_UNAVAILABLE = -1; // 0xffffffff field public static final java.lang.String SETTINGS_URI = "settings_uri"; field public static final java.lang.String SOURCE_PACKAGE = "source_package"; field public static final java.lang.String VOICEMAIL_ACCESS_URI = "voicemail_access_uri"; @@ -33752,6 +33764,7 @@ package android.provider { field public static final java.lang.String HAS_CONTENT = "has_content"; field public static final java.lang.String IS_READ = "is_read"; field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail"; + field public static final java.lang.String LAST_MODIFIED = "last_modified"; field public static final java.lang.String MIME_TYPE = "mime_type"; field public static final java.lang.String NUMBER = "number"; field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name"; @@ -35335,13 +35348,20 @@ package android.service.notification { field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService"; } + public class NotificationAdjustment implements android.os.Parcelable { + ctor public NotificationAdjustment(int, java.lang.CharSequence, android.net.Uri); + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.notification.NotificationAdjustment> CREATOR; + } + public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService { ctor public NotificationAssistantService(); - method public final void adjustImportance(java.lang.String, android.service.notification.NotificationAssistantService.Adjustment); + method public final void adjustImportance(java.lang.String, android.service.notification.NotificationAdjustment); method public final void clearAnnotation(java.lang.String); method public void onNotificationActionClick(java.lang.String, long, int); method public void onNotificationClick(java.lang.String, long); - method public abstract android.service.notification.NotificationAssistantService.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean); + method public abstract android.service.notification.NotificationAdjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean); method public void onNotificationRemoved(java.lang.String, long, int); method public void onNotificationVisibilityChanged(java.lang.String, long, boolean); method public final void setAnnotation(java.lang.String, android.app.Notification); @@ -35358,10 +35378,7 @@ package android.service.notification { field public static final int REASON_PACKAGE_BANNED = 7; // 0x7 field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5 field public static final int REASON_USER_STOPPED = 6; // 0x6 - } - - public class NotificationAssistantService.Adjustment { - ctor public NotificationAssistantService.Adjustment(int, java.lang.CharSequence, android.net.Uri); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; } public abstract class NotificationListenerService extends android.app.Service { @@ -41654,11 +41671,11 @@ package android.view { method public int getAction(); method public android.content.ClipData getClipData(); method public android.content.ClipDescription getClipDescription(); - method public android.view.DropPermissionHolder getDropPermissionHolder(); method public java.lang.Object getLocalState(); method public boolean getResult(); method public float getX(); method public float getY(); + method public android.view.DropPermissions requestDropPermissions(); method public void writeToParcel(android.os.Parcel, int); field public static final int ACTION_DRAG_ENDED = 4; // 0x4 field public static final int ACTION_DRAG_ENTERED = 5; // 0x5 @@ -41669,12 +41686,8 @@ package android.view { field public static final android.os.Parcelable.Creator<android.view.DragEvent> CREATOR; } - public class DropPermissionHolder implements android.os.Parcelable { - method public int describeContents(); - method public void grant(); - method public void revoke(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.view.DropPermissionHolder> CREATOR; + public final class DropPermissions { + method public void release(); } public class FocusFinder { diff --git a/api/test-current.txt b/api/test-current.txt index e506cfc44f60..3531e34a2407 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5741,6 +5741,7 @@ package android.app.admin { method public java.lang.String[] getAccountTypesWithManagementDisabled(); method public java.util.List<android.content.ComponentName> getActiveAdmins(); method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String); + method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName); method public boolean getAutoTimeRequired(); method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName); method public boolean getCameraDisabled(android.content.ComponentName); @@ -5784,6 +5785,7 @@ package android.app.admin { method public boolean isActivePasswordSufficient(); method public boolean isAdminActive(android.content.ComponentName); method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String); + method public boolean isCallerApplicationRestrictionsManagingPackage(); method public boolean isDeviceOwnerApp(java.lang.String); method public boolean isLockTaskPermitted(java.lang.String); method public boolean isMasterVolumeMuted(android.content.ComponentName); @@ -5791,6 +5793,7 @@ package android.app.admin { method public boolean isProvisioningAllowed(java.lang.String); method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String); method public void lockNow(); + method public void reboot(android.content.ComponentName); method public void removeActiveAdmin(android.content.ComponentName); method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String); method public boolean removeKeyPair(android.content.ComponentName, java.lang.String); @@ -5799,6 +5802,7 @@ package android.app.admin { method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean); method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean); method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle); + method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String); method public void setAutoTimeRequired(android.content.ComponentName, boolean); method public void setBluetoothContactSharingDisabled(android.content.ComponentName, boolean); method public void setCameraDisabled(android.content.ComponentName, boolean); @@ -29312,6 +29316,7 @@ package android.provider { field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location"; field public static final int INCOMING_TYPE = 1; // 0x1 field public static final java.lang.String IS_READ = "is_read"; + field public static final java.lang.String LAST_MODIFIED = "last_modified"; field public static final java.lang.String LIMIT_PARAM_KEY = "limit"; field public static final int MISSED_TYPE = 3; // 0x3 field public static final java.lang.String NEW = "new"; @@ -30920,6 +30925,7 @@ package android.provider { field public static final java.lang.String ACTION_LOCATION_SOURCE_SETTINGS = "android.settings.LOCATION_SOURCE_SETTINGS"; field public static final java.lang.String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS"; field public static final java.lang.String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS"; + field public static final java.lang.String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS"; field public static final java.lang.String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION"; field public static final java.lang.String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS"; field public static final java.lang.String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS"; @@ -31581,6 +31587,7 @@ package android.provider { public static final class VoicemailContract.Status implements android.provider.BaseColumns { method public static android.net.Uri buildSourceUri(java.lang.String); + method public static void setQuota(android.content.Context, android.telecom.PhoneAccountHandle, int, int); field public static final java.lang.String CONFIGURATION_STATE = "configuration_state"; field public static final int CONFIGURATION_STATE_CAN_BE_CONFIGURED = 2; // 0x2 field public static final int CONFIGURATION_STATE_NOT_CONFIGURED = 1; // 0x1 @@ -31597,6 +31604,9 @@ package android.provider { field public static final int NOTIFICATION_CHANNEL_STATE_OK = 0; // 0x0 field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name"; field public static final java.lang.String PHONE_ACCOUNT_ID = "phone_account_id"; + field public static final java.lang.String QUOTA_OCCUPIED = "quota_occupied"; + field public static final java.lang.String QUOTA_TOTAL = "quota_total"; + field public static final int QUOTA_UNAVAILABLE = -1; // 0xffffffff field public static final java.lang.String SETTINGS_URI = "settings_uri"; field public static final java.lang.String SOURCE_PACKAGE = "source_package"; field public static final java.lang.String VOICEMAIL_ACCESS_URI = "voicemail_access_uri"; @@ -31613,6 +31623,7 @@ package android.provider { field public static final java.lang.String HAS_CONTENT = "has_content"; field public static final java.lang.String IS_READ = "is_read"; field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail"; + field public static final java.lang.String LAST_MODIFIED = "last_modified"; field public static final java.lang.String MIME_TYPE = "mime_type"; field public static final java.lang.String NUMBER = "number"; field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name"; @@ -33196,13 +33207,20 @@ package android.service.notification { field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService"; } + public class NotificationAdjustment implements android.os.Parcelable { + ctor public NotificationAdjustment(int, java.lang.CharSequence, android.net.Uri); + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.notification.NotificationAdjustment> CREATOR; + } + public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService { ctor public NotificationAssistantService(); - method public final void adjustImportance(java.lang.String, android.service.notification.NotificationAssistantService.Adjustment); + method public final void adjustImportance(java.lang.String, android.service.notification.NotificationAdjustment); method public final void clearAnnotation(java.lang.String); method public void onNotificationActionClick(java.lang.String, long, int); method public void onNotificationClick(java.lang.String, long); - method public abstract android.service.notification.NotificationAssistantService.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean); + method public abstract android.service.notification.NotificationAdjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean); method public void onNotificationRemoved(java.lang.String, long, int); method public void onNotificationVisibilityChanged(java.lang.String, long, boolean); method public final void setAnnotation(java.lang.String, android.app.Notification); @@ -33219,10 +33237,7 @@ package android.service.notification { field public static final int REASON_PACKAGE_BANNED = 7; // 0x7 field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5 field public static final int REASON_USER_STOPPED = 6; // 0x6 - } - - public class NotificationAssistantService.Adjustment { - ctor public NotificationAssistantService.Adjustment(int, java.lang.CharSequence, android.net.Uri); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; } public abstract class NotificationListenerService extends android.app.Service { @@ -39304,11 +39319,11 @@ package android.view { method public int getAction(); method public android.content.ClipData getClipData(); method public android.content.ClipDescription getClipDescription(); - method public android.view.DropPermissionHolder getDropPermissionHolder(); method public java.lang.Object getLocalState(); method public boolean getResult(); method public float getX(); method public float getY(); + method public android.view.DropPermissions requestDropPermissions(); method public void writeToParcel(android.os.Parcel, int); field public static final int ACTION_DRAG_ENDED = 4; // 0x4 field public static final int ACTION_DRAG_ENTERED = 5; // 0x5 @@ -39319,12 +39334,8 @@ package android.view { field public static final android.os.Parcelable.Creator<android.view.DragEvent> CREATOR; } - public class DropPermissionHolder implements android.os.Parcelable { - method public int describeContents(); - method public void grant(); - method public void revoke(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.view.DropPermissionHolder> CREATOR; + public final class DropPermissions { + method public void release(); } public class FocusFinder { diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index 72f8c779227a..3e6b595da00e 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -31,9 +31,11 @@ import android.view.ActionMode; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; +import android.view.View.OnFocusChangeListener; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewHierarchyEncoder; +import android.view.ViewParent; import android.view.Window; import android.widget.SpinnerAdapter; import java.lang.annotation.Retention; @@ -1071,6 +1073,62 @@ public abstract class ActionBar { } /** + * Attempts to move focus to the ActionBar if it does not already contain the focus. + * + * @return {@code true} if focus changes or {@code false} if focus doesn't change. + * @hide + */ + public boolean requestFocus() { + return false; + } + + /** + * Common implementation for requestFocus that takes in the Toolbar and moves focus + * to the contents. This makes the ViewGroups containing the toolbar allow focus while it stays + * in the ActionBar and then prevents it again once it leaves. + * + * @param viewGroup The toolbar ViewGroup + * @return {@code true} if focus changes or {@code false} if focus doesn't change. + * @hide + */ + protected boolean requestFocus(ViewGroup viewGroup) { + if (viewGroup != null && !viewGroup.hasFocus()) { + final ViewGroup toolbar = viewGroup.getTouchscreenBlocksFocus() ? viewGroup : null; + ViewParent parent = viewGroup.getParent(); + ViewGroup container = null; + while (parent != null && parent instanceof ViewGroup) { + final ViewGroup vgParent = (ViewGroup) parent; + if (vgParent.getTouchscreenBlocksFocus()) { + container = vgParent; + break; + } + parent = vgParent.getParent(); + } + if (container != null) { + container.setTouchscreenBlocksFocus(false); + } + if (toolbar != null) { + toolbar.setTouchscreenBlocksFocus(false); + } + viewGroup.requestFocus(); + final View focused = viewGroup.findFocus(); + if (focused != null) { + focused.setOnFocusChangeListener(new FollowOutOfActionBar(viewGroup, + container, toolbar)); + } else { + if (container != null) { + container.setTouchscreenBlocksFocus(true); + } + if (toolbar != null) { + toolbar.setTouchscreenBlocksFocus(true); + } + } + return true; + } + return false; + } + + /** * Listener interface for ActionBar navigation events. * * @deprecated Action bar navigation modes are deprecated and not supported by inline @@ -1388,4 +1446,43 @@ public abstract class ActionBar { encoder.addProperty("gravity", gravity); } } + + /** + * Tracks the focused View until it leaves the ActionBar, then it resets the + * touchscreenBlocksFocus value. + */ + private static class FollowOutOfActionBar implements OnFocusChangeListener, Runnable { + private final ViewGroup mFocusRoot; + private final ViewGroup mContainer; + private final ViewGroup mToolbar; + + public FollowOutOfActionBar(ViewGroup focusRoot, ViewGroup container, ViewGroup toolbar) { + mContainer = container; + mToolbar = toolbar; + mFocusRoot = focusRoot; + } + + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + v.setOnFocusChangeListener(null); + final View focused = mFocusRoot.findFocus(); + if (focused != null) { + focused.setOnFocusChangeListener(this); + } else { + mFocusRoot.post(this); + } + } + } + + @Override + public void run() { + if (mContainer != null) { + mContainer.setTouchscreenBlocksFocus(true); + } + if (mToolbar != null) { + mToolbar.setTouchscreenBlocksFocus(true); + } + } + } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 68cf72a56141..6d72059e0a47 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -816,6 +816,7 @@ public class Activity extends ContextThemeWrapper SharedElementCallback mExitTransitionListener = SharedElementCallback.NULL_CALLBACK; private boolean mHasCurrentPermissionsRequest; + private boolean mEatKeyUpEvent; /** Return the intent that started this activity. */ public Intent getIntent() { @@ -2827,9 +2828,25 @@ public class Activity extends ContextThemeWrapper // Let action bars open menus in response to the menu key prioritized over // the window handling it - if (event.getKeyCode() == KeyEvent.KEYCODE_MENU && + final int keyCode = event.getKeyCode(); + if (keyCode == KeyEvent.KEYCODE_MENU && mActionBar != null && mActionBar.onMenuKeyEvent(event)) { return true; + } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { + // Capture the Alt-up and send focus to the ActionBar + final int action = event.getAction(); + if (action == KeyEvent.ACTION_DOWN) { + if (event.hasModifiers(KeyEvent.META_ALT_ON)) { + final ActionBar actionBar = getActionBar(); + if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) { + mEatKeyUpEvent = true; + return true; + } + } + } else if (action == KeyEvent.ACTION_UP && mEatKeyUpEvent) { + mEatKeyUpEvent = false; + return true; + } } Window win = getWindow(); @@ -5300,8 +5317,7 @@ public class Activity extends ContextThemeWrapper */ public boolean isTaskRoot() { try { - return ActivityManagerNative.getDefault() - .getTaskForActivity(mToken, true) >= 0; + return ActivityManagerNative.getDefault().getTaskForActivity(mToken, true) >= 0; } catch (RemoteException e) { return false; } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index c39ee7597db1..8637dde109d7 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -556,14 +556,6 @@ public class ActivityManager { public static boolean isAlwaysOnTop(int stackId) { return stackId == PINNED_STACK_ID; } - - /** - * Returns true if the application windows in this stack should be displayed above all - * other application windows, including during the animation. - */ - public static boolean shouldIncreaseApplicationWindowLayer(int stackId) { - return stackId == PINNED_STACK_ID || stackId == DOCKED_STACK_ID; - } } /** @@ -1245,6 +1237,18 @@ public class ActivityManager { public static final int RECENT_IGNORE_HOME_STACK_TASKS = 0x0008; /** + * Ignores all tasks that are on the docked stack. + * @hide + */ + public static final int RECENT_INGORE_DOCKED_STACK_TASKS = 0x0010; + + /** + * Ignores all tasks that are on the pinned stack. + * @hide + */ + public static final int RECENT_INGORE_PINNED_STACK_TASKS = 0x0020; + + /** * <p></p>Return a list of the tasks that the user has recently launched, with * the most recent being first and older ones after in order. * diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 20f34951e363..c6cc452f5bb7 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -31,6 +31,7 @@ import android.view.ViewGroup; import android.view.ViewGroupOverlay; import android.view.ViewTreeObserver; import android.view.Window; +import android.view.accessibility.AccessibilityEvent; import java.util.ArrayList; @@ -500,6 +501,10 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { @Override protected void onTransitionsComplete() { moveSharedElementsFromOverlay(); + final ViewGroup decorView = getDecor(); + if (decorView != null) { + decorView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + } } private void sharedElementTransitionStarted() { diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 136b810d5ee7..bce5bf37f0eb 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -54,6 +54,7 @@ interface INotificationManager int getTopicPriority(String pkg, int uid, in Notification.Topic topic); void setTopicImportance(String pkg, int uid, in Notification.Topic topic, int importance); int getTopicImportance(String pkg, int uid, in Notification.Topic topic); + void setAppImportance(String pkg, int uid, int importance); // TODO: Remove this when callers have been migrated to the equivalent // INotificationListener method. diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 660ce3bd0a44..5df6ba80a10a 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -40,8 +40,8 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; -import android.provider.Settings; import android.provider.ContactsContract.Directory; +import android.provider.Settings; import android.security.Credentials; import android.service.restrictions.RestrictionsReceiver; import android.util.Log; @@ -56,14 +56,14 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.InvalidKeySpecException; -import java.security.NoSuchAlgorithmException; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -3312,8 +3312,69 @@ public class DevicePolicyManager { } /** - * Called by a profile or device owner to set the application restrictions for a given target - * application running in the profile. + * Called by a profile owner or device owner to grant permission to a package to manage + * application restrictions for the calling user via {@link #setApplicationRestrictions} and + * {@link #getApplicationRestrictions}. + * <p> + * This permission is persistent until it is later cleared by calling this method with a + * {@code null} value or uninstalling the managing package. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param packageName The package name which will be given access to application restrictions + * APIs. If {@code null} is given the current package will be cleared. + */ + public void setApplicationRestrictionsManagingPackage(@NonNull ComponentName admin, + @Nullable String packageName) { + if (mService != null) { + try { + mService.setApplicationRestrictionsManagingPackage(admin, packageName); + } catch (RemoteException e) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e); + } + } + } + + /** + * Called by a profile owner or device owner to retrieve the application restrictions managing + * package for the current user, or {@code null} if none is set. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @return The package name allowed to manage application restrictions on the current user, or + * {@code null} if none is set. + */ + public String getApplicationRestrictionsManagingPackage(@NonNull ComponentName admin) { + if (mService != null) { + try { + return mService.getApplicationRestrictionsManagingPackage(admin); + } catch (RemoteException e) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e); + } + } + return null; + } + + /** + * Returns {@code true} if the calling package has been granted permission via + * {@link #setApplicationRestrictionsManagingPackage} to manage application + * restrictions for the calling user. + */ + public boolean isCallerApplicationRestrictionsManagingPackage() { + if (mService != null) { + try { + return mService.isCallerApplicationRestrictionsManagingPackage(); + } catch (RemoteException e) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e); + } + } + return false; + } + + /** + * Sets the application restrictions for a given target application running in the calling user. + * + * <p>The caller must be a profile or device owner on that user, or the package allowed to + * manage application restrictions via {@link #setApplicationRestrictionsManagingPackage}; + * otherwise a security exception will be thrown. * * <p>The provided {@link Bundle} consists of key-value pairs, where the types of values may be: * <ul> @@ -3323,24 +3384,25 @@ public class DevicePolicyManager { * <li>From {@link android.os.Build.VERSION_CODES#M}, {@code Bundle} or {@code Bundle[]} * </ul> * - * <p>The application restrictions are only made visible to the target application and the - * profile or device owner. - * * <p>If the restrictions are not available yet, but may be applied in the near future, - * the admin can notify the target application of that by adding + * the caller can notify the target application of that by adding * {@link UserManager#KEY_RESTRICTIONS_PENDING} to the settings parameter. * - * <p>The calling device admin must be a profile or device owner; if it is not, a security - * exception will be thrown. + * <p>The application restrictions are only made visible to the target application via + * {@link UserManager#getApplicationRestrictions(String)}, in addition to the profile or + * device owner, and the application restrictions managing package via + * {@link #getApplicationRestrictions}. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or + * {@code null} if called by the application restrictions managing package. * @param packageName The name of the package to update restricted settings for. * @param settings A {@link Bundle} to be parsed by the receiving application, conveying a new * set of active restrictions. * + * @see #setApplicationRestrictionsManagingPackage * @see UserManager#KEY_RESTRICTIONS_PENDING */ - public void setApplicationRestrictions(@NonNull ComponentName admin, String packageName, + public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName, Bundle settings) { if (mService != null) { try { @@ -3896,19 +3958,23 @@ public class DevicePolicyManager { } /** - * Called by a profile or device owner to get the application restrictions for a given target - * application running in the profile. + * Retrieves the application restrictions for a given target application running in the calling + * user. * - * <p>The calling device admin must be a profile or device owner; if it is not, a security - * exception will be thrown. + * <p>The caller must be a profile or device owner on that user, or the package allowed to + * manage application restrictions via {@link #setApplicationRestrictionsManagingPackage}; + * otherwise a security exception will be thrown. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or + * {@code null} if called by the application restrictions managing package. * @param packageName The name of the package to fetch restricted settings of. * @return {@link Bundle} of settings corresponding to what was set last time * {@link DevicePolicyManager#setApplicationRestrictions} was called, or an empty {@link Bundle} * if no restrictions have been set. + * + * @see {@link #setApplicationRestrictionsManagingPackage} */ - public Bundle getApplicationRestrictions(@NonNull ComponentName admin, String packageName) { + public Bundle getApplicationRestrictions(@Nullable ComponentName admin, String packageName) { if (mService != null) { try { return mService.getApplicationRestrictions(admin, packageName); @@ -4721,4 +4787,15 @@ public class DevicePolicyManager { return null; } } + + /** + * Called by device owner to reboot the device. + */ + public void reboot(@NonNull ComponentName admin) { + try { + mService.reboot(admin); + } catch (RemoteException re) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re); + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index d8cc2eaf280d..30ce682ef888 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -149,6 +149,9 @@ interface IDevicePolicyManager { void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings); Bundle getApplicationRestrictions(in ComponentName who, in String packageName); + void setApplicationRestrictionsManagingPackage(in ComponentName admin, in String packageName); + String getApplicationRestrictionsManagingPackage(in ComponentName admin); + boolean isCallerApplicationRestrictionsManagingPackage(); void setRestrictionsProvider(in ComponentName who, in ComponentName provider); ComponentName getRestrictionsProvider(int userHandle); @@ -241,4 +244,5 @@ interface IDevicePolicyManager { boolean isManagedProfile(in ComponentName admin); boolean isSystemOnlyUser(in ComponentName admin); String getWifiMacAddress(); + void reboot(in ComponentName admin); } diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index cbce22cdea60..6bf3fab71ec7 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -131,6 +131,18 @@ public interface BluetoothProfile { public static final int HEADSET_CLIENT = 16; /** + * HID Profile + * @hide + */ + public static final int HID = 17; + + /** + * HDP Profile + * @hide + */ + public static final int HDP = 18; + + /** * Default priority for devices that we try to auto-connect to and * and allow incoming connections for the profile * @hide diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java index c934e8dfec0a..0b80f8a9fc18 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java @@ -36,6 +36,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; +import java.util.List; /** * Representation of a clipped data on the clipboard. @@ -914,6 +915,27 @@ public class ClipData implements Parcelable { } } + /** @hide */ + public void collectUris(List<Uri> out) { + for (int i = 0; i < mItems.size(); ++i) { + ClipData.Item item = getItemAt(i); + + if (item.getUri() != null) { + out.add(item.getUri()); + } + + Intent intent = item.getIntent(); + if (intent != null) { + if (intent.getData() != null) { + out.add(intent.getData()); + } + if (intent.getClipData() != null) { + intent.getClipData().collectUris(out); + } + } + } + } + @Override public int describeContents() { return 0; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 38a4475064af..67bdad576af3 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -326,10 +326,30 @@ public abstract class Context { */ public static final int BIND_NOT_VISIBLE = 0x40000000; - /** Return an AssetManager instance for your application's package. */ + /** + * Returns an AssetManager instance for the application's package. + * <p> + * <strong>Note:</strong> Implementations of this method should return + * an AssetManager instance that is consistent with the Resources instance + * returned by {@link #getResources()}. For example, they should share the + * same {@link Configuration} object. + * + * @return an AssetManager instance for the application's package + * @see #getResources() + */ public abstract AssetManager getAssets(); - /** Return a Resources instance for your application's package. */ + /** + * Returns a Resources instance for the application's package. + * <p> + * <strong>Note:</strong> Implementations of this method should return + * a Resources instance that is consistent with the AssetManager instance + * returned by {@link #getAssets()}. For example, they should share the + * same {@link Configuration} object. + * + * @return a Resources instance for the application's package + * @see #getAssets() + */ public abstract Resources getResources(); /** Return PackageManager instance to find global package information. */ diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 1a3d262f6b56..c99ddc8fbe11 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -82,8 +82,7 @@ public class ContextWrapper extends Context { } @Override - public Resources getResources() - { + public Resources getResources() { return mBase.getResources(); } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index d6d395b79fa1..32830053fae2 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1042,12 +1042,6 @@ public class PackageInstaller { } /** {@hide} */ - @SystemApi - public void setInstallFlagsQuick() { - installFlags |= PackageManager.INSTALL_QUICK; - } - - /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); pw.printHexPair("installFlags", installFlags); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 38242fbc55e6..3235bcff1eaf 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -457,19 +457,11 @@ public abstract class PackageManager { /** * Flag parameter for {@link #installPackage} to indicate that this package is - * to be installed quickly. - * - * @hide - */ - public static final int INSTALL_QUICK = 0x00000800; - - /** - * Flag parameter for {@link #installPackage} to indicate that this package is * to be installed as a lightweight "ephemeral" app. * * @hide */ - public static final int INSTALL_EPHEMERAL = 0x00001000; + public static final int INSTALL_EPHEMERAL = 0x00000800; /** * Flag parameter for diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 019ed2b8c36f..f445cf8208b6 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -625,9 +625,7 @@ public class PackageParser { public final static int PARSE_COLLECT_CERTIFICATES = 1<<8; public final static int PARSE_TRUSTED_OVERLAY = 1<<9; public final static int PARSE_ENFORCE_CODE = 1<<10; - // TODO: fix b/25118622; remove this entirely once signature processing is quick - public final static int PARSE_SKIP_VERIFICATION = 1<<11; - public final static int PARSE_IS_EPHEMERAL = 1<<12; + public final static int PARSE_IS_EPHEMERAL = 1<<11; private static final Comparator<String> sSplitNameComparator = new SplitNameComparator(); @@ -1060,8 +1058,7 @@ public class PackageParser { /** * Collect certificates from all the APKs described in the given package, - * populating {@link Package#mSignatures}. - * <p>Depending upon the parser flags, this may also asserts that all APK + * populating {@link Package#mSignatures}. Also asserts that all APK * contents are signed correctly and consistently. */ public void collectCertificates(Package pkg, int parseFlags) throws PackageParserException { @@ -1084,10 +1081,8 @@ public class PackageParser { final boolean hasCode = (apkFlags & ApplicationInfo.FLAG_HAS_CODE) != 0; final boolean requireCode = ((parseFlags & PARSE_ENFORCE_CODE) != 0) && hasCode; final String apkPath = apkFile.getAbsolutePath(); - final boolean skipVerification = Build.IS_DEBUGGABLE - && ((parseFlags & PARSE_SKIP_VERIFICATION) != 0); - boolean codeFound = skipVerification; + boolean codeFound = false; StrictJarFile jarFile = null; try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "strictJarFileCtor"); @@ -1106,7 +1101,7 @@ public class PackageParser { toVerify.add(manifestEntry); // If we're parsing an untrusted package, verify all contents - if (!skipVerification && (parseFlags & PARSE_IS_SYSTEM) == 0) { + if ((parseFlags & PARSE_IS_SYSTEM) == 0) { final Iterator<ZipEntry> i = jarFile.iterator(); while (i.hasNext()) { final ZipEntry entry = i.next(); @@ -1150,9 +1145,6 @@ public class PackageParser { for (int i=0; i < entryCerts.length; i++) { pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey()); } - if (skipVerification) { - break; - } } else { if (!Signature.areExactMatch(pkg.mSignatures, entrySignatures)) { throw new PackageParserException( @@ -1218,9 +1210,7 @@ public class PackageParser { if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { // TODO: factor signature related items out of Package object final Package tempPkg = new Package(null); - // TODO: fix b/25118622; pass in '0' for parse flags - collectCertificates(tempPkg, apkFile, 0 /*apkFlags*/, - flags & PARSE_SKIP_VERIFICATION); + collectCertificates(tempPkg, apkFile, 0 /*apkFlags*/, 0 /*flags*/); signatures = tempPkg.mSignatures; } else { signatures = null; diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index cd483b104489..ee7bd9acec77 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -32,7 +32,9 @@ public class BaseBundle { private static final String TAG = "Bundle"; static final boolean DEBUG = false; + // Keep in sync with frameworks/native/libs/binder/PersistableBundle.cpp. static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L' + static final Parcel EMPTY_PARCEL; static { @@ -1308,6 +1310,8 @@ public class BaseBundle { * @param parcel The parcel to copy this bundle to. */ void writeToParcelInner(Parcel parcel, int flags) { + // Keep implementation in sync with writeToParcel() in + // frameworks/native/libs/binder/PersistableBundle.cpp. if (mParcelledData != null) { if (mParcelledData == EMPTY_PARCEL) { parcel.writeInt(0); @@ -1345,6 +1349,8 @@ public class BaseBundle { * @param parcel The parcel to overwrite this bundle from. */ void readFromParcelInner(Parcel parcel) { + // Keep implementation in sync with readFromParcel() in + // frameworks/native/libs/binder/PersistableBundle.cpp. int length = parcel.readInt(); readFromParcelInner(parcel, length); } diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index a01d34a386e4..2ca9ab8a0ac2 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -37,6 +37,7 @@ public class Environment { private static final String ENV_ANDROID_STORAGE = "ANDROID_STORAGE"; private static final String ENV_DOWNLOAD_CACHE = "DOWNLOAD_CACHE"; private static final String ENV_OEM_ROOT = "OEM_ROOT"; + private static final String ENV_ODM_ROOT = "ODM_ROOT"; private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT"; /** {@hide} */ @@ -56,6 +57,7 @@ public class Environment { private static final File DIR_ANDROID_STORAGE = getDirectory(ENV_ANDROID_STORAGE, "/storage"); private static final File DIR_DOWNLOAD_CACHE = getDirectory(ENV_DOWNLOAD_CACHE, "/cache"); private static final File DIR_OEM_ROOT = getDirectory(ENV_OEM_ROOT, "/oem"); + private static final File DIR_ODM_ROOT = getDirectory(ENV_ODM_ROOT, "/odm"); private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor"); private static UserEnvironment sCurrentUser; @@ -156,6 +158,16 @@ public class Environment { } /** + * Return root directory of the "odm" partition holding ODM customizations, + * if any. If present, the partition is mounted read-only. + * + * @hide + */ + public static File getOdmDirectory() { + return DIR_ODM_ROOT; + } + + /** * Return root directory of the "vendor" partition that holds vendor-provided * software that should persist across simple reflashing of the "system" partition. * @hide diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 9b68f90aa28b..26312475297c 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -204,6 +204,7 @@ public final class Parcel { private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE]; private static final Parcel[] sHolderPool = new Parcel[POOL_SIZE]; + // Keep in sync with frameworks/native/libs/binder/PersistableBundle.cpp. private static final int VAL_NULL = -1; private static final int VAL_STRING = 0; private static final int VAL_INTEGER = 1; @@ -704,6 +705,8 @@ public final class Parcel { writeInt(-1); return; } + // Keep the format of this Parcel in sync with writeToParcelInner() in + // frameworks/native/libs/binder/PersistableBundle.cpp. final int N = val.size(); writeInt(N); if (DEBUG_ARRAY_MAP) { @@ -1370,7 +1373,13 @@ public final class Parcel { // Must be before Parcelable writeInt(VAL_BUNDLE); writeBundle((Bundle) v); + } else if (v instanceof PersistableBundle) { + writeInt(VAL_PERSISTABLEBUNDLE); + writePersistableBundle((PersistableBundle) v); } else if (v instanceof Parcelable) { + // IMPOTANT: cases for classes that implement Parcelable must + // come before the Parcelable case, so that their specific VAL_* + // types will be written. writeInt(VAL_PARCELABLE); writeParcelable((Parcelable) v, 0); } else if (v instanceof Short) { @@ -1426,9 +1435,6 @@ public final class Parcel { } else if (v instanceof Byte) { writeInt(VAL_BYTE); writeInt((Byte) v); - } else if (v instanceof PersistableBundle) { - writeInt(VAL_PERSISTABLEBUNDLE); - writePersistableBundle((PersistableBundle) v); } else if (v instanceof Size) { writeInt(VAL_SIZE); writeSize((Size) v); diff --git a/core/java/android/os/PersistableBundle.aidl b/core/java/android/os/PersistableBundle.aidl index 5b05873257c5..94e86076308d 100644 --- a/core/java/android/os/PersistableBundle.aidl +++ b/core/java/android/os/PersistableBundle.aidl @@ -2,19 +2,19 @@ ** ** Copyright 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 +** 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 +** 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 +** 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.os; -parcelable PersistableBundle; +parcelable PersistableBundle cpp_header "binder/PersistableBundle.h"; diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index a0a006044751..126824f2c46a 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -389,6 +389,13 @@ public final class PowerManager { public static final String REBOOT_RECOVERY = "recovery"; /** + * The value to pass as the 'reason' argument to reboot() when device owner requests a reboot on + * the device. + * @hide + */ + public static final String REBOOT_REQUESTED_BY_DEVICE_OWNER = "deviceowner"; + + /** * The value to pass as the 'reason' argument to android_reboot(). * @hide */ diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index c5adafe9d16d..ddd16e21b066 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -852,9 +852,14 @@ public class UserManager { * @param user to retrieve the unlocked state for. */ public boolean isUserUnlocked(UserHandle user) { + return isUserUnlocked(user.getIdentifier()); + } + + /** {@hide} */ + public boolean isUserUnlocked(int userId) { try { - return ActivityManagerNative.getDefault().isUserRunning( - user.getIdentifier(), ActivityManager.FLAG_AND_UNLOCKED); + return ActivityManagerNative.getDefault().isUserRunning(userId, + ActivityManager.FLAG_AND_UNLOCKED); } catch (RemoteException e) { return false; } diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 1d4d57278a57..6a5d857c4a37 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -421,6 +421,13 @@ public class CallLog { public static final String ADD_FOR_ALL_USERS = "add_for_all_users"; /** + * The date the row is last inserted, updated, or marked as deleted, in milliseconds + * since the epoch. Read only. + * <P>Type: INTEGER (long)</P> + */ + public static final String LAST_MODIFIED = "last_modified"; + + /** * If a successful call is made that is longer than this duration, update the phone number * in the ContactsProvider with the normalized version of the number, based on the user's * current country code. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a1e551082799..b883f9c736d0 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1092,6 +1092,22 @@ public final class Settings { public static final String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS"; + + + /** + * Activity Action: Show Default apps settings. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS + = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS"; + /** * Activity Action: Show notification settings. * @@ -5617,6 +5633,15 @@ public final class Settings { public static final String ASSIST_SCREENSHOT_ENABLED = "assist_screenshot_enabled"; /** + * Names of the service component that the current user has explicitly allowed to + * see and change the importance of all of the user's notifications. + * + * @hide + */ + public static final String ENABLED_NOTIFICATION_ASSISTANT + = "enabled_notification_assistant"; + + /** * Names of the service components that the current user has explicitly allowed to * see all of the user's notifications, separated by ':'. * @@ -6108,6 +6133,13 @@ public final class Settings { public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios"; /** + * A Long representing a bitmap of profiles that should be disabled when bluetooth starts. + * See {@link android.bluetooth.BluetoothProfile}. + * {@hide} + */ + public static final String BLUETOOTH_DISABLED_PROFILES = "bluetooth_disabled_profiles"; + + /** * The policy for deciding when Wi-Fi should go to sleep (which will in * turn switch to using the mobile data as an Internet connection). * <p> diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java index 76eaea9998cd..24683cb1d9fd 100644 --- a/core/java/android/provider/VoicemailContract.java +++ b/core/java/android/provider/VoicemailContract.java @@ -245,6 +245,13 @@ public class VoicemailContract { public static final String DELETED = "deleted"; /** + * The date the row is last inserted, updated, or marked as deleted, in milliseconds + * since the epoch. Read only. + * <P>Type: INTEGER (long)</P> + */ + public static final String LAST_MODIFIED = "last_modified"; + + /** * A convenience method to build voicemail URI specific to a source package by appending * {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI. */ @@ -449,6 +456,26 @@ public class VoicemailContract { public static final int NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING = 2; /** + * Amount of resource that is used by existing voicemail in the visual voicemail inbox, + * or {@link #QUOTA_UNAVAILABLE}. Unit is not specified. + * <P>Type: INTEGER</P> + */ + public static final String QUOTA_OCCUPIED = "quota_occupied"; + + /** + * Total resource in the visual voicemail inbox that can be used, or + * {@link #QUOTA_UNAVAILABLE}. Unit is not specified. + * <P>Type: INTEGER</P> + */ + public static final String QUOTA_TOTAL = "quota_total"; + + /** + * Value for {@link #QUOTA_OCCUPIED} and {@link #QUOTA_TOTAL} to indicate that no + * information is available. + */ + public static final int QUOTA_UNAVAILABLE = -1; + + /** * A convenience method to build status URI specific to a source package by appending * {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI. */ @@ -488,6 +515,39 @@ public class VoicemailContract { } /** + * A helper method to set the quota of a voicemail source. Unit is unspecified. + * + * @param context The context from the package calling the method. This will be the source. + * @param accountHandle The handle for the account the source is associated with. + * @param occupied See {@link Status#QUOTA_OCCUPIED} + * @param total See {@link Status#QUOTA_TOTAL} + */ + public static void setQuota(Context context, PhoneAccountHandle accountHandle, int occupied, + int total) { + if (occupied == QUOTA_UNAVAILABLE && total == QUOTA_UNAVAILABLE) { + return; + } + ContentValues values = new ContentValues(); + values.put(Status.PHONE_ACCOUNT_COMPONENT_NAME, + accountHandle.getComponentName().flattenToString()); + values.put(Status.PHONE_ACCOUNT_ID, accountHandle.getId()); + if (occupied != QUOTA_UNAVAILABLE) { + values.put(Status.QUOTA_OCCUPIED,occupied); + } + if (total != QUOTA_UNAVAILABLE) { + values.put(Status.QUOTA_TOTAL,total); + } + + ContentResolver contentResolver = context.getContentResolver(); + Uri statusUri = buildSourceUri(context.getPackageName()); + if (isStatusPresent(contentResolver, statusUri)) { + contentResolver.update(statusUri, values, null, null); + } else { + contentResolver.insert(statusUri, values); + } + } + + /** * Determines if a voicemail source exists in the status table. * * @param contentResolver A content resolver constructed from the appropriate context. diff --git a/core/java/android/service/notification/INotificationAssistant.aidl b/core/java/android/service/notification/INotificationAssistant.aidl new file mode 100644 index 000000000000..5c5f358b99b9 --- /dev/null +++ b/core/java/android/service/notification/INotificationAssistant.aidl @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.notification; + +import android.service.notification.NotificationAdjustment; +import android.service.notification.IStatusBarNotificationHolder; +import android.service.notification.NotificationRankingUpdate; + +/** @hide */ +interface INotificationAssistant +{ + void onListenerConnected(in NotificationRankingUpdate update); + void onNotificationPosted(in IStatusBarNotificationHolder notificationHolder, + in NotificationRankingUpdate update); + void onNotificationRankingUpdate(in NotificationRankingUpdate update); + void onListenerHintsChanged(int hints); + void onInterruptionFilterChanged(int interruptionFilter); + NotificationAdjustment onNotificationEnqueued(in IStatusBarNotificationHolder notificationHolder, int importance, boolean user); + void onNotificationVisibilityChanged(String key, long time, boolean visible); + void onNotificationClick(String key, long time); + void onNotificationActionClick(String key, long time, int actionIndex); + void onNotificationRemoved(String key, long time, int reason); +}
\ No newline at end of file diff --git a/core/java/android/service/notification/NotificationAdjustment.aidl b/core/java/android/service/notification/NotificationAdjustment.aidl new file mode 100644 index 000000000000..805fe2c67695 --- /dev/null +++ b/core/java/android/service/notification/NotificationAdjustment.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2015, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.notification; + +parcelable NotificationAdjustment;
\ No newline at end of file diff --git a/core/java/android/service/notification/NotificationAdjustment.java b/core/java/android/service/notification/NotificationAdjustment.java new file mode 100644 index 000000000000..c5f0db9bf5f1 --- /dev/null +++ b/core/java/android/service/notification/NotificationAdjustment.java @@ -0,0 +1,57 @@ +package android.service.notification; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +public class NotificationAdjustment implements Parcelable { + int mImportance; + CharSequence mExplanation; + Uri mReference; + + /** + * Create a notification importance adjustment. + * + * @param importance The final importance of the notification. + * @param explanation A human-readable justification for the adjustment. + * @param reference A reference to an external object that augments the + * explanation, such as a + * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}, + * or null. + */ + public NotificationAdjustment(int importance, CharSequence explanation, Uri reference) { + mImportance = importance; + mExplanation = explanation; + mReference = reference; + } + + private NotificationAdjustment(Parcel source) { + this(source.readInt(), source.readCharSequence(), + (Uri) source.readParcelable(NotificationAdjustment.class.getClassLoader())); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mImportance); + dest.writeCharSequence(mExplanation); + dest.writeParcelable(mReference, 0); + } + + public static final Parcelable.Creator<NotificationAdjustment> CREATOR + = new Parcelable.Creator<NotificationAdjustment>() { + @Override + public NotificationAdjustment createFromParcel(Parcel source) { + return new NotificationAdjustment(source); + } + + @Override + public NotificationAdjustment[] newArray(int size) { + return new NotificationAdjustment[size]; + } + }; +} diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index 5d1317cf7d24..7ce5e1f9da9c 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -16,8 +16,12 @@ package android.service.notification; +import android.annotation.SdkConstant; import android.app.Notification; +import android.content.Intent; import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; /** * A service that helps the user manage notifications by modifying the @@ -35,6 +39,13 @@ import android.net.Uri; * </service></pre> */ public abstract class NotificationAssistantService extends NotificationListenerService { + /** + * The {@link Intent} that must be declared as handled by the service. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE + = "android.service.notification.NotificationAssistantService"; + /** Notification was canceled by the status bar reporting a click. */ public static final int REASON_DELEGATE_CLICK = 1; @@ -74,28 +85,6 @@ public abstract class NotificationAssistantService extends NotificationListenerS /** Notification was canceled because it was an invisible member of a group. */ public static final int REASON_GROUP_OPTIMIZATION = 13; - public class Adjustment { - int mImportance; - CharSequence mExplanation; - Uri mReference; - - /** - * Create a notification importance adjustment. - * - * @param importance The final importance of the notification. - * @param explanation A human-readable justification for the adjustment. - * @param reference A reference to an external object that augments the - * explanation, such as a - * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}, - * or null. - */ - public Adjustment(int importance, CharSequence explanation, Uri reference) { - mImportance = importance; - mExplanation = explanation; - mReference = reference; - } - } - /** * A notification was posted by an app. Called before alert. * @@ -104,7 +93,7 @@ public abstract class NotificationAssistantService extends NotificationListenerS * @param user true if the initial importance reflects an explicit user preference. * @return an adjustment or null to take no action, within 100ms. */ - abstract public Adjustment onNotificationEnqueued(StatusBarNotification sbn, + abstract public NotificationAdjustment onNotificationEnqueued(StatusBarNotification sbn, int importance, boolean user); /** @@ -161,7 +150,7 @@ public abstract class NotificationAssistantService extends NotificationListenerS * @param key the notification key * @param adjustment the new importance with an explanation */ - public final void adjustImportance(String key, Adjustment adjustment) + public final void adjustImportance(String key, NotificationAdjustment adjustment) { // TODO: pack up the adjustment and send it to the NotificationManager. } diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java index fd2d5b05b5f0..d8787b4caca7 100644 --- a/core/java/android/service/quicksettings/TileService.java +++ b/core/java/android/service/quicksettings/TileService.java @@ -77,6 +77,15 @@ public class TileService extends Service { private Tile mTile; private IBinder mToken; + @Override + public void onDestroy() { + if (mListening) { + onStopListening(); + mListening = false; + } + super.onDestroy(); + } + /** * Called when the user adds this tile to Quick Settings. * <p/> @@ -197,10 +206,10 @@ public class TileService extends Service { mTile = (Tile) msg.obj; break; case MSG_TILE_ADDED: - TileService.this.onTileRemoved(); + TileService.this.onTileAdded(); break; case MSG_TILE_REMOVED: - TileService.this.onTileAdded(); + TileService.this.onTileRemoved(); break; case MSG_STOP_LISTENING: if (mListening) { diff --git a/core/java/android/util/StateSet.java b/core/java/android/util/StateSet.java index c2a6a7ad4349..051de8a190e1 100644 --- a/core/java/android/util/StateSet.java +++ b/core/java/android/util/StateSet.java @@ -260,6 +260,12 @@ public class StateSet { case R.attr.state_enabled: sb.append("E "); break; + case R.attr.state_checked: + sb.append("C "); + break; + case R.attr.state_activated: + sb.append("A "); + break; } } diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java index ea0873d6fb1b..4888877cb28b 100644 --- a/core/java/android/view/ContextThemeWrapper.java +++ b/core/java/android/view/ContextThemeWrapper.java @@ -104,11 +104,15 @@ public class ContextThemeWrapper extends ContextWrapper { @Override public AssetManager getAssets() { // Ensure we're returning assets with the correct configuration. - return getResources().getAssets(); + return getResourcesInternal().getAssets(); } @Override public Resources getResources() { + return getResourcesInternal(); + } + + private Resources getResourcesInternal() { if (mResources == null) { if (mOverrideConfiguration == null) { mResources = super.getResources(); @@ -117,7 +121,6 @@ public class ContextThemeWrapper extends ContextWrapper { mResources = resContext.getResources(); } } - return mResources; } diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java index 34835f4a8070..5903d4a7c444 100644 --- a/core/java/android/view/DragEvent.java +++ b/core/java/android/view/DragEvent.java @@ -21,6 +21,8 @@ import android.content.ClipDescription; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.view.IDropPermissions; + //TODO: Improve Javadoc /** * Represents an event that is sent out by the system at various times during a drag and drop @@ -128,7 +130,7 @@ public class DragEvent implements Parcelable { float mX, mY; ClipDescription mClipDescription; ClipData mClipData; - DropPermissionHolder mDropPermissionHolder; + IDropPermissions mDropPermissions; Object mLocalState; boolean mDragResult; @@ -146,7 +148,7 @@ public class DragEvent implements Parcelable { * Action constant returned by {@link #getAction()}: Signals the start of a * drag and drop operation. The View should return {@code true} from its * {@link View#onDragEvent(DragEvent) onDragEvent()} handler method or - * {@link View.View.OnDragListener#onDrag(View,DragEvent) OnDragListener.onDrag()} listener + * {@link View.OnDragListener#onDrag(View,DragEvent) OnDragListener.onDrag()} listener * if it can accept a drop. The onDragEvent() or onDrag() methods usually inspect the metadata * from {@link #getClipDescription()} to determine if they can accept the data contained in * this drag. For an operation that doesn't represent data transfer, these methods may @@ -190,7 +192,7 @@ public class DragEvent implements Parcelable { * within the View object's bounding box. * <p> * The View should return {@code true} from its {@link View#onDragEvent(DragEvent)} - * handler or {@link View.View.OnDragListener#onDrag(View,DragEvent) OnDragListener.onDrag()} + * handler or {@link View.OnDragListener#onDrag(View,DragEvent) OnDragListener.onDrag()} * listener if it accepted the drop, and {@code false} if it ignored the drop. * </p> * <p> @@ -255,13 +257,13 @@ public class DragEvent implements Parcelable { } private void init(int action, float x, float y, ClipDescription description, ClipData data, - DropPermissionHolder dropPermissionHolder, Object localState, boolean result) { + IDropPermissions dropPermissions, Object localState, boolean result) { mAction = action; mX = x; mY = y; mClipDescription = description; mClipData = data; - mDropPermissionHolder = dropPermissionHolder; + mDropPermissions = dropPermissions; mLocalState = localState; mDragResult = result; } @@ -272,13 +274,13 @@ public class DragEvent implements Parcelable { /** @hide */ public static DragEvent obtain(int action, float x, float y, Object localState, - ClipDescription description, ClipData data, DropPermissionHolder dropPermissionHolder, + ClipDescription description, ClipData data, IDropPermissions dropPermissions, boolean result) { final DragEvent ev; synchronized (gRecyclerLock) { if (gRecyclerTop == null) { ev = new DragEvent(); - ev.init(action, x, y, description, data, dropPermissionHolder, localState, result); + ev.init(action, x, y, description, data, dropPermissions, localState, result); return ev; } ev = gRecyclerTop; @@ -289,7 +291,7 @@ public class DragEvent implements Parcelable { ev.mRecycled = false; ev.mNext = null; - ev.init(action, x, y, description, data, dropPermissionHolder, localState, result); + ev.init(action, x, y, description, data, dropPermissions, localState, result); return ev; } @@ -297,7 +299,7 @@ public class DragEvent implements Parcelable { /** @hide */ public static DragEvent obtain(DragEvent source) { return obtain(source.mAction, source.mX, source.mY, source.mLocalState, - source.mClipDescription, source.mClipData, source.mDropPermissionHolder, + source.mClipDescription, source.mClipData, source.mDropPermissions, source.mDragResult); } @@ -363,14 +365,19 @@ public class DragEvent implements Parcelable { } /** - * Returns the {@link android.view.DropPermissionHolder} object that can be used by the drag - * listener to request and release the permissions for the content URIs contained in the - * {@link android.content.ClipData} object associated with this event. + * Requests the permissions for the content URIs contained in {@link android.content.ClipData} + * object associated with this event. Which permissions will be granted is defined by the set of + * flags passed to {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)}. + * Returns the {@link DropPermissions} object that can be used by the receiving app to release + * the permissions for the content URIs when they are no longer needed. * This method only returns valid data if the event action is {@link #ACTION_DROP}. - * @return The DropPermissionHolder object used to handle content URI permissions. + * @return The DropPermissions object used to control access to the content URIs. */ - public DropPermissionHolder getDropPermissionHolder() { - return mDropPermissionHolder; + public DropPermissions requestDropPermissions() { + if (mDropPermissions == null) { + return null; + } + return new DropPermissions(mDropPermissions); } /** @@ -493,11 +500,11 @@ public class DragEvent implements Parcelable { dest.writeInt(1); mClipDescription.writeToParcel(dest, flags); } - if (mDropPermissionHolder == null) { + if (mDropPermissions == null) { dest.writeInt(0); } else { dest.writeInt(1); - mDropPermissionHolder.writeToParcel(dest, flags); + dest.writeStrongBinder(mDropPermissions.asBinder()); } } @@ -519,7 +526,7 @@ public class DragEvent implements Parcelable { event.mClipDescription = ClipDescription.CREATOR.createFromParcel(in); } if (in.readInt() != 0) { - event.mDropPermissionHolder = DropPermissionHolder.CREATOR.createFromParcel(in); + event.mDropPermissions = IDropPermissions.Stub.asInterface(in.readStrongBinder());; } return event; } diff --git a/core/java/android/view/DropPermissionHolder.java b/core/java/android/view/DropPermissionHolder.java deleted file mode 100644 index 993e67a2578e..000000000000 --- a/core/java/android/view/DropPermissionHolder.java +++ /dev/null @@ -1,161 +0,0 @@ -package android.view; - -import android.app.IActivityManager; -import android.content.ClipData; -import android.content.Intent; -import android.net.Uri; -import android.os.Binder; -import android.os.IBinder; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.RemoteException; -import com.android.internal.view.IDropPermissionHolder; - -import java.util.ArrayList; - -public class DropPermissionHolder implements Parcelable { - - IDropPermissionHolder mDropPermissionHolder; - - /** - * Create a new DropPermissionHolder to be passed to the client with a DragEvent. - * - * @hide - */ - public DropPermissionHolder(ClipData clipData, IActivityManager activityManager, - int sourceUid, String targetPackage, int mode, int sourceUserId, int targetUserId) { - mDropPermissionHolder = new LocalDropPermissionHolder(clipData, activityManager, - sourceUid, targetPackage, mode, sourceUserId, targetUserId); - } - - private class LocalDropPermissionHolder extends IDropPermissionHolder.Stub { - - private final IActivityManager mActivityManager; - private final int mSourceUid; - private final String mTargetPackage; - private final int mMode; - private final int mSourceUserId; - private final int mTargetUserId; - - IBinder mPermissionOwner = null; - - final private ArrayList<Uri> mUris = new ArrayList<Uri>(); - - LocalDropPermissionHolder(ClipData clipData, IActivityManager activityManager, - int sourceUid, String targetPackage, int mode, int sourceUserId, int targetUserId) { - mActivityManager = activityManager; - mSourceUid = sourceUid; - mTargetPackage = targetPackage; - mMode = mode; - mSourceUserId = sourceUserId; - mTargetUserId = targetUserId; - - int N = clipData.getItemCount(); - for (int i = 0; i != N; ++i) { - ClipData.Item item = clipData.getItemAt(i); - - if (item.getUri() != null) { - mUris.add(item.getUri()); - } - - Intent intent = item.getIntent(); - if (intent != null && intent.getData() != null) { - mUris.add(intent.getData()); - } - } - } - - @Override - public void grant() throws RemoteException { - if (mPermissionOwner != null) { - return; - } - - mPermissionOwner = mActivityManager.newUriPermissionOwner("drop"); - - long origId = Binder.clearCallingIdentity(); - try { - for (Uri mUri : mUris) { - mActivityManager.grantUriPermissionFromOwner( - mPermissionOwner, mSourceUid, mTargetPackage, mUri, mMode, - mSourceUserId, mTargetUserId); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - - } - - @Override - public void revoke() throws RemoteException { - if (mPermissionOwner == null) { - return; - } - - for (Uri mUri : mUris) { - mActivityManager.revokeUriPermissionFromOwner( - mPermissionOwner, mUri, mMode, mSourceUserId); - } - - mPermissionOwner = null; - } - } - - /** - * Request permissions granted by the activity which started the drag. - */ - public void grant() { - try { - mDropPermissionHolder.grant(); - } catch (RemoteException e) { - } - } - - /** - * Revoke permissions granted by the {@link #grant()} call. - */ - public void revoke() { - try { - mDropPermissionHolder.revoke(); - } catch (RemoteException e) { - } - } - - /** - * Returns information about the {@link android.os.Parcel} representation of this - * DropPermissionHolder object. - * @return Information about the {@link android.os.Parcel} representation. - */ - @Override - public int describeContents() { - return 0; - } - - /** - * Creates a {@link android.os.Parcel} object from this DropPermissionHolder object. - * @param dest A {@link android.os.Parcel} object in which to put the DropPermissionHolder - * object. - * @param flags Flags to store in the Parcel. - */ - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeStrongBinder(mDropPermissionHolder.asBinder()); - } - - DropPermissionHolder(Parcel in) { - mDropPermissionHolder = IDropPermissionHolder.Stub.asInterface(in.readStrongBinder()); - } - - /** - * A container for creating a DropPermissionHolder from a Parcel. - */ - public static final Parcelable.Creator<DropPermissionHolder> CREATOR - = new Parcelable.Creator<DropPermissionHolder>() { - public DropPermissionHolder createFromParcel(Parcel in) { - return new DropPermissionHolder(in); - } - public DropPermissionHolder[] newArray(int size) { - return new DropPermissionHolder[size]; - } - }; -} diff --git a/core/java/android/view/DropPermissions.java b/core/java/android/view/DropPermissions.java new file mode 100644 index 000000000000..780461fe69a3 --- /dev/null +++ b/core/java/android/view/DropPermissions.java @@ -0,0 +1,66 @@ +/* +** Copyright 2015, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.view; + +import android.os.RemoteException; +import com.android.internal.view.IDropPermissions; +import dalvik.system.CloseGuard; + + +public final class DropPermissions { + + private final IDropPermissions mDropPermissions; + + private final CloseGuard mCloseGuard = CloseGuard.get(); + + /** + * Create a new DropPermissions object to be passed to the client with a DragEvent. + * + * @hide + */ + DropPermissions(IDropPermissions dropPermissions) { + mDropPermissions = dropPermissions; + try { + mDropPermissions.take(); + } catch (RemoteException e) { + } + mCloseGuard.open("release"); + } + + /** + * Revoke permissions taken by {@link DragEvent#requestDropPermissions()}. + */ + public void release() { + try { + mDropPermissions.release(); + } catch (RemoteException e) { + } + mCloseGuard.close(); + } + + @Override + protected void finalize() throws Throwable { + try { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + release(); + } finally { + super.finalize(); + } + } +} diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 2e884ccc7b25..251f4c802ce6 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1183,6 +1183,13 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH = 0x00008000; /** + * Flag to indicate that this child window should always be laid-out in the parent + * frame regardless of the current windowing mode configuration. + * @hide + */ + public static final int PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME = 0x00010000; + + /** * Control flags that are private to the platform. * @hide */ diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index 01d15664f24a..6aa5e2f573bd 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -134,6 +134,25 @@ public final class WebViewFactory { private static String TAG_SIGNATURE = "signature"; /** + * Reads all signatures at the current depth (within the current provider) from the XML parser. + */ + private static String[] readSignatures(XmlResourceParser parser) throws IOException, + XmlPullParserException { + List<String> signatures = new ArrayList<String>(); + int outerDepth = parser.getDepth(); + while(XmlUtils.nextElementWithin(parser, outerDepth)) { + if (parser.getName().equals(TAG_SIGNATURE)) { + // Parse the value within the signature tag + String signature = parser.nextText(); + signatures.add(signature); + } else { + Log.e(LOGTAG, "Found an element in a webview provider that is not a signature"); + } + } + return signatures.toArray(new String[signatures.size()]); + } + + /** * Returns all packages declared in the framework resources as potential WebView providers. * @hide * */ @@ -161,9 +180,9 @@ public final class WebViewFactory { throw new MissingWebViewPackageException( "WebView provider in framework resources missing description"); } - String signature = parser.getAttributeValue(null, TAG_SIGNATURE); webViewProviders.add( - new WebViewProviderInfo(packageName, description, signature)); + new WebViewProviderInfo(packageName, description, + readSignatures(parser))); } else { Log.e(LOGTAG, "Found an element that is not a webview provider"); diff --git a/core/java/android/webkit/WebViewProviderInfo.java b/core/java/android/webkit/WebViewProviderInfo.java index d5e3a230919b..7bad6521b734 100644 --- a/core/java/android/webkit/WebViewProviderInfo.java +++ b/core/java/android/webkit/WebViewProviderInfo.java @@ -40,10 +40,10 @@ public class WebViewProviderInfo implements Parcelable { public WebViewPackageNotFoundException(Exception e) { super(e); } } - public WebViewProviderInfo(String packageName, String description, String signature) { + public WebViewProviderInfo(String packageName, String description, String[] signatures) { this.packageName = packageName; this.description = description; - this.signature = signature; + this.signatures = signatures; } private boolean hasValidSignature() { @@ -53,7 +53,7 @@ public class WebViewProviderInfo implements Parcelable { try { // If no signature is declared, instead check whether the package is included in the // system. - if (signature == null) + if (signatures == null || signatures.length == 0) return getPackageInfo().applicationInfo.isSystemApp(); packageSignatures = getPackageInfo().signatures; @@ -62,8 +62,15 @@ public class WebViewProviderInfo implements Parcelable { } if (packageSignatures.length != 1) return false; - final byte[] releaseSignature = Base64.decode(signature, Base64.DEFAULT); - return Arrays.equals(releaseSignature, packageSignatures[0].toByteArray()); + + final byte[] packageSignature = packageSignatures[0].toByteArray(); + // Return whether the package signature matches any of the valid signatures + for (String signature : signatures) { + final byte[] validSignature = Base64.decode(signature, Base64.DEFAULT); + if (Arrays.equals(packageSignature, validSignature)) + return true; + } + return false; } /** @@ -109,7 +116,7 @@ public class WebViewProviderInfo implements Parcelable { private WebViewProviderInfo(Parcel in) { packageName = in.readString(); description = in.readString(); - signature = in.readString(); + signatures = in.createStringArray(); packageInfo = null; } @@ -122,14 +129,14 @@ public class WebViewProviderInfo implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeString(packageName); out.writeString(description); - out.writeString(signature); + out.writeStringArray(signatures); } // fields read from framework resource public String packageName; public String description; - private String signature; + private String[] signatures; private PackageInfo packageInfo; // flags declaring we want extra info from the package manager diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 2d1f85508968..15cea772d418 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -72,6 +72,7 @@ import android.util.Log; import android.util.SparseArray; import android.view.ActionMode; import android.view.ActionMode.Callback; +import android.view.ContextMenu; import android.view.DisplayListCanvas; import android.view.DragEvent; import android.view.Gravity; @@ -135,13 +136,16 @@ public class Editor { // Tag used when the Editor maintains its own separate UndoManager. private static final String UNDO_OWNER_TAG = "Editor"; - // Ordering constants used to place the Action Mode items in their menu. - private static final int MENU_ITEM_ORDER_CUT = 1; - private static final int MENU_ITEM_ORDER_COPY = 2; - private static final int MENU_ITEM_ORDER_PASTE = 3; - private static final int MENU_ITEM_ORDER_SHARE = 4; - private static final int MENU_ITEM_ORDER_SELECT_ALL = 5; - private static final int MENU_ITEM_ORDER_REPLACE = 6; + // Ordering constants used to place the Action Mode or context menu items in their menu. + private static final int MENU_ITEM_ORDER_UNDO = 1; + private static final int MENU_ITEM_ORDER_REDO = 2; + private static final int MENU_ITEM_ORDER_CUT = 3; + private static final int MENU_ITEM_ORDER_COPY = 4; + private static final int MENU_ITEM_ORDER_PASTE = 5; + private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 6; + private static final int MENU_ITEM_ORDER_SHARE = 7; + private static final int MENU_ITEM_ORDER_SELECT_ALL = 8; + private static final int MENU_ITEM_ORDER_REPLACE = 9; private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 10; // Each Editor manages its own undo stack. @@ -184,6 +188,7 @@ public class Editor { boolean mDiscardNextActionUp; boolean mIgnoreActionUpEvent; + private boolean mIgnoreNextMouseActionUpOrDown; long mShowCursor; Blink mBlink; @@ -209,6 +214,8 @@ public class Editor { boolean mPreserveDetachedSelection; boolean mTemporaryDetach; + boolean mIsBeingLongClicked; + SuggestionsPopupWindow mSuggestionsPopupWindow; SuggestionRangeSpan mSuggestionRangeSpan; Runnable mShowSuggestionRunnable; @@ -224,6 +231,7 @@ public class Editor { private PositionListener mPositionListener; float mLastDownPositionX, mLastDownPositionY; + private float mContextMenuAnchorX, mContextMenuAnchorY; Callback mCustomSelectionActionModeCallback; Callback mCustomInsertionActionModeCallback; @@ -239,6 +247,9 @@ public class Editor { // Only for mouse input. private static final int TAP_STATE_TRIPLE_CLICK = 3; + // The button state as of the last time #onTouchEvent is called. + private int mLastButtonState; + private Runnable mInsertionActionModeRunnable; // The span controller helps monitoring the changes to which the Editor needs to react: @@ -1314,7 +1325,33 @@ public class Editor { } } + private boolean shouldFilterOutTouchEvent(MotionEvent event) { + if (!event.isFromSource(InputDevice.SOURCE_MOUSE)) { + return false; + } + final boolean primaryButtonStateChanged = + ((mLastButtonState ^ event.getButtonState()) & MotionEvent.BUTTON_PRIMARY) != 0; + final int action = event.getActionMasked(); + if ((action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_UP) + && !primaryButtonStateChanged) { + return true; + } + if (action == MotionEvent.ACTION_MOVE + && !event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { + return true; + } + return false; + } + void onTouchEvent(MotionEvent event) { + final boolean filterOutEvent = shouldFilterOutTouchEvent(event); + mLastButtonState = event.getButtonState(); + if (filterOutEvent) { + if (event.getActionMasked() == MotionEvent.ACTION_UP) { + mDiscardNextActionUp = true; + } + return; + } updateTapState(event); updateFloatingToolbarVisibility(event); @@ -2318,6 +2355,84 @@ public class Editor { text.setSpan(mSpanController, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); } + void setContextMenuAnchor(float x, float y) { + mContextMenuAnchorX = x; + mContextMenuAnchorY = y; + } + + void onCreateContextMenu(ContextMenu menu) { + if (mIsBeingLongClicked || Float.isNaN(mContextMenuAnchorX) + || Float.isNaN(mContextMenuAnchorY)) { + return; + } + final int offset = mTextView.getOffsetForPosition(mContextMenuAnchorX, mContextMenuAnchorY); + if (offset == -1) { + return; + } + final boolean isOnSelection = mTextView.hasSelection() + && offset >= mTextView.getSelectionStart() && offset <= mTextView.getSelectionEnd(); + if (!isOnSelection) { + // Right clicked position is not on the selection. Remove the selection and move the + // cursor to the right clicked position. + stopTextActionMode(); + Selection.setSelection((Spannable) mTextView.getText(), offset); + } + + // TODO: Add suggestions in the context menu. + + menu.add(Menu.NONE, TextView.ID_UNDO, MENU_ITEM_ORDER_UNDO, + com.android.internal.R.string.undo) + .setAlphabeticShortcut('z') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setEnabled(mTextView.canUndo()); + menu.add(Menu.NONE, TextView.ID_REDO, MENU_ITEM_ORDER_REDO, + com.android.internal.R.string.redo) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setEnabled(mTextView.canRedo()); + + menu.add(Menu.NONE, TextView.ID_CUT, MENU_ITEM_ORDER_CUT, + com.android.internal.R.string.cut) + .setAlphabeticShortcut('x') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setEnabled(mTextView.canCut()); + menu.add(Menu.NONE, TextView.ID_COPY, MENU_ITEM_ORDER_COPY, + com.android.internal.R.string.copy) + .setAlphabeticShortcut('c') + .setOnMenuItemClickListener(mOnContextMenuItemClickListener) + .setEnabled(mTextView.canCopy()); + menu.add(Menu.NONE, TextView.ID_PASTE, MENU_ITEM_ORDER_PASTE, + com.android.internal.R.string.paste) + .setAlphabeticShortcut('v') + .setEnabled(mTextView.canPaste()) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + menu.add(Menu.NONE, TextView.ID_PASTE, MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT, + com.android.internal.R.string.paste_as_plain_text) + .setEnabled(mTextView.canPaste()) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + menu.add(Menu.NONE, TextView.ID_SHARE, MENU_ITEM_ORDER_SHARE, + com.android.internal.R.string.share) + .setEnabled(mTextView.canShare()) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + menu.add(Menu.NONE, TextView.ID_SELECT_ALL, MENU_ITEM_ORDER_SELECT_ALL, + com.android.internal.R.string.selectAll) + .setAlphabeticShortcut('a') + .setEnabled(mTextView.canSelectAllText()) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + + mPreserveDetachedSelection = true; + } + + private final MenuItem.OnMenuItemClickListener mOnContextMenuItemClickListener = + new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + if (mProcessTextIntentActionsHandler.performMenuItemAction(item)) { + return true; + } + return mTextView.onTextContextMenuItem(item.getItemId()); + } + }; + /** * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related * pop-up should be displayed. @@ -2710,6 +2825,9 @@ public class Editor { } public void hide() { + if (!isShowing()) { + return; + } mPopupWindow.dismiss(); getPositionListener().removeSubscriber(this); } @@ -2759,8 +2877,10 @@ public class Editor { @Override public void dismiss() { + if (!isShowing()) { + return; + } super.dismiss(); - getPositionListener().removeSubscriber(SuggestionsPopupWindow.this); // Safe cast since show() checks that mTextView.getText() is an Editable diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index ad939be13a44..f6e618657758 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -359,7 +359,6 @@ public class LinearLayout extends ViewGroup { final int count = getVirtualChildCount(); for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); - if (child != null && child.getVisibility() != GONE) { if (hasDividerBeforeChildAt(i)) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); @@ -388,7 +387,7 @@ public class LinearLayout extends ViewGroup { */ private View getLastNonGoneChild() { for (int i = getVirtualChildCount() - 1; i >= 0; i--) { - View child = getVirtualChildAt(i); + final View child = getVirtualChildAt(i); if (child != null && child.getVisibility() != GONE) { return child; } @@ -401,7 +400,6 @@ public class LinearLayout extends ViewGroup { final boolean isLayoutRtl = isLayoutRtl(); for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); - if (child != null && child.getVisibility() != GONE) { if (hasDividerBeforeChildAt(i)) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); @@ -588,8 +586,9 @@ public class LinearLayout extends ViewGroup { * for an example.</p> * * @param index the child's index - * @return the child at the specified index + * @return the child at the specified index, may be {@code null} */ + @Nullable View getVirtualChildAt(int index) { return getChildAt(index); } @@ -670,7 +669,7 @@ public class LinearLayout extends ViewGroup { */ private boolean allViewsAreGoneBefore(int childIndex) { for (int i = childIndex - 1; i >= 0; i--) { - View child = getVirtualChildAt(i); + final View child = getVirtualChildAt(i); if (child != null && child.getVisibility() != GONE) { return false; } @@ -715,7 +714,6 @@ public class LinearLayout extends ViewGroup { // See how tall everyone is. Also remember max width. for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); - if (child == null) { mTotalLength += measureNullChild(i); continue; @@ -837,7 +835,6 @@ public class LinearLayout extends ViewGroup { for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); - if (child == null) { mTotalLength += measureNullChild(i); continue; @@ -943,7 +940,6 @@ public class LinearLayout extends ViewGroup { if (useLargestChild && heightMode != MeasureSpec.EXACTLY) { for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); - if (child == null || child.getVisibility() == View.GONE) { continue; } @@ -986,7 +982,7 @@ public class LinearLayout extends ViewGroup { MeasureSpec.EXACTLY); for (int i = 0; i< count; ++i) { final View child = getVirtualChildAt(i); - if (child.getVisibility() != GONE) { + if (child != null && child.getVisibility() != GONE) { LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams()); if (lp.width == LayoutParams.MATCH_PARENT) { @@ -1053,7 +1049,6 @@ public class LinearLayout extends ViewGroup { // See how wide everyone is. Also remember max height. for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); - if (child == null) { mTotalLength += measureNullChild(i); continue; @@ -1211,7 +1206,6 @@ public class LinearLayout extends ViewGroup { for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); - if (child == null) { mTotalLength += measureNullChild(i); continue; @@ -1357,7 +1351,6 @@ public class LinearLayout extends ViewGroup { if (useLargestChild && widthMode != MeasureSpec.EXACTLY) { for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); - if (child == null || child.getVisibility() == View.GONE) { continue; } @@ -1402,7 +1395,7 @@ public class LinearLayout extends ViewGroup { MeasureSpec.EXACTLY); for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); - if (child.getVisibility() != GONE) { + if (child != null && child.getVisibility() != GONE) { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); if (lp.height == LayoutParams.MATCH_PARENT) { @@ -1662,9 +1655,8 @@ public class LinearLayout extends ViewGroup { } for (int i = 0; i < count; i++) { - int childIndex = start + dir * i; + final int childIndex = start + dir * i; final View child = getVirtualChildAt(childIndex); - if (child == null) { childLeft += measureNullChild(childIndex); } else if (child.getVisibility() != GONE) { diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index f4c343a9509a..19e290b71a97 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -16,6 +16,7 @@ package android.widget; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH; import com.android.internal.R; @@ -1313,7 +1314,8 @@ public class PopupWindow { p.width = mLastWidth = mWidth; } - p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH; + p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH + | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; // Used for debugging. p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java index f7f9c9177034..22931fcb3e97 100644 --- a/core/java/android/widget/TableRow.java +++ b/core/java/android/widget/TableRow.java @@ -98,7 +98,7 @@ public class TableRow extends LinearLayout { * {@hide} */ void setColumnCollapsed(int columnIndex, boolean collapsed) { - View child = getVirtualChildAt(columnIndex); + final View child = getVirtualChildAt(columnIndex); if (child != null) { child.setVisibility(collapsed ? GONE : VISIBLE); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 17c803f76cbb..d46c6f92b762 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -111,6 +111,7 @@ import android.util.TypedValue; import android.view.AccessibilityIterators.TextSegmentIterator; import android.view.ActionMode; import android.view.Choreographer; +import android.view.ContextMenu; import android.view.DragEvent; import android.view.Gravity; import android.view.HapticFeedbackConstants; @@ -393,7 +394,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mOverride = false; } - public void resolveWithLayoutDirection(int layoutDirection) { + /** + * Updates the list of displayed drawables to account for the current + * layout direction. + * + * @param layoutDirection the current layout direction + * @return {@code true} if the displayed drawables changed + */ + public boolean resolveWithLayoutDirection(int layoutDirection) { + final Drawable previousLeft = mShowing[Drawables.LEFT]; + final Drawable previousRight = mShowing[Drawables.RIGHT]; + // First reset "left" and "right" drawables to their initial values mShowing[Drawables.LEFT] = mDrawableLeftInitial; mShowing[Drawables.RIGHT] = mDrawableRightInitial; @@ -441,16 +452,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; } } + applyErrorDrawableIfNeeded(layoutDirection); - updateDrawablesLayoutDirection(layoutDirection); - } - private void updateDrawablesLayoutDirection(int layoutDirection) { - for (Drawable dr : mShowing) { - if (dr != null) { - dr.setLayoutDirection(layoutDirection); - } - } + return mShowing[Drawables.LEFT] != previousLeft + || mShowing[Drawables.RIGHT] != previousRight; } public void setErrorDrawable(Drawable dr, TextView tv) { @@ -5957,6 +5963,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public PointerIcon getPointerIcon(MotionEvent event, float x, float y) { + if (mText instanceof Spannable && mLinksClickable) { + final int offset = getOffsetForPosition(x, y); + final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset, + ClickableSpan.class); + if (clickables.length > 0) { + return PointerIcon.getSystemIcon(mContext, PointerIcon.STYLE_HAND); + } + } if (isTextSelectable() || isTextEditable()) { return PointerIcon.getSystemIcon(mContext, PointerIcon.STYLE_TEXT); } @@ -8489,6 +8503,29 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return super.onGenericMotionEvent(event); } + @Override + protected void onCreateContextMenu(ContextMenu menu) { + if (mEditor != null) { + mEditor.onCreateContextMenu(menu); + } + } + + @Override + public boolean showContextMenu() { + if (mEditor != null) { + mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); + } + return super.showContextMenu(); + } + + @Override + public boolean showContextMenu(float x, float y) { + if (mEditor != null) { + mEditor.setContextMenuAnchor(x, y); + } + return super.showContextMenu(x, y); + } + /** * @return True iff this TextView contains a text that can be edited, or if this is * a selectable TextView. @@ -9390,12 +9427,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public boolean performLongClick() { boolean handled = false; + if (mEditor != null) { + mEditor.mIsBeingLongClicked = true; + } + if (super.performLongClick()) { handled = true; } if (mEditor != null) { handled |= mEditor.performLongClick(handled); + mEditor.mIsBeingLongClicked = false; } if (handled) { @@ -9809,7 +9851,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Resolve drawables if (mDrawables != null) { - mDrawables.resolveWithLayoutDirection(layoutDirection); + if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { + prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); + prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); + applyCompoundDrawableTint(); + } + } + } + + /** + * Prepares a drawable for display by propagating layout direction and + * drawable state. + * + * @param dr the drawable to prepare + */ + private void prepareDrawableForDisplay(@Nullable Drawable dr) { + if (dr == null) { + return; + } + + dr.setLayoutDirection(getLayoutDirection()); + + if (dr.isStateful()) { + dr.setState(getDrawableState()); + dr.jumpToCurrentState(); } } diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java index 66374a6bf22c..27c3b724df7e 100644 --- a/core/java/com/android/internal/app/AlertController.java +++ b/core/java/com/android/internal/app/AlertController.java @@ -378,6 +378,7 @@ public class AlertController { if (mIconView != null) { if (icon != null) { + mIconView.setVisibility(View.VISIBLE); mIconView.setImageDrawable(icon); } else { mIconView.setVisibility(View.GONE); diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java index 9d128035a250..4b6e7e4a03d2 100644 --- a/core/java/com/android/internal/app/ToolbarActionBar.java +++ b/core/java/com/android/internal/app/ToolbarActionBar.java @@ -29,10 +29,14 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.View.OnFocusChangeListener; +import android.view.ViewGroup; +import android.view.ViewParent; import android.view.Window; import android.view.WindowCallbackWrapper; import android.widget.SpinnerAdapter; import android.widget.Toolbar; + import com.android.internal.view.menu.MenuBuilder; import com.android.internal.view.menu.MenuPresenter; import com.android.internal.widget.DecorToolbar; @@ -499,6 +503,12 @@ public class ToolbarActionBar extends ActionBar { } } + /** @hide */ + @Override + public boolean requestFocus() { + return requestFocus(mDecorToolbar.getViewGroup()); + } + private class ToolbarCallbackWrapper extends WindowCallbackWrapper { public ToolbarCallbackWrapper(Window.Callback wrapped) { super(wrapped); diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java index 05cfd81766ab..c6bf1b46becf 100644 --- a/core/java/com/android/internal/app/WindowDecorActionBar.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -18,6 +18,8 @@ package com.android.internal.app; import android.animation.ValueAnimator; import android.content.res.TypedArray; +import android.view.View.OnFocusChangeListener; +import android.view.ViewGroup; import android.view.ViewParent; import android.widget.Toolbar; @@ -950,6 +952,12 @@ public class WindowDecorActionBar extends ActionBar implements return false; } + /** @hide */ + @Override + public boolean requestFocus() { + return requestFocus(mDecorToolbar.getViewGroup()); + } + /** * @hide */ diff --git a/core/java/com/android/internal/view/IDropPermissionHolder.aidl b/core/java/com/android/internal/view/IDropPermissions.aidl index e60ab0e516e5..86d27e7c6f1f 100644 --- a/core/java/com/android/internal/view/IDropPermissionHolder.aidl +++ b/core/java/com/android/internal/view/IDropPermissions.aidl @@ -20,7 +20,7 @@ package com.android.internal.view; * Interface to allow a drop receiver to request permissions for URIs passed along with ClipData * contained in DragEvent. */ -interface IDropPermissionHolder { - void grant(); - void revoke(); +interface IDropPermissions { + void take(); + void release(); } diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 88b3769a1691..4a0f3fc23032 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -265,7 +265,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding if (options != NULL) { jstring mimeType = encodedFormatToString(env, codec->getEncodedFormat()); if (env->ExceptionCheck()) { - return nullObjectReturn("OOM in getEncodedFormat()"); + return nullObjectReturn("OOM in encodedFormatToString()"); } env->SetIntField(options, gOptions_widthFieldID, size.width()); env->SetIntField(options, gOptions_heightFieldID, size.height()); diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp index 492d766e6f5c..a1ba42e7ab72 100644 --- a/core/jni/android/graphics/BitmapRegionDecoder.cpp +++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp @@ -136,9 +136,6 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); colorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig); - if (kAlpha_8_SkColorType == colorType) { - colorType = kGray_8_SkColorType; - } requireUnpremul = !env->GetBooleanField(options, gOptions_premultipliedFieldID); javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); // The Java options of ditherMode and preferQualityOverSpeed are deprecated. We will @@ -189,6 +186,9 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in env->SetIntField(options, gOptions_heightFieldID, bitmap.height()); env->SetObjectField(options, gOptions_mimeFieldID, encodedFormatToString(env, brd->getEncodedFormat())); + if (env->ExceptionCheck()) { + return nullObjectReturn("OOM in encodedFormatToString()"); + } } // If we may have reused a bitmap, we need to indicate that the pixels have changed. diff --git a/core/jni/android_view_DisplayListCanvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp index 9b41eb3c659b..d2c99fd5595a 100644 --- a/core/jni/android_view_DisplayListCanvas.cpp +++ b/core/jni/android_view_DisplayListCanvas.cpp @@ -27,7 +27,13 @@ #include <SkBitmap.h> #include <SkRegion.h> +#if HWUI_NEW_OPS +#include <RecordingCanvas.h> +typedef android::uirenderer::RecordingCanvas canvas_t; +#else #include <DisplayListCanvas.h> +typedef android::uirenderer::DisplayListCanvas canvas_t; +#endif #include <Rect.h> #include <RenderNode.h> #include <CanvasProperty.h> @@ -46,7 +52,7 @@ using namespace uirenderer; static void android_view_DisplayListCanvas_insertReorderBarrier(JNIEnv* env, jobject clazz, jlong canvasPtr, jboolean reorderEnable) { - DisplayListCanvas* canvas = reinterpret_cast<DisplayListCanvas*>(canvasPtr); + canvas_t* canvas = reinterpret_cast<canvas_t*>(canvasPtr); canvas->insertReorderBarrier(reorderEnable); } @@ -56,7 +62,7 @@ static void android_view_DisplayListCanvas_insertReorderBarrier(JNIEnv* env, job static void android_view_DisplayListCanvas_callDrawGLFunction(JNIEnv* env, jobject clazz, jlong canvasPtr, jlong functorPtr) { - DisplayListCanvas* canvas = reinterpret_cast<DisplayListCanvas*>(canvasPtr); + canvas_t* canvas = reinterpret_cast<canvas_t*>(canvasPtr); Functor* functor = reinterpret_cast<Functor*>(functorPtr); canvas->callDrawGLFunction(functor); } @@ -80,7 +86,7 @@ static jint android_view_DisplayListCanvas_getMaxTextureHeight(JNIEnv* env, jobj static void android_view_DisplayListCanvas_drawRoundRectProps(JNIEnv* env, jobject clazz, jlong canvasPtr, jlong leftPropPtr, jlong topPropPtr, jlong rightPropPtr, jlong bottomPropPtr, jlong rxPropPtr, jlong ryPropPtr, jlong paintPropPtr) { - DisplayListCanvas* canvas = reinterpret_cast<DisplayListCanvas*>(canvasPtr); + canvas_t* canvas = reinterpret_cast<canvas_t*>(canvasPtr); CanvasPropertyPrimitive* leftProp = reinterpret_cast<CanvasPropertyPrimitive*>(leftPropPtr); CanvasPropertyPrimitive* topProp = reinterpret_cast<CanvasPropertyPrimitive*>(topPropPtr); CanvasPropertyPrimitive* rightProp = reinterpret_cast<CanvasPropertyPrimitive*>(rightPropPtr); @@ -93,7 +99,7 @@ static void android_view_DisplayListCanvas_drawRoundRectProps(JNIEnv* env, jobje static void android_view_DisplayListCanvas_drawCircleProps(JNIEnv* env, jobject clazz, jlong canvasPtr, jlong xPropPtr, jlong yPropPtr, jlong radiusPropPtr, jlong paintPropPtr) { - DisplayListCanvas* canvas = reinterpret_cast<DisplayListCanvas*>(canvasPtr); + canvas_t* canvas = reinterpret_cast<canvas_t*>(canvasPtr); CanvasPropertyPrimitive* xProp = reinterpret_cast<CanvasPropertyPrimitive*>(xPropPtr); CanvasPropertyPrimitive* yProp = reinterpret_cast<CanvasPropertyPrimitive*>(yPropPtr); CanvasPropertyPrimitive* radiusProp = reinterpret_cast<CanvasPropertyPrimitive*>(radiusPropPtr); @@ -107,25 +113,25 @@ static void android_view_DisplayListCanvas_drawCircleProps(JNIEnv* env, jobject static jlong android_view_DisplayListCanvas_finishRecording(JNIEnv* env, jobject clazz, jlong canvasPtr) { - DisplayListCanvas* canvas = reinterpret_cast<DisplayListCanvas*>(canvasPtr); + canvas_t* canvas = reinterpret_cast<canvas_t*>(canvasPtr); return reinterpret_cast<jlong>(canvas->finishRecording()); } static jlong android_view_DisplayListCanvas_createDisplayListCanvas(JNIEnv* env, jobject clazz, jint width, jint height) { - return reinterpret_cast<jlong>(new DisplayListCanvas(width, height)); + return reinterpret_cast<jlong>(new canvas_t(width, height)); } static void android_view_DisplayListCanvas_resetDisplayListCanvas(JNIEnv* env, jobject clazz, jlong canvasPtr, jint width, jint height) { - DisplayListCanvas* canvas = reinterpret_cast<DisplayListCanvas*>(canvasPtr); + canvas_t* canvas = reinterpret_cast<canvas_t*>(canvasPtr); canvas->reset(width, height); } static void android_view_DisplayListCanvas_drawRenderNode(JNIEnv* env, jobject clazz, jlong canvasPtr, jlong renderNodePtr) { - DisplayListCanvas* canvas = reinterpret_cast<DisplayListCanvas*>(canvasPtr); + canvas_t* canvas = reinterpret_cast<canvas_t*>(canvasPtr); RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr); canvas->drawRenderNode(renderNode); } @@ -136,7 +142,7 @@ static void android_view_DisplayListCanvas_drawRenderNode(JNIEnv* env, static void android_view_DisplayListCanvas_drawLayer(JNIEnv* env, jobject clazz, jlong canvasPtr, jlong layerPtr) { - DisplayListCanvas* canvas = reinterpret_cast<DisplayListCanvas*>(canvasPtr); + canvas_t* canvas = reinterpret_cast<canvas_t*>(canvasPtr); DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr); canvas->drawLayer(layer); } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 313987a9ff36..c03d4711120e 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2370,7 +2370,7 @@ <!-- Set initial MaxRetry value for operators --> <integer name="config_mdc_initial_max_retry">1</integer> - <!-- The OEM specified sensor type for the gesture to launch the camear app. --> + <!-- The OEM specified sensor type for the gesture to launch the camera app. --> <integer name="config_cameraLaunchGestureSensorType">-1</integer> <!-- The OEM specified sensor string type for the gesture to launch camera app, this value must match the value of config_cameraLaunchGestureSensorType in OEM's HAL --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 4d967ff45513..4843879a2dba 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2453,6 +2453,9 @@ <!-- Item on EditText context menu. This action is used to paste from the clipboard into the eidt field --> <string name="paste">Paste</string> + <!-- Item on EditText context menu. This action is used to paste from the clipboard into the eidt field without formatting --> + <string name="paste_as_plain_text">Paste as plain text</string> + <!-- Item on EditText context menu. This action is used to replace the current word by other suggested words, suggested by the IME or the spell checker --> <string name="replace">Replace\u2026</string> @@ -2465,6 +2468,12 @@ <!-- Item on EditText context menu. Added only when the context menu is not empty, it enable selection context mode. [CHAR LIMIT=20] --> <string name="selectTextMode">Select text</string> + <!-- Item on EditText context menu. This action is used to undo a text edit operation. --> + <string name="undo">Undo</string> + + <!-- Item on EditText context menu. This action is used to redo a text edit operation. --> + <string name="redo">Redo</string> + <!-- Text selection contextual mode title, displayed in the CAB. [CHAR LIMIT=20] --> <string name="textSelectionCABTitle">Text selection</string> @@ -3069,6 +3078,9 @@ <string name="notification_listener_binding_label">Notification listener</string> <!-- Label to show for a service that is running because it is providing conditions. --> <string name="condition_provider_service_binding_label">Condition provider</string> + <!-- Label to show for a service that is running because it is observing and modifying the + importance of the user's notifications. --> + <string name="notification_assistant_binding_label">Notification assistant</string> <!-- Do Not Translate: Alternate eri.xml --> <string name="alternate_eri_file">/data/eri.xml</string> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 9a4016bd5f9a..937d83db8dbb 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1235,7 +1235,7 @@ please see styles_device_defaults.xml. <item name="subtitleTextAppearance">@style/TextAppearance.Widget.Toolbar.Subtitle</item> <item name="minHeight">?attr/actionBarSize</item> <item name="titleMargin">4dp</item> - <item name="maxButtonHeight">56dp</item> + <item name="maxButtonHeight">@dimen/action_bar_default_height_material</item> <item name="buttonGravity">top</item> <item name="navigationButtonStyle">@style/Widget.Toolbar.Button.Navigation</item> <item name="collapseIcon">?attr/homeAsUpIndicator</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 517bb75c08a0..f257f1455cb2 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -456,7 +456,10 @@ <java-symbol type="string" name="notification_title" /> <java-symbol type="string" name="permission_request_notification_with_subtitle" /> <java-symbol type="string" name="prepend_shortcut_label" /> + <java-symbol type="string" name="paste_as_plain_text" /> <java-symbol type="string" name="replace" /> + <java-symbol type="string" name="undo" /> + <java-symbol type="string" name="redo" /> <java-symbol type="string" name="textSelectionCABTitle" /> <java-symbol type="string" name="BaMmi" /> <java-symbol type="string" name="CLIRDefaultOffNextCallOff" /> @@ -1807,6 +1810,7 @@ <java-symbol type="string" name="low_internal_storage_view_title" /> <java-symbol type="string" name="notification_listener_binding_label" /> <java-symbol type="string" name="condition_provider_service_binding_label" /> + <java-symbol type="string" name="notification_assistant_binding_label" /> <java-symbol type="string" name="report" /> <java-symbol type="string" name="select_input_method" /> <java-symbol type="string" name="select_keyboard_layout_notification_title" /> diff --git a/core/res/res/xml/config_webview_packages.xml b/core/res/res/xml/config_webview_packages.xml index 6f9c58d2f9b7..fd443c15702d 100644 --- a/core/res/res/xml/config_webview_packages.xml +++ b/core/res/res/xml/config_webview_packages.xml @@ -16,5 +16,6 @@ <webviewproviders> <!-- The default WebView implementation --> - <webviewprovider description="Android WebView" packageName="com.android.webview" /> + <webviewprovider description="Android WebView" packageName="com.android.webview"> + </webviewprovider> </webviewproviders> diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java index 83a9e01761d9..afd0bc4e5295 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java @@ -26,8 +26,11 @@ import static android.widget.espresso.TextViewActions.mouseDragOnText; import static android.widget.espresso.TextViewActions.mouseLongClickAndDragOnText; import static android.widget.espresso.TextViewActions.mouseTripleClickAndDragOnText; import static android.widget.espresso.TextViewActions.mouseTripleClickOnTextAtIndex; +import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex; import static android.widget.espresso.TextViewAssertions.hasSelection; + import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.Espresso.pressBack; import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.action.ViewActions.replaceText; import static android.support.test.espresso.action.ViewActions.typeTextIntoFocusedView; @@ -37,8 +40,11 @@ import static android.support.test.espresso.matcher.ViewMatchers.withId; import com.android.frameworks.coretests.R; +import android.support.test.espresso.Espresso; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.SmallTest; +import android.view.MotionEvent; +import android.widget.espresso.ContextMenuUtils; /** * Tests mouse interaction of the TextView widget from an Activity @@ -49,10 +55,13 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< super(TextViewActivity.class); } - @SmallTest - public void testSelectTextByDrag() throws Exception { + @Override + public void setUp() { getActivity(); + } + @SmallTest + public void testSelectTextByDrag() throws Exception { final String helloWorld = "Hello world!"; onView(withId(R.id.textview)).perform(click()); onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld)); @@ -77,8 +86,6 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< @SmallTest public void testSelectTextByDrag_reverse() throws Exception { - getActivity(); - final String helloWorld = "Hello world!"; onView(withId(R.id.textview)).perform(click()); onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld)); @@ -89,9 +96,56 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< } @SmallTest - public void testSelectTextByLongClick() throws Exception { - getActivity(); + public void testContextMenu() throws Exception { + final String text = "abc def ghi."; + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); + ContextMenuUtils.assertContextMenuIsNotDisplayed(); + + onView(withId(R.id.textview)).perform( + mouseClickOnTextAtIndex(text.indexOf("d"), MotionEvent.BUTTON_SECONDARY)); + + ContextMenuUtils.assertContextMenuContainsItemDisabled( + getActivity().getString(com.android.internal.R.string.copy)); + ContextMenuUtils.assertContextMenuContainsItemEnabled( + getActivity().getString(com.android.internal.R.string.undo)); + + // Hide context menu. + pressBack(); + ContextMenuUtils.assertContextMenuIsNotDisplayed(); + + onView(withId(R.id.textview)).perform( + mouseDragOnText(text.indexOf("c"), text.indexOf("h"))); + onView(withId(R.id.textview)).perform( + mouseClickOnTextAtIndex(text.indexOf("d"), MotionEvent.BUTTON_SECONDARY)); + + ContextMenuUtils.assertContextMenuContainsItemEnabled( + getActivity().getString(com.android.internal.R.string.copy)); + ContextMenuUtils.assertContextMenuContainsItemEnabled( + getActivity().getString(com.android.internal.R.string.undo)); + + // Hide context menu. + pressBack(); + + onView(withId(R.id.textview)).check(hasSelection("c def g")); + + onView(withId(R.id.textview)).perform( + mouseClickOnTextAtIndex(text.indexOf("i"), MotionEvent.BUTTON_SECONDARY)); + ContextMenuUtils.assertContextMenuContainsItemDisabled( + getActivity().getString(com.android.internal.R.string.copy)); + ContextMenuUtils.assertContextMenuContainsItemEnabled( + getActivity().getString(com.android.internal.R.string.undo)); + + // Hide context menu. + pressBack(); + + onView(withId(R.id.textview)).check(hasSelection("")); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("i"))); + } + + @SmallTest + public void testSelectTextByLongClick() throws Exception { final String helloWorld = "Hello world!"; onView(withId(R.id.textview)).perform(click()); onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld)); @@ -117,8 +171,6 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< @SmallTest public void testSelectTextByDoubleClick() throws Exception { - getActivity(); - final String helloWorld = "Hello world!"; onView(withId(R.id.textview)).perform(click()); onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld)); @@ -144,8 +196,6 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< @SmallTest public void testSelectTextByDoubleClickAndDrag() throws Exception { - getActivity(); - final String text = "abcd efg hijk lmn"; onView(withId(R.id.textview)).perform(click()); onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); @@ -157,8 +207,6 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< @SmallTest public void testSelectTextByDoubleClickAndDrag_reverse() throws Exception { - getActivity(); - final String text = "abcd efg hijk lmn"; onView(withId(R.id.textview)).perform(click()); onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); @@ -170,8 +218,6 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< @SmallTest public void testSelectTextByLongPressAndDrag() throws Exception { - getActivity(); - final String text = "abcd efg hijk lmn"; onView(withId(R.id.textview)).perform(click()); onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); @@ -183,8 +229,6 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< @SmallTest public void testSelectTextByLongPressAndDrag_reverse() throws Exception { - getActivity(); - final String text = "abcd efg hijk lmn"; onView(withId(R.id.textview)).perform(click()); onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); @@ -196,8 +240,6 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< @SmallTest public void testSelectTextByTripleClick() throws Exception { - getActivity(); - final StringBuilder builder = new StringBuilder(); builder.append("First paragraph.\n"); builder.append("Second paragraph."); @@ -232,8 +274,6 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< @SmallTest public void testSelectTextByTripleClickAndDrag() throws Exception { - getActivity(); - final StringBuilder builder = new StringBuilder(); builder.append("First paragraph.\n"); builder.append("Second paragraph."); @@ -263,8 +303,6 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< @SmallTest public void testSelectTextByTripleClickAndDrag_reverse() throws Exception { - getActivity(); - final StringBuilder builder = new StringBuilder(); builder.append("First paragraph.\n"); builder.append("Second paragraph."); diff --git a/core/tests/coretests/src/android/widget/espresso/ContextMenuUtils.java b/core/tests/coretests/src/android/widget/espresso/ContextMenuUtils.java new file mode 100644 index 000000000000..c8218aa490f2 --- /dev/null +++ b/core/tests/coretests/src/android/widget/espresso/ContextMenuUtils.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.widget.espresso; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.RootMatchers.withDecorView; +import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; +import static android.support.test.espresso.matcher.ViewMatchers.hasFocus; +import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.isEnabled; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.not; + +import com.android.internal.view.menu.ListMenuItemView; + +import android.support.test.espresso.NoMatchingRootException; +import android.support.test.espresso.NoMatchingViewException; +import android.support.test.espresso.ViewInteraction; +import android.support.test.espresso.matcher.ViewMatchers; +import android.widget.MenuPopupWindow.MenuDropDownListView; + +/** + * Espresso utility methods for the context menu. + */ +public final class ContextMenuUtils { + private ContextMenuUtils() {} + + private static ViewInteraction onContextMenu() { + // TODO: Have more reliable way to get context menu. + return onView(ViewMatchers.isAssignableFrom(MenuDropDownListView.class)) + .inRoot(withDecorView(hasFocus())); + } + + /** + * Asserts that the context menu is displayed + * + * @throws AssertionError if the assertion fails + */ + private static void assertContextMenuIsDisplayed() { + onContextMenu().check(matches(isDisplayed())); + } + + /** + * Asserts that the context menu is not displayed + * + * @throws AssertionError if the assertion fails + */ + public static void assertContextMenuIsNotDisplayed() { + try { + assertContextMenuIsDisplayed(); + } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) { + return; + } + throw new AssertionError("Context menu is displayed"); + } + + /** + * Asserts that the context menu contains the specified item and the item has specified enabled + * state. + * + * @param itemLabel label of the item. + * @param enabled enabled state of the item. + * @throws AssertionError if the assertion fails + */ + private static void asssertContextMenuContainsItemWithEnabledState(String itemLabel, + boolean enabled) { + onContextMenu().check(matches( + hasDescendant(allOf( + isAssignableFrom(ListMenuItemView.class), + enabled ? isEnabled() : not(isEnabled()), + hasDescendant(withText(itemLabel)))))); + } + + /** + * Asserts that the context menu contains the specified item and the item is enabled. + * + * @param itemLabel label of the item. + * @throws AssertionError if the assertion fails + */ + public static void assertContextMenuContainsItemEnabled(String itemLabel) { + asssertContextMenuContainsItemWithEnabledState(itemLabel, true); + } + + /** + * Asserts that the context menu contains the specified item and the item is disabled. + * + * @param itemLabel label of the item. + * @throws AssertionError if the assertion fails + */ + public static void assertContextMenuContainsItemDisabled(String itemLabel) { + asssertContextMenuContainsItemWithEnabledState(itemLabel, false); + } +} diff --git a/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java index e51f27857a4b..b8ea2de3abfd 100644 --- a/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java +++ b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java @@ -24,9 +24,9 @@ import android.support.test.espresso.action.CoordinatesProvider; import android.support.test.espresso.action.GeneralClickAction; import android.support.test.espresso.action.MotionEvents; import android.support.test.espresso.action.MotionEvents.DownResultHolder; -import android.support.test.espresso.action.PrecisionDescriber; import android.support.test.espresso.action.Press; import android.support.test.espresso.action.Tapper; +import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -35,6 +35,8 @@ import android.view.ViewConfiguration; */ public final class MouseClickAction implements ViewAction { private final GeneralClickAction mGeneralClickAction; + @MouseUiController.MouseButton + private final int mButton; public enum CLICK implements Tapper { TRIPLE { @@ -86,8 +88,20 @@ public final class MouseClickAction implements ViewAction { }; public MouseClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider) { - mGeneralClickAction = new GeneralClickAction(tapper, coordinatesProvider, - Press.PINPOINT); + this(tapper, coordinatesProvider, MotionEvent.BUTTON_PRIMARY); + } + + /** + * Constructs MouseClickAction + * + * @param tapper the tapper + * @param coordinatesProvider the provider of the event coordinates + * @param button the mouse button used to send motion events + */ + public MouseClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider, + @MouseUiController.MouseButton int button) { + mGeneralClickAction = new GeneralClickAction(tapper, coordinatesProvider, Press.PINPOINT); + mButton = button; } @Override @@ -102,7 +116,7 @@ public final class MouseClickAction implements ViewAction { @Override public void perform(UiController uiController, View view) { - mGeneralClickAction.perform(new MouseUiController(uiController), view); + mGeneralClickAction.perform(new MouseUiController(uiController, mButton), view); long doubleTapTimeout = ViewConfiguration.getDoubleTapTimeout(); if (0 < doubleTapTimeout) { // Wait to avoid false gesture detection. Without this wait, consecutive clicks can be diff --git a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java index f1387f801b01..022be7603dc1 100644 --- a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java +++ b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java @@ -16,6 +16,12 @@ package android.widget.espresso; +import static com.android.internal.util.Preconditions.checkNotNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import android.annotation.IntDef; import android.support.test.espresso.InjectEventSecurityException; import android.support.test.espresso.UiController; import android.view.InputDevice; @@ -26,11 +32,28 @@ import android.view.MotionEvent; * Class to wrap an UiController to overwrite source of motion events to SOURCE_MOUSE. * Note that this doesn't change the tool type. */ -public class MouseUiController implements UiController { +public final class MouseUiController implements UiController { + @Retention(RetentionPolicy.SOURCE) + @IntDef({MotionEvent.BUTTON_PRIMARY, MotionEvent.BUTTON_SECONDARY, MotionEvent.BUTTON_TERTIARY}) + public @interface MouseButton {} + private final UiController mUiController; + @MouseButton + private final int mButton; public MouseUiController(UiController uiController) { - mUiController = uiController; + this(uiController, MotionEvent.BUTTON_PRIMARY); + } + + /** + * Constructs MouseUiController. + * + * @param uiController the uiController to wrap + * @param button the button to be used for generating input events. + */ + public MouseUiController(UiController uiController, @MouseButton int button) { + mUiController = checkNotNull(uiController); + mButton = button; } @Override @@ -40,9 +63,11 @@ public class MouseUiController implements UiController { @Override public boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException { - // Modify the event to mimic mouse primary button event. + // Modify the event to mimic mouse event. event.setSource(InputDevice.SOURCE_MOUSE); - event.setButtonState(MotionEvent.BUTTON_PRIMARY); + if (event.getActionMasked() != MotionEvent.ACTION_UP) { + event.setButtonState(mButton); + } return mUiController.injectMotionEvent(event); } diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java index 54d5823c7aca..1dd6e170e718 100644 --- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java +++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java @@ -26,6 +26,7 @@ import android.support.test.espresso.action.Press; import android.support.test.espresso.action.Tap; import android.support.test.espresso.util.HumanReadables; import android.text.Layout; +import android.view.MotionEvent; import android.view.View; import android.widget.Editor; import android.widget.TextView; @@ -63,8 +64,24 @@ public final class TextViewActions { * @param index The index of the TextView's text to click on. */ public static ViewAction mouseClickOnTextAtIndex(int index) { + return mouseClickOnTextAtIndex(index, MotionEvent.BUTTON_PRIMARY); + } + + /** + * Returns an action that clicks by mouse on text at an index on the TextView.<br> + * <br> + * View constraints: + * <ul> + * <li>must be a TextView displayed on screen + * <ul> + * + * @param index The index of the TextView's text to click on. + * @param button the mouse button to use. + */ + public static ViewAction mouseClickOnTextAtIndex(int index, + @MouseUiController.MouseButton int button) { return actionWithAssertions( - new MouseClickAction(Tap.SINGLE, new TextCoordinates(index))); + new MouseClickAction(Tap.SINGLE, new TextCoordinates(index), button)); } /** diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 5acc1a3e09dd..d5166ab92179 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -1079,11 +1079,12 @@ public class Canvas { * (count >> 2). * @param paint The paint used to draw the points */ - public void drawLines(@Size(min=4,multiple=2) float[] pts, int offset, int count, Paint paint) { + public void drawLines(@Size(multiple=4) @NonNull float[] pts, int offset, int count, + @NonNull Paint paint) { native_drawLines(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance()); } - public void drawLines(@Size(min=4,multiple=2) @NonNull float[] pts, @NonNull Paint paint) { + public void drawLines(@Size(multiple=4) @NonNull float[] pts, @NonNull Paint paint) { drawLines(pts, 0, pts.length, paint); } diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp index 6a6cc42043df..cf76e6be46c4 100644 --- a/libs/hwui/CanvasState.cpp +++ b/libs/hwui/CanvasState.cpp @@ -192,7 +192,7 @@ void CanvasState::concatMatrix(const Matrix4& matrix) { /////////////////////////////////////////////////////////////////////////////// bool CanvasState::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) { - mSnapshot->clip(left, top, right, bottom, op); + mSnapshot->clip(Rect(left, top, right, bottom), op); mDirtyClip = true; return !mSnapshot->clipIsEmpty(); } diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp index fd6f0b5739d2..5f166cafd01c 100644 --- a/libs/hwui/ClipArea.cpp +++ b/libs/hwui/ClipArea.cpp @@ -201,12 +201,6 @@ void ClipArea::setClip(float left, float top, float right, float bottom) { mClipRegion.setEmpty(); } -void ClipArea::clipRectWithTransform(float left, float top, float right, - float bottom, const mat4* transform, SkRegion::Op op) { - Rect r(left, top, right, bottom); - clipRectWithTransform(r, transform, op); -} - void ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op) { switch (mMode) { @@ -274,13 +268,6 @@ void ClipArea::rectangleModeClipRectWithTransform(const Rect& r, rectangleListModeClipRectWithTransform(r, transform, op); } -void ClipArea::rectangleModeClipRectWithTransform(float left, float top, - float right, float bottom, const mat4* transform, SkRegion::Op op) { - Rect r(left, top, right, bottom); - rectangleModeClipRectWithTransform(r, transform, op); - mClipRect = mRectangleList.calculateBounds(); -} - /* * RectangleList mode implementation */ @@ -303,12 +290,6 @@ void ClipArea::rectangleListModeClipRectWithTransform(const Rect& r, } } -void ClipArea::rectangleListModeClipRectWithTransform(float left, float top, - float right, float bottom, const mat4* transform, SkRegion::Op op) { - Rect r(left, top, right, bottom); - rectangleListModeClipRectWithTransform(r, transform, op); -} - /* * Region mode implementation */ @@ -336,11 +317,6 @@ void ClipArea::regionModeClipRectWithTransform(const Rect& r, onClipRegionUpdated(); } -void ClipArea::regionModeClipRectWithTransform(float left, float top, - float right, float bottom, const mat4* transform, SkRegion::Op op) { - regionModeClipRectWithTransform(Rect(left, top, right, bottom), transform, op); -} - void ClipArea::onClipRegionUpdated() { if (!mClipRegion.isEmpty()) { mClipRect.set(mClipRegion.getBounds()); diff --git a/libs/hwui/ClipArea.h b/libs/hwui/ClipArea.h index f88fd92e234d..268301c62fc9 100644 --- a/libs/hwui/ClipArea.h +++ b/libs/hwui/ClipArea.h @@ -98,8 +98,6 @@ public: void setEmpty(); void setClip(float left, float top, float right, float bottom); - void clipRectWithTransform(float left, float top, float right, float bottom, - const mat4* transform, SkRegion::Op op); void clipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op); void clipRegion(const SkRegion& region, SkRegion::Op op); @@ -133,12 +131,8 @@ public: private: void enterRectangleMode(); void rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op); - void rectangleModeClipRectWithTransform(float left, float top, float right, - float bottom, const mat4* transform, SkRegion::Op op); void enterRectangleListMode(); - void rectangleListModeClipRectWithTransform(float left, float top, - float right, float bottom, const mat4* transform, SkRegion::Op op); void rectangleListModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op); @@ -147,8 +141,6 @@ private: void enterRegionMode(); void regionModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op); - void regionModeClipRectWithTransform(float left, float top, float right, - float bottom, const mat4* transform, SkRegion::Op op); void ensureClipRegion(); void onClipRegionUpdated(); diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp index bdc8f68cf3b8..ad9559ffdf9f 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/OpReorderer.cpp @@ -91,8 +91,7 @@ public: MergingOpBatch(batchid_t batchId, BakedOpState* op) : BatchBase(batchId, op, true) - , mClipSideFlags(op->computedState.clipSideFlags) - , mClipRect(op->computedState.clipRect) { + , mClipSideFlags(op->computedState.clipSideFlags) { } /* @@ -194,22 +193,17 @@ public: mBounds.unionWith(op->computedState.clippedBounds); mOps.push_back(op); - const int newClipSideFlags = op->computedState.clipSideFlags; - mClipSideFlags |= newClipSideFlags; - - const Rect& opClip = op->computedState.clipRect; - if (newClipSideFlags & OpClipSideFlags::Left) mClipRect.left = opClip.left; - if (newClipSideFlags & OpClipSideFlags::Top) mClipRect.top = opClip.top; - if (newClipSideFlags & OpClipSideFlags::Right) mClipRect.right = opClip.right; - if (newClipSideFlags & OpClipSideFlags::Bottom) mClipRect.bottom = opClip.bottom; + // Because a new op must have passed canMergeWith(), we know it's passed the clipping compat + // check, and doesn't extend past a side of the clip that's in use by the merged batch. + // Therefore it's safe to simply always merge flags, and use the bounds as the clip rect. + mClipSideFlags |= op->computedState.clipSideFlags; } int getClipSideFlags() const { return mClipSideFlags; } - const Rect& getClipRect() const { return mClipRect; } + const Rect& getClipRect() const { return mBounds; } private: int mClipSideFlags; - Rect mClipRect; }; OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height, diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 48cc91abd89c..470f9ecb5024 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -39,7 +39,7 @@ namespace uirenderer { class DeferredLayerUpdater; struct RecordedOp; -class RecordingCanvas: public Canvas, public CanvasStateClient { +class ANDROID_API RecordingCanvas: public Canvas, public CanvasStateClient { enum class DeferredBarrierType { None, InOrder, diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp index 2f535bb1cb08..c6d89775390b 100644 --- a/libs/hwui/Snapshot.cpp +++ b/libs/hwui/Snapshot.cpp @@ -88,9 +88,9 @@ void Snapshot::clipRegionTransformed(const SkRegion& region, SkRegion::Op op) { mClipArea->clipRegion(region, op); } -void Snapshot::clip(float left, float top, float right, float bottom, SkRegion::Op op) { +void Snapshot::clip(const Rect& localClip, SkRegion::Op op) { flags |= Snapshot::kFlagClipSet; - mClipArea->clipRectWithTransform(left, top, right, bottom, transform, op); + mClipArea->clipRectWithTransform(localClip, transform, op); } void Snapshot::clipPath(const SkPath& path, SkRegion::Op op) { diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index 4789b338d173..194aa5735a7e 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -124,7 +124,7 @@ public: * the specified operation. The specified rectangle is transformed * by this snapshot's trasnformation. */ - void clip(float left, float top, float right, float bottom, SkRegion::Op op); + void clip(const Rect& localClip, SkRegion::Op op); /** * Modifies the current clip with the new clip rectangle and diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index d37fad4676eb..ac14fc84b8a3 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -98,7 +98,7 @@ public: static std::unique_ptr<Snapshot> makeSnapshot(const Matrix4& transform, const Rect& clip) { std::unique_ptr<Snapshot> snapshot(new Snapshot()); - snapshot->clip(clip.left, clip.top, clip.right, clip.bottom, SkRegion::kReplace_Op); + snapshot->clip(clip, SkRegion::kReplace_Op); // store clip first, so it isn't transformed *(snapshot->transform) = transform; return snapshot; } diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/OpReordererTests.cpp index 288f8af50081..b28e4361bc4a 100644 --- a/libs/hwui/tests/unit/OpReordererTests.cpp +++ b/libs/hwui/tests/unit/OpReordererTests.cpp @@ -222,6 +222,46 @@ TEST(OpReorderer, simpleBatching) { << "Expect number of ops = 2 * loop count"; } +TEST(OpReorderer, clippedMerging) { + class ClippedMergingTestRenderer : public TestRendererBase { + public: + void onMergedBitmapOps(const MergedBakedOpList& opList) override { + EXPECT_EQ(0, mIndex); + mIndex += opList.count; + EXPECT_EQ(4u, opList.count); + EXPECT_EQ(Rect(10, 10, 90, 90), opList.clip); + EXPECT_EQ(OpClipSideFlags::Left | OpClipSideFlags::Top | OpClipSideFlags::Right, + opList.clipSideFlags); + } + }; + auto node = TestUtils::createNode(0, 0, 100, 100, + [](RenderProperties& props, TestCanvas& canvas) { + SkBitmap bitmap = TestUtils::createSkBitmap(20, 20); + + // left side clipped (to inset left half) + canvas.clipRect(10, 0, 50, 100, SkRegion::kReplace_Op); + canvas.drawBitmap(bitmap, 0, 40, nullptr); + + // top side clipped (to inset top half) + canvas.clipRect(0, 10, 100, 50, SkRegion::kReplace_Op); + canvas.drawBitmap(bitmap, 40, 0, nullptr); + + // right side clipped (to inset right half) + canvas.clipRect(50, 0, 90, 100, SkRegion::kReplace_Op); + canvas.drawBitmap(bitmap, 80, 40, nullptr); + + // bottom not clipped, just abutting (inset bottom half) + canvas.clipRect(0, 50, 100, 90, SkRegion::kReplace_Op); + canvas.drawBitmap(bitmap, 40, 70, nullptr); + }); + + OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, + createSyncedNodeList(node), sLightCenter); + ClippedMergingTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); +} + TEST(OpReorderer, textMerging) { class TextMergingTestRenderer : public TestRendererBase { public: diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index e9c94c07dde4..6a13f826949a 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -129,9 +129,8 @@ public final class TvInputManager { * The TV input is connected. * * <p>This state indicates that a source device is connected to the input port and is in the - * normal operation mode. It is mostly relevant to hardware inputs such as HDMI input. This is - * the default state for any hardware inputs where their states are unknown. Non-hardware inputs - * are considered connected all the time. + * normal operation mode. It is mostly relevant to hardware inputs such as HDMI input. + * Non-hardware inputs are considered connected all the time. * * @see #getInputState * @see TvInputManager.TvInputCallback#onInputStateChanged @@ -141,7 +140,8 @@ public final class TvInputManager { * The TV input is connected but in standby mode. * * <p>This state indicates that a source device is connected to the input port but is in standby - * mode. It is mostly relevant to hardware inputs such as HDMI input. + * or low power mode. It is mostly relevant to hardware inputs such as HDMI inputs and Component + * inputs. * * @see #getInputState * @see TvInputManager.TvInputCallback#onInputStateChanged diff --git a/media/java/android/media/tv/TvStreamConfig.java b/media/java/android/media/tv/TvStreamConfig.java index 0c2f3fec26a4..eae83cfff5f1 100644 --- a/media/java/android/media/tv/TvStreamConfig.java +++ b/media/java/android/media/tv/TvStreamConfig.java @@ -28,8 +28,15 @@ import android.util.Log; public class TvStreamConfig implements Parcelable { static final String TAG = TvStreamConfig.class.getSimpleName(); + // Must be in sync with tv_input.h public final static int STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE = 1; public final static int STREAM_TYPE_BUFFER_PRODUCER = 2; + /** + * A flag indicating whether the HAL is sure about signal at this stream. Note that + * value of 0 here does not necessarily mean no signal. It just means that it may not have + * signal and the underlying layer is not sure. + */ + public static final int FLAG_MASK_SIGNAL_DETECTION = 0x1; private int mStreamId; private int mType; @@ -41,6 +48,10 @@ public class TvStreamConfig implements Parcelable { * via tv_input_device::get_stream_configurations(). */ private int mGeneration; + /** + * Flags for stream status. See FLAG_MASK_* for details. + */ + private int mFlags; public static final Parcelable.Creator<TvStreamConfig> CREATOR = new Parcelable.Creator<TvStreamConfig>() { @@ -52,7 +63,8 @@ public class TvStreamConfig implements Parcelable { type(source.readInt()). maxWidth(source.readInt()). maxHeight(source.readInt()). - generation(source.readInt()).build(); + generation(source.readInt()). + flags(source.readInt()).build(); } catch (Exception e) { Log.e(TAG, "Exception creating TvStreamConfig from parcel", e); return null; @@ -87,6 +99,10 @@ public class TvStreamConfig implements Parcelable { return mGeneration; } + public int getFlags() { + return mFlags; + } + @Override public String toString() { return "TvStreamConfig {mStreamId=" + mStreamId + ";" + "mType=" + mType + ";mGeneration=" @@ -106,6 +122,7 @@ public class TvStreamConfig implements Parcelable { dest.writeInt(mMaxWidth); dest.writeInt(mMaxHeight); dest.writeInt(mGeneration); + dest.writeInt(mFlags); } /** @@ -117,6 +134,7 @@ public class TvStreamConfig implements Parcelable { private Integer mMaxWidth; private Integer mMaxHeight; private Integer mGeneration; + private int mFlags = 0; public Builder() { } @@ -146,6 +164,11 @@ public class TvStreamConfig implements Parcelable { return this; } + public Builder flags(int flag) { + mFlags = flag; + return this; + } + public TvStreamConfig build() { if (mStreamId == null || mType == null || mMaxWidth == null || mMaxHeight == null || mGeneration == null) { @@ -158,6 +181,7 @@ public class TvStreamConfig implements Parcelable { config.mMaxWidth = mMaxWidth; config.mMaxHeight = mMaxHeight; config.mGeneration = mGeneration; + config.mFlags = mFlags; return config; } } @@ -172,6 +196,7 @@ public class TvStreamConfig implements Parcelable { && config.mStreamId == mStreamId && config.mType == mType && config.mMaxWidth == mMaxWidth - && config.mMaxHeight == mMaxHeight; + && config.mMaxHeight == mMaxHeight + && config.mFlags == mFlags; } } diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml index 8cc79a4c5584..5e634a4df3d3 100644 --- a/packages/DocumentsUI/AndroidManifest.xml +++ b/packages/DocumentsUI/AndroidManifest.xml @@ -40,7 +40,7 @@ <activity android:name=".DownloadsActivity" - android:theme="@style/DocumentsFullScreenTheme" + android:theme="@style/DocumentsTheme" android:label="@string/downloads_label" android:icon="@drawable/ic_doc_text"> <intent-filter> @@ -64,7 +64,7 @@ <activity android:name=".FilesActivity" - android:theme="@style/DocumentsFullScreenTheme" + android:theme="@style/DocumentsTheme" android:icon="@drawable/ic_files_app" android:label="@string/files_label" android:documentLaunchMode="intoExisting"> diff --git a/packages/DocumentsUI/res/values-sw720dp/dimens.xml b/packages/DocumentsUI/res/values-sw720dp/dimens.xml index f393d88a82cd..83ceb556499f 100644 --- a/packages/DocumentsUI/res/values-sw720dp/dimens.xml +++ b/packages/DocumentsUI/res/values-sw720dp/dimens.xml @@ -15,10 +15,6 @@ --> <resources> - <bool name="show_as_dialog">true</bool> - - <item type="dimen" name="dialog_width">85%</item> - <dimen name="grid_padding_horiz">16dp</dimen> <dimen name="grid_padding_vert">16dp</dimen> diff --git a/packages/DocumentsUI/res/values-sw720dp/layouts.xml b/packages/DocumentsUI/res/values-sw720dp/layouts.xml deleted file mode 100644 index 7d28f9cbc2ac..000000000000 --- a/packages/DocumentsUI/res/values-sw720dp/layouts.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2015 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<resources> - <item name="docs_activity" type="layout">@layout/fixed_layout</item> -</resources> diff --git a/packages/DocumentsUI/res/values-sw720dp/styles.xml b/packages/DocumentsUI/res/values-sw720dp/styles.xml deleted file mode 100644 index a8dcbb0bbd8a..000000000000 --- a/packages/DocumentsUI/res/values-sw720dp/styles.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?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 xmlns:android="http://schemas.android.com/apk/res/android"> - - <style name="DocumentsBaseTheme" parent="@style/Theme.AppCompat.Light.Dialog"> - <!-- We do not specify width of window here because the max size of - floating window specified by windowFixedWidthis is limited. --> - <item name="*android:windowFixedHeightMajor">80%</item> - <item name="*android:windowFixedHeightMinor">90%</item> - </style> - -</resources> diff --git a/packages/DocumentsUI/res/values/attrs.xml b/packages/DocumentsUI/res/values/attrs.xml index 0afc3a2e4d8b..9e130011496b 100644 --- a/packages/DocumentsUI/res/values/attrs.xml +++ b/packages/DocumentsUI/res/values/attrs.xml @@ -14,7 +14,7 @@ limitations under the License. --> <resources> - <declare-styleable name="DocumentsBaseTheme"> + <declare-styleable name="DocumentsTheme"> <attr name="colorActionMode" format="color"/> </declare-styleable> </resources> diff --git a/packages/DocumentsUI/res/values/dimens.xml b/packages/DocumentsUI/res/values/dimens.xml index f94a00ed74d0..060871d526d0 100644 --- a/packages/DocumentsUI/res/values/dimens.xml +++ b/packages/DocumentsUI/res/values/dimens.xml @@ -35,7 +35,6 @@ <dimen name="list_divider_inset">72dp</dimen> <bool name="list_divider_inset_left">true</bool> - <bool name="show_as_dialog">false</bool> <bool name="always_show_summary">false</bool> <dimen name="dir_elevation">8dp</dimen> diff --git a/packages/DocumentsUI/res/values/styles.xml b/packages/DocumentsUI/res/values/styles.xml index 6712e2dd4537..d14631dbbc1a 100644 --- a/packages/DocumentsUI/res/values/styles.xml +++ b/packages/DocumentsUI/res/values/styles.xml @@ -16,31 +16,10 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android"> - <style name="DocumentsBaseTheme" parent="@style/Theme.AppCompat.Light.DarkActionBar" /> <style name="ActionBarTheme" parent="@*android:style/ThemeOverlay.Material.Dark.ActionBar" /> <style name="ActionBarPopupTheme" parent="@*android:style/ThemeOverlay.Material.Light" /> - <style name="DocumentsTheme" parent="@style/DocumentsBaseTheme"> - <item name="actionBarWidgetTheme">@null</item> - <item name="actionBarTheme">@style/ActionBarTheme</item> - <item name="actionBarPopupTheme">@style/ActionBarPopupTheme</item> - - <item name="android:windowBackground">@color/window_background</item> - <item name="android:colorPrimaryDark">@color/primary_dark</item> - <item name="android:colorPrimary">@color/primary</item> - <item name="android:colorAccent">@color/accent</item> - <item name="colorActionMode">@color/action_mode</item> - - <item name="android:listDivider">@*android:drawable/list_divider_material</item> - - <item name="android:windowActionBar">false</item> - <item name="android:windowActionModeOverlay">true</item> - <item name="android:windowNoTitle">true</item> - - <item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item> - </style> - - <style name="DocumentsFullScreenTheme" parent="@style/Theme.AppCompat.Light.DarkActionBar"> + <style name="DocumentsTheme" parent="@style/Theme.AppCompat.Light.DarkActionBar"> <item name="actionBarWidgetTheme">@null</item> <item name="actionBarTheme">@style/ActionBarTheme</item> <item name="actionBarPopupTheme">@style/ActionBarPopupTheme</item> diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java index fd8b56a4d04f..6a5911bb1bfd 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java +++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java @@ -45,6 +45,7 @@ import android.support.annotation.VisibleForTesting; import android.support.design.widget.Snackbar; import android.text.format.DateUtils; import android.util.Log; +import android.webkit.MimeTypeMap; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentStack; @@ -495,10 +496,41 @@ public class CopyService extends IntentService { } } + final String dstMimeType; + final String dstDisplayName; + + // If the file is virtual, but can be converted to another format, then try to copy it + // as such format. Also, append an extension for the target mime type (if known). + if (srcInfo.isVirtualDocument()) { + if (!srcInfo.isTypedDocument()) { + // Impossible to copy a file which is virtual, but not typed. + mFailedFiles.add(srcInfo); + return false; + } + final String[] streamTypes = getContentResolver().getStreamTypes( + srcInfo.derivedUri, "*/*"); + if (streamTypes != null && streamTypes.length > 0) { + dstMimeType = streamTypes[0]; + final String extension = MimeTypeMap.getSingleton(). + getExtensionFromMimeType(dstMimeType); + dstDisplayName = srcInfo.displayName + + (extension != null ? "." + extension : srcInfo.displayName); + } else { + // The provider says that it supports typed documents, but doesn't say + // anything about available formats. + // TODO: Log failures. b/26192412 + mFailedFiles.add(srcInfo); + return false; + } + } else { + dstMimeType = srcInfo.mimeType; + dstDisplayName = srcInfo.displayName; + } + // Create the target document (either a file or a directory), then copy recursively the // contents (bytes or children). final Uri dstUri = DocumentsContract.createDocument(mDstClient, - dstDirInfo.derivedUri, srcInfo.mimeType, srcInfo.displayName); + dstDirInfo.derivedUri, dstMimeType, dstDisplayName); if (dstUri == null) { // If this is a directory, the entire subdir will not be copied over. mFailedFiles.add(srcInfo); @@ -517,7 +549,7 @@ public class CopyService extends IntentService { if (Document.MIME_TYPE_DIR.equals(srcInfo.mimeType)) { success = copyDirectoryHelper(srcInfo, dstInfo, mode); } else { - success = copyFileHelper(srcInfo, dstInfo, mode); + success = copyFileHelper(srcInfo, dstInfo, dstMimeType, mode); } if (mode == TRANSFER_MODE_MOVE && success) { @@ -593,11 +625,12 @@ public class CopyService extends IntentService { * * @param srcUriInfo Info of the file to copy from. * @param dstUriInfo Info of the *file* to copy to. Must be created beforehand. + * @param mimeType Mime type for the target. Can be different than source for virtual files. * @return True on success, false on error. * @throws RemoteException */ - private boolean copyFileHelper(DocumentInfo srcInfo, DocumentInfo dstInfo, int mode) - throws RemoteException { + private boolean copyFileHelper(DocumentInfo srcInfo, DocumentInfo dstInfo, String mimeType, + int mode) throws RemoteException { // Copy an individual file. CancellationSignal canceller = new CancellationSignal(); ParcelFileDescriptor srcFile = null; @@ -610,19 +643,11 @@ public class CopyService extends IntentService { // If the file is virtual, but can be converted to another format, then try to copy it // as such format. if (srcInfo.isVirtualDocument() && srcInfo.isTypedDocument()) { - final String[] streamTypes = mSrcClient.getStreamTypes( - srcInfo.derivedUri, "*/*"); - if (streamTypes.length > 0) { - // Pick the first streamable format. - final AssetFileDescriptor srcFileAsAsset = - mSrcClient.openTypedAssetFileDescriptor( - srcInfo.derivedUri, streamTypes[0], null, canceller); - srcFile = srcFileAsAsset.getParcelFileDescriptor(); - src = new AssetFileDescriptor.AutoCloseInputStream(srcFileAsAsset); - } else { - // TODO: Log failures. b/26192412 - mFailedFiles.add(srcInfo); - } + final AssetFileDescriptor srcFileAsAsset = + mSrcClient.openTypedAssetFileDescriptor( + srcInfo.derivedUri, mimeType, null, canceller); + srcFile = srcFileAsAsset.getParcelFileDescriptor(); + src = new AssetFileDescriptor.AutoCloseInputStream(srcFileAsAsset); } else { srcFile = mSrcClient.openFile(srcInfo.derivedUri, "r", canceller); src = new ParcelFileDescriptor.AutoCloseInputStream(srcFile); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 313d303e785d..8ca2cfb93bac 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -34,7 +34,6 @@ import android.content.ContentValues; import android.content.Intent; import android.content.pm.ResolveInfo; import android.content.res.Resources; -import android.graphics.Point; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -45,7 +44,6 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.WindowManager; import android.widget.BaseAdapter; import android.widget.Spinner; import android.widget.Toolbar; @@ -64,8 +62,6 @@ public class DocumentsActivity extends BaseActivity { private static final int CODE_FORWARD = 42; private static final String TAG = "DocumentsActivity"; - private boolean mShowAsDialog; - private Toolbar mToolbar; private Spinner mToolbarStack; @@ -83,29 +79,8 @@ public class DocumentsActivity extends BaseActivity { super.onCreate(icicle); final Resources res = getResources(); - mShowAsDialog = res.getBoolean(R.bool.show_as_dialog); - - if (!mShowAsDialog) { - setTheme(R.style.DocumentsFullScreenTheme); - } - - if (mShowAsDialog) { - mDrawer = DrawerController.createDummy(); - - // Strongly define our horizontal dimension; we leave vertical as - // WRAP_CONTENT so that system resizes us when IME is showing. - final WindowManager.LayoutParams a = getWindow().getAttributes(); - - final Point size = new Point(); - getWindowManager().getDefaultDisplay().getSize(size); - a.width = (int) res.getFraction(R.dimen.dialog_width, size.x, size.x); - - getWindow().setAttributes(a); - - } else { - mDrawer = DrawerController.create(this); - } + mDrawer = DrawerController.create(this); mToolbar = (Toolbar) findViewById(R.id.toolbar); mStackAdapter = new StackAdapter(); @@ -267,15 +242,16 @@ public class DocumentsActivity extends BaseActivity { } } - if (!mShowAsDialog && mDrawer.isUnlocked()) { + if (mDrawer.isUnlocked()) { mToolbar.setNavigationIcon(R.drawable.ic_hamburger); mToolbar.setNavigationContentDescription(R.string.drawer_open); - mToolbar.setNavigationOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - setRootsDrawerOpen(true); - } - }); + mToolbar.setNavigationOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + setRootsDrawerOpen(true); + } + }); } else { mToolbar.setNavigationIcon(null); mToolbar.setNavigationContentDescription(R.string.drawer_open); @@ -306,10 +282,7 @@ public class DocumentsActivity extends BaseActivity { public boolean onCreateOptionsMenu(Menu menu) { boolean showMenu = super.onCreateOptionsMenu(menu); - // Most actions are visible when showing as dialog - if (mShowAsDialog) { - expandMenus(menu); - } + expandMenus(menu); return showMenu; } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java index 079d59914de6..24a811393b65 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java @@ -97,7 +97,7 @@ public class CopyServiceTest extends ServiceTestCase<CopyService> { public void testCopyFile() throws Exception { String srcPath = "/test0.txt"; - Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", + Uri testFile = mStorage.createRegularFile(SRC_ROOT, srcPath, "text/plain", "The five boxing wizards jump quickly".getBytes()); startService(createCopyIntent(Lists.newArrayList(testFile))); @@ -110,10 +110,33 @@ public class CopyServiceTest extends ServiceTestCase<CopyService> { assertCopied(srcPath); } + public void testCopyVirtualTypedFile() throws Exception { + String srcPath = "/virtual.sth"; + String expectedDstPath = "/virtual.sth.pdf"; + ArrayList<String> streamTypes = new ArrayList<>(); + streamTypes.add("application/pdf"); + streamTypes.add("text/html"); + String testContent = "I love fruit cakes!"; + Uri testFile = mStorage.createVirtualFile(SRC_ROOT, srcPath, "virtual/mime-type", + streamTypes, testContent.getBytes()); + + startService(createCopyIntent(Lists.newArrayList(testFile))); + + // 2 operations: file creation, then writing data. + mResolver.waitForChanges(2); + + // Verify that one file was copied. + assertDestFileCount(1); + + byte[] dstContent = readFile(DST_ROOT, expectedDstPath); + MoreAsserts.assertEquals("Moved file contents differ", testContent.getBytes(), dstContent); + } + public void testMoveFile() throws Exception { String srcPath = "/test0.txt"; String testContent = "The five boxing wizards jump quickly"; - Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", testContent.getBytes()); + Uri testFile = mStorage.createRegularFile(SRC_ROOT, srcPath, "text/plain", + testContent.getBytes()); Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile)); moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE); @@ -142,9 +165,12 @@ public class CopyServiceTest extends ServiceTestCase<CopyService> { "/test2.txt" }; List<Uri> testFiles = Lists.newArrayList( - mStorage.createFile(SRC_ROOT, srcPaths[0], "text/plain", testContent[0].getBytes()), - mStorage.createFile(SRC_ROOT, srcPaths[1], "text/plain", testContent[1].getBytes()), - mStorage.createFile(SRC_ROOT, srcPaths[2], "text/plain", testContent[2].getBytes())); + mStorage.createRegularFile(SRC_ROOT, srcPaths[0], "text/plain", + testContent[0].getBytes()), + mStorage.createRegularFile(SRC_ROOT, srcPaths[1], "text/plain", + testContent[1].getBytes()), + mStorage.createRegularFile(SRC_ROOT, srcPaths[2], "text/plain", + testContent[2].getBytes())); // Copy all the test files. startService(createCopyIntent(testFiles)); @@ -195,7 +221,6 @@ public class CopyServiceTest extends ServiceTestCase<CopyService> { Intent intent = createCopyIntent(Lists.newArrayList(testDir), descDir); startService(intent); - getService().addFinishedListener(mListener); mListener.waitForFinished(); @@ -240,9 +265,9 @@ public class CopyServiceTest extends ServiceTestCase<CopyService> { }; // Create test dir; put some files in it. Uri testDir = createTestDirectory(srcDir); - mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes()); - mStorage.createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes()); - mStorage.createFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes()); + mStorage.createRegularFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes()); + mStorage.createRegularFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes()); + mStorage.createRegularFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes()); Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir)); moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE); @@ -270,7 +295,7 @@ public class CopyServiceTest extends ServiceTestCase<CopyService> { public void testCopyFileWithReadErrors() throws Exception { String srcPath = "/test0.txt"; - Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", + Uri testFile = mStorage.createRegularFile(SRC_ROOT, srcPath, "text/plain", "The five boxing wizards jump quickly".getBytes()); mStorage.simulateReadErrorsForFile(testFile); @@ -284,9 +309,26 @@ public class CopyServiceTest extends ServiceTestCase<CopyService> { assertDestFileCount(0); } + public void testCopyVirtualNonTypedFile() throws Exception { + String srcPath = "/non-typed.sth"; + // Empty stream types causes the FLAG_SUPPORTS_TYPED_DOCUMENT to be not set. + ArrayList<String> streamTypes = new ArrayList<>(); + Uri testFile = mStorage.createVirtualFile(SRC_ROOT, srcPath, "virtual/mime-type", + streamTypes, "I love Tokyo!".getBytes()); + + Intent intent = createCopyIntent(Lists.newArrayList(testFile)); + startService(intent); + getService().addFinishedListener(mListener); + + mListener.waitForFinished(); + mListener.assertFailedCount(1); + mListener.assertFileFailed("non-typed.sth"); + assertDestFileCount(0); + } + public void testMoveFileWithReadErrors() throws Exception { String srcPath = "/test0.txt"; - Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", + Uri testFile = mStorage.createRegularFile(SRC_ROOT, srcPath, "text/plain", "The five boxing wizards jump quickly".getBytes()); mStorage.simulateReadErrorsForFile(testFile); @@ -326,10 +368,10 @@ public class CopyServiceTest extends ServiceTestCase<CopyService> { }; // Create test dir; put some files in it. Uri testDir = createTestDirectory(srcDir); - mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes()); + mStorage.createRegularFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes()); Uri errFile = mStorage - .createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes()); - mStorage.createFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes()); + .createRegularFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes()); + mStorage.createRegularFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes()); mStorage.simulateReadErrorsForFile(errFile); @@ -363,7 +405,7 @@ public class CopyServiceTest extends ServiceTestCase<CopyService> { } private Uri createTestDirectory(String dir) throws IOException { - return mStorage.createFile( + return mStorage.createRegularFile( SRC_ROOT, dir, DocumentsContract.Document.MIME_TYPE_DIR, null); } @@ -473,6 +515,7 @@ public class CopyServiceTest extends ServiceTestCase<CopyService> { final CountDownLatch latch = new CountDownLatch(1); final List<DocumentInfo> failedDocs = new ArrayList<>(); + @Override public void onFinished(List<DocumentInfo> failed) { failedDocs.addAll(failed); diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java index 7a75503ef12a..2c311a7cc192 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java @@ -44,9 +44,11 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; public class StubProvider extends DocumentsProvider { @@ -59,7 +61,7 @@ public class StubProvider extends DocumentsProvider { private static final String EXTRA_SIZE = "com.android.documentsui.stubprovider.SIZE"; private static final String EXTRA_ROOT = "com.android.documentsui.stubprovider.ROOT"; private static final String STORAGE_SIZE_KEY = "documentsui.stubprovider.size"; - private static int DEFAULT_ROOT_SIZE = 1024 * 1024 * 100; // 100 MB. + private static int DEFAULT_ROOT_SIZE = 1024 * 1024 * 100; // 100 MB. private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, @@ -105,7 +107,13 @@ public class StubProvider extends DocumentsProvider { mRoots.clear(); for (String rootId : rootIds) { - final RootInfo rootInfo = new RootInfo(rootId, getSize(rootId)); + // Make a subdir in the cache dir for each root. + final File file = new File(getContext().getCacheDir(), rootId); + if (file.mkdir()) { + Log.i(TAG, "Created new root directory @ " + file.getPath()); + } + final RootInfo rootInfo = new RootInfo(file, getSize(rootId)); + mStorage.put(rootInfo.document.documentId, rootInfo.document); mRoots.put(rootId, rootInfo); } } @@ -188,7 +196,7 @@ public class StubProvider extends DocumentsProvider { created = file.createNewFile(); } catch (IOException e) { // We'll throw an FNF exception later :) - Log.e(TAG, "createnewFile operation failed for file: " + file, e); + Log.e(TAG, "createNewFile operation failed for file: " + file, e); } if (!created) { throw new FileNotFoundException( @@ -197,7 +205,8 @@ public class StubProvider extends DocumentsProvider { Log.i(TAG, "Created new file: " + file); } - final StubDocument document = new StubDocument(file, mimeType, parent); + final StubDocument document = StubDocument.createRegularDocument(file, mimeType, parent); + mStorage.put(document.documentId, document); Log.d(TAG, "Created document " + document.documentId); notifyParentChanged(document.parentId); getContext().getContentResolver().notifyChange( @@ -264,14 +273,18 @@ public class StubProvider extends DocumentsProvider { public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal) throws FileNotFoundException { final StubDocument document = mStorage.get(docId); - if (document == null || !document.file.isFile()) + if (document == null || !document.file.isFile()) { throw new FileNotFoundException(); + } + if ((document.flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0) { + throw new IllegalStateException("Tried to open a virtual file."); + } if ("r".equals(mode)) { - ParcelFileDescriptor pfd = ParcelFileDescriptor.open(document.file, - ParcelFileDescriptor.MODE_READ_ONLY); + final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(document.file, + ParcelFileDescriptor.MODE_READ_ONLY); if (docId.equals(mSimulateReadErrors)) { - pfd = new ParcelFileDescriptor(pfd) { + return new ParcelFileDescriptor(pfd) { @Override public void checkError() throws IOException { throw new IOException("Test error"); @@ -298,6 +311,54 @@ public class StubProvider extends DocumentsProvider { throw new FileNotFoundException(); } + @Override + public AssetFileDescriptor openTypedDocument( + String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal) + throws FileNotFoundException { + final StubDocument document = mStorage.get(documentId); + if (document == null || !document.file.isFile()) { + throw new FileNotFoundException(); + } + if ((document.flags & Document.FLAG_SUPPORTS_TYPED_DOCUMENT) == 0) { + throw new IllegalStateException("Tried to open a non-typed document as typed."); + } + for (final String mimeType : document.streamTypes) { + // Strict compare won't accept wildcards, but that's OK for tests, as DocumentsUI + // doesn't use them for getStreamTypes nor openTypedDocument. + if (mimeType.equals(mimeTypeFilter)) { + ParcelFileDescriptor pfd = ParcelFileDescriptor.open( + document.file, ParcelFileDescriptor.MODE_READ_ONLY); + if (documentId.equals(mSimulateReadErrors)) { + pfd = new ParcelFileDescriptor(pfd) { + @Override + public void checkError() throws IOException { + throw new IOException("Test error"); + } + }; + } + return new AssetFileDescriptor(pfd, 0, document.file.length()); + } + } + throw new IllegalArgumentException("Invalid MIME type filter for openTypedDocument()."); + } + + @Override + public String[] getStreamTypes(Uri uri, String mimeTypeFilter) { + final StubDocument document = mStorage.get(DocumentsContract.getDocumentId(uri)); + if (document == null) { + throw new IllegalArgumentException( + "The provided Uri is incorrect, or the file is gone."); + } + if ((document.flags & Document.FLAG_SUPPORTS_TYPED_DOCUMENT) == 0) { + return null; + } + if (!"*/*".equals(mimeTypeFilter)) { + // Not used by DocumentsUI, so don't bother implementing it. + throw new UnsupportedOperationException(); + } + return document.streamTypes.toArray(new String[document.streamTypes.size()]); + } + private ParcelFileDescriptor startWrite(final StubDocument document) throws FileNotFoundException { ParcelFileDescriptor[] pipe; @@ -398,14 +459,7 @@ public class StubProvider extends DocumentsProvider { row.add(Document.COLUMN_DISPLAY_NAME, document.file.getName()); row.add(Document.COLUMN_SIZE, document.file.length()); row.add(Document.COLUMN_MIME_TYPE, document.mimeType); - int flags = Document.FLAG_SUPPORTS_DELETE; - // TODO: Add support for renaming. - if (document.file.isDirectory()) { - flags |= Document.FLAG_DIR_SUPPORTS_CREATE; - } else { - flags |= Document.FLAG_SUPPORTS_WRITE; - } - row.add(Document.COLUMN_FLAGS, flags); + row.add(Document.COLUMN_FLAGS, document.flags); row.add(Document.COLUMN_LAST_MODIFIED, document.file.lastModified()); } @@ -439,37 +493,30 @@ public class StubProvider extends DocumentsProvider { } @VisibleForTesting - public Uri createFile(String rootId, String path, String mimeType, byte[] content) + public Uri createRegularFile(String rootId, String path, String mimeType, byte[] content) throws FileNotFoundException, IOException { - Log.d(TAG, "Creating test file " + rootId + ":" + path); - StubDocument root = mRoots.get(rootId).document; - if (root == null) { - throw new FileNotFoundException("No roots with the ID " + rootId + " were found"); - } - File file = new File(root.file, path.substring(1)); - StubDocument parent = mStorage.get(getDocumentIdForFile(file.getParentFile())); + final File file = createFile(rootId, path, mimeType, content); + final StubDocument parent = mStorage.get(getDocumentIdForFile(file.getParentFile())); if (parent == null) { - parent = mStorage.get(createFile(rootId, file.getParentFile().getPath(), - DocumentsContract.Document.MIME_TYPE_DIR, null)); - Log.d(TAG, "Created parent " + parent.documentId); - } else { - Log.d(TAG, "Found parent " + parent.documentId); + throw new FileNotFoundException("Parent not found."); } + final StubDocument document = StubDocument.createRegularDocument(file, mimeType, parent); + mStorage.put(document.documentId, document); + return DocumentsContract.buildDocumentUri(mAuthority, document.documentId); + } - if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) { - if (!file.mkdirs()) { - throw new FileNotFoundException("Couldn't create directory " + file.getPath()); - } - } else { - if (!file.createNewFile()) { - throw new FileNotFoundException("Couldn't create file " + file.getPath()); - } - // Add content to the file. - FileOutputStream fout = new FileOutputStream(file); - fout.write(content); - fout.close(); + @VisibleForTesting + public Uri createVirtualFile( + String rootId, String path, String mimeType, List<String> streamTypes, byte[] content) + throws FileNotFoundException, IOException { + final File file = createFile(rootId, path, mimeType, content); + final StubDocument parent = mStorage.get(getDocumentIdForFile(file.getParentFile())); + if (parent == null) { + throw new FileNotFoundException("Parent not found."); } - final StubDocument document = new StubDocument(file, mimeType, parent); + final StubDocument document = StubDocument.createVirtualDocument( + file, mimeType, streamTypes, parent); + mStorage.put(document.documentId, document); return DocumentsContract.buildDocumentUri(mAuthority, document.documentId); } @@ -489,21 +536,39 @@ public class StubProvider extends DocumentsProvider { return found.file; } - final class RootInfo { + private File createFile(String rootId, String path, String mimeType, byte[] content) + throws FileNotFoundException, IOException { + Log.d(TAG, "Creating test file " + rootId + ":" + path); + StubDocument root = mRoots.get(rootId).document; + if (root == null) { + throw new FileNotFoundException("No roots with the ID " + rootId + " were found"); + } + final File file = new File(root.file, path.substring(1)); + if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) { + if (!file.mkdirs()) { + throw new FileNotFoundException("Couldn't create directory " + file.getPath()); + } + } else { + if (!file.createNewFile()) { + throw new FileNotFoundException("Couldn't create file " + file.getPath()); + } + try (final FileOutputStream fout = new FileOutputStream(file)) { + fout.write(content); + } + } + return file; + } + + final static class RootInfo { public final String name; public final StubDocument document; public long capacity; public long size; - RootInfo(String name, long capacity) { - this.name = name; + RootInfo(File file, long capacity) { + this.name = file.getName(); this.capacity = 1024 * 1024; - // Make a subdir in the cache dir for each root. - File file = new File(getContext().getCacheDir(), name); - if (file.mkdir()) { - Log.i(TAG, "Created new root directory @ " + file.getPath()); - } - this.document = new StubDocument(file, Document.MIME_TYPE_DIR, this); + this.document = StubDocument.createRootDocument(file, this); this.capacity = capacity; this.size = 0; } @@ -513,38 +578,72 @@ public class StubProvider extends DocumentsProvider { } } - final class StubDocument { + final static class StubDocument { public final File file; - public final String mimeType; public final String documentId; + public final String mimeType; + public final List<String> streamTypes; + public final int flags; public final String parentId; public final RootInfo rootInfo; - StubDocument(File file, String mimeType, StubDocument parent) { + private StubDocument( + File file, String mimeType, List<String> streamTypes, int flags, + StubDocument parent) { this.file = file; - this.mimeType = mimeType; this.documentId = getDocumentIdForFile(file); + this.mimeType = mimeType; + this.streamTypes = streamTypes; + this.flags = flags; this.parentId = parent.documentId; this.rootInfo = parent.rootInfo; - mStorage.put(this.documentId, this); } - StubDocument(File file, String mimeType, RootInfo rootInfo) { + private StubDocument(File file, RootInfo rootInfo) { this.file = file; - this.mimeType = mimeType; this.documentId = getDocumentIdForFile(file); + this.mimeType = Document.MIME_TYPE_DIR; + this.streamTypes = new ArrayList<String>(); + this.flags = Document.FLAG_DIR_SUPPORTS_CREATE; this.parentId = null; this.rootInfo = rootInfo; - mStorage.put(this.documentId, this); } + + public static StubDocument createRootDocument(File file, RootInfo rootInfo) { + return new StubDocument(file, rootInfo); + } + + public static StubDocument createRegularDocument( + File file, String mimeType, StubDocument parent) { + int flags = Document.FLAG_SUPPORTS_DELETE; + if (file.isDirectory()) { + flags |= Document.FLAG_DIR_SUPPORTS_CREATE; + } else { + flags |= Document.FLAG_SUPPORTS_WRITE; + } + return new StubDocument(file, mimeType, new ArrayList<String>(), flags, parent); + } + + public static StubDocument createVirtualDocument( + File file, String mimeType, List<String> streamTypes, StubDocument parent) { + int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_WRITE + | Document.FLAG_VIRTUAL_DOCUMENT; + if (streamTypes.size() > 0) { + flags |= Document.FLAG_SUPPORTS_TYPED_DOCUMENT; + } + return new StubDocument(file, mimeType, streamTypes, flags, parent); + } + @Override public String toString() { return "StubDocument{" + "path:" + file.getPath() - + ", mimeType:" + mimeType - + ", rootInfo:" + rootInfo + ", documentId:" + documentId + + ", mimeType:" + mimeType + + ", streamTypes:" + streamTypes.toString() + + ", flags:" + flags + ", parentId:" + parentId + + ", rootInfo:" + rootInfo + "}"; } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java index cb49535ea55d..ac470671a582 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java @@ -66,10 +66,13 @@ class Mapper { database.beginTransaction(); try { final ContentValues[] valuesList = new ContentValues[1]; + final ContentValues[] extraValuesList = new ContentValues[1]; valuesList[0] = new ContentValues(); - MtpDatabase.getDeviceDocumentValues(valuesList[0], device); + extraValuesList[0] = new ContentValues(); + MtpDatabase.getDeviceDocumentValues(valuesList[0], extraValuesList[0], device); final boolean changed = putDocuments( valuesList, + extraValuesList, COLUMN_PARENT_DOCUMENT_ID + " IS NULL", EMPTY_ARGS, /* heuristic */ false, @@ -88,7 +91,7 @@ class Mapper { * @param roots List of root information. * @return If roots are added or removed from the database. */ - synchronized boolean putRootDocuments( + synchronized boolean putStorageDocuments( String parentDocumentId, Resources resources, MtpRoot[] roots) { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); database.beginTransaction(); @@ -109,36 +112,21 @@ class Mapper { throw new Error("Unexpected map mode."); } final ContentValues[] valuesList = new ContentValues[roots.length]; + final ContentValues[] extraValuesList = new ContentValues[roots.length]; for (int i = 0; i < roots.length; i++) { valuesList[i] = new ContentValues(); + extraValuesList[i] = new ContentValues(); MtpDatabase.getStorageDocumentValues( - valuesList[i], resources, parentDocumentId, roots[i]); + valuesList[i], extraValuesList[i], resources, parentDocumentId, roots[i]); } final boolean changed = putDocuments( valuesList, + extraValuesList, COLUMN_PARENT_DOCUMENT_ID + "=?", strings(parentDocumentId), heuristic, mapColumn); - final ContentValues values = new ContentValues(); - int i = 0; - for (final MtpRoot root : roots) { - // Use the same value for the root ID and the corresponding document ID. - final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID); - // If it fails to insert/update documents, the document ID will be set with -1. - // In this case we don't insert/update root extra information neither. - if (documentId == null) { - continue; - } - values.put(Root.COLUMN_ROOT_ID, documentId); - values.put( - Root.COLUMN_FLAGS, - Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); - values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace); - values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity); - values.put(Root.COLUMN_MIME_TYPES, ""); - database.replace(TABLE_ROOT_EXTRA, null, values); - } + database.setTransactionSuccessful(); return changed; } finally { @@ -176,6 +164,7 @@ class Mapper { } putDocuments( valuesList, + null, COLUMN_PARENT_DOCUMENT_ID + "=?", strings(parentId), heuristic, @@ -257,6 +246,7 @@ class Mapper { * rows. If the methods adds rows to database, it updates valueList with correct document ID. * * @param valuesList Values for documents to be stored in the database. + * @param rootExtraValuesList Values for root extra to be stored in the database. * @param selection SQL where closure to select rows that shares the same parent. * @param args Argument for selection SQL. * @param heuristic Whether the mapping mode is heuristic. @@ -264,6 +254,7 @@ class Mapper { */ private boolean putDocuments( ContentValues[] valuesList, + @Nullable ContentValues[] rootExtraValuesList, String selection, String[] args, boolean heuristic, @@ -272,7 +263,14 @@ class Mapper { boolean added = false; database.beginTransaction(); try { - for (final ContentValues values : valuesList) { + for (int i = 0; i < valuesList.length; i++) { + final ContentValues values = valuesList[i]; + final ContentValues rootExtraValues; + if (rootExtraValuesList != null) { + rootExtraValues = rootExtraValuesList[i]; + } else { + rootExtraValues = null; + } final Cursor candidateCursor = database.query( TABLE_DOCUMENTS, strings(Document.COLUMN_DOCUMENT_ID), @@ -290,25 +288,26 @@ class Mapper { final long rowId; if (candidateCursor.getCount() == 0) { rowId = database.insert(TABLE_DOCUMENTS, null, values); - if (rowId == -1) { - throw new SQLiteException("Failed to put a document into database."); - } added = true; } else if (!heuristic) { candidateCursor.moveToNext(); - final String documentId = candidateCursor.getString(0); - rowId = database.update( + rowId = candidateCursor.getLong(0); + database.update( TABLE_DOCUMENTS, values, SELECTION_DOCUMENT_ID, - strings(documentId)); + strings(rowId)); } else { values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING); - rowId = database.insert(TABLE_DOCUMENTS, null, values); + rowId = database.insertOrThrow(TABLE_DOCUMENTS, null, values); } // Document ID is a primary integer key of the table. So the returned row // IDs should be same with the document ID. values.put(Document.COLUMN_DOCUMENT_ID, rowId); + if (rootExtraValues != null) { + rootExtraValues.put(Root.COLUMN_ROOT_ID, rowId); + database.replace(TABLE_ROOT_EXTRA, null, rootExtraValues); + } } finally { candidateCursor.close(); } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java index 10941eb02a0a..112914e93830 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java @@ -23,6 +23,9 @@ import android.content.ContentValues; import android.content.Context; import android.content.res.Resources; import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.MatrixCursor; +import android.database.MatrixCursor.RowBuilder; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; @@ -106,17 +109,93 @@ class MtpDatabase { * @return Database cursor. */ Cursor queryRoots(String[] columnNames) { - final SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); - builder.setTables(JOIN_ROOTS); - builder.setProjectionMap(COLUMN_MAP_ROOTS); - return builder.query( - mDatabase, - columnNames, - COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_DOCUMENT_TYPE + " = ?", - strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, DOCUMENT_TYPE_STORAGE), + final String selection = + COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_DOCUMENT_TYPE + " = ?"; + final Cursor deviceCursor = mDatabase.query( + TABLE_DOCUMENTS, + strings(COLUMN_DEVICE_ID), + selection, + strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, DOCUMENT_TYPE_DEVICE), + COLUMN_DEVICE_ID, null, null, null); + + try { + final SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); + builder.setTables(JOIN_ROOTS); + builder.setProjectionMap(COLUMN_MAP_ROOTS); + final MatrixCursor result = new MatrixCursor(columnNames); + final ContentValues values = new ContentValues(); + + while (deviceCursor.moveToNext()) { + final int deviceId = deviceCursor.getInt(0); + final Cursor storageCursor = builder.query( + mDatabase, + columnNames, + selection + " AND " + COLUMN_DEVICE_ID + " = ?", + strings(ROW_STATE_VALID, + ROW_STATE_INVALIDATED, + DOCUMENT_TYPE_STORAGE, + deviceId), + null, + null, + null); + try { + values.clear(); + if (storageCursor.getCount() == 1) { + storageCursor.moveToNext(); + DatabaseUtils.cursorRowToContentValues(storageCursor, values); + } else { + final Cursor cursor = builder.query( + mDatabase, + columnNames, + selection + " AND " + COLUMN_DEVICE_ID + " = ?", + strings(ROW_STATE_VALID, + ROW_STATE_INVALIDATED, + DOCUMENT_TYPE_DEVICE, + deviceId), + null, + null, + null); + try { + cursor.moveToNext(); + DatabaseUtils.cursorRowToContentValues(cursor, values); + } finally { + cursor.close(); + } + + long capacityBytes = 0; + long availableBytes = 0; + int capacityIndex = cursor.getColumnIndex(Root.COLUMN_CAPACITY_BYTES); + int availableIndex = cursor.getColumnIndex(Root.COLUMN_AVAILABLE_BYTES); + while (storageCursor.moveToNext()) { + // If requested columnNames does not include COLUMN_XXX_BYTES, we don't + // calculate corresponding values. + if (capacityIndex != -1) { + capacityBytes += cursor.getLong(capacityIndex); + } + if (availableIndex != -1) { + availableBytes += cursor.getLong(availableIndex); + } + } + values.put(Root.COLUMN_CAPACITY_BYTES, capacityBytes); + values.put(Root.COLUMN_AVAILABLE_BYTES, availableBytes); + } + } finally { + storageCursor.close(); + } + + final RowBuilder row = result.newRow(); + for (final String key : values.keySet()) { + row.add(key, values.get(key)); + } + } + + return result; + } finally { + deviceCursor.close(); + } } /** @@ -380,7 +459,10 @@ class MtpDatabase { context.deleteDatabase(DATABASE_NAME); } - static void getDeviceDocumentValues(ContentValues values, MtpDeviceRecord device) { + static void getDeviceDocumentValues( + ContentValues values, + ContentValues extraValues, + MtpDeviceRecord device) { values.clear(); values.put(COLUMN_DEVICE_ID, device.deviceId); values.putNull(COLUMN_STORAGE_ID); @@ -394,11 +476,15 @@ class MtpDatabase { values.putNull(Document.COLUMN_LAST_MODIFIED); values.put(Document.COLUMN_ICON, R.drawable.ic_root_mtp); values.put(Document.COLUMN_FLAGS, 0); - long size = 0; - for (final MtpRoot root : device.roots) { - size += root.mMaxCapacity - root.mFreeSpace; - } - values.put(Document.COLUMN_SIZE, size); + values.putNull(Document.COLUMN_SIZE); + + extraValues.clear(); + extraValues.put( + Root.COLUMN_FLAGS, + Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); + extraValues.putNull(Root.COLUMN_AVAILABLE_BYTES); + extraValues.putNull(Root.COLUMN_CAPACITY_BYTES); + extraValues.put(Root.COLUMN_MIME_TYPES, ""); } /** @@ -408,7 +494,11 @@ class MtpDatabase { * @param root Root to be converted {@link ContentValues}. */ static void getStorageDocumentValues( - ContentValues values, Resources resources, String parentDocumentId, MtpRoot root) { + ContentValues values, + ContentValues extraValues, + Resources resources, + String parentDocumentId, + MtpRoot root) { values.clear(); values.put(COLUMN_DEVICE_ID, root.mDeviceId); values.put(COLUMN_STORAGE_ID, root.mStorageId); @@ -424,6 +514,13 @@ class MtpDatabase { values.put(Document.COLUMN_FLAGS, 0); values.put(Document.COLUMN_SIZE, (int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE)); + + extraValues.put( + Root.COLUMN_FLAGS, + Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); + extraValues.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace); + extraValues.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity); + extraValues.put(Root.COLUMN_MIME_TYPES, ""); } /** diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java index a23358935987..f252e0f7d337 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java @@ -126,14 +126,14 @@ class MtpDatabaseConstants { Document.COLUMN_LAST_MODIFIED + " INTEGER," + Document.COLUMN_ICON + " INTEGER," + Document.COLUMN_FLAGS + " INTEGER NOT NULL," + - Document.COLUMN_SIZE + " INTEGER NOT NULL);"; + Document.COLUMN_SIZE + " INTEGER);"; static final String QUERY_CREATE_ROOT_EXTRA = "CREATE TABLE " + TABLE_ROOT_EXTRA + " (" + Root.COLUMN_ROOT_ID + " INTEGER PRIMARY KEY," + Root.COLUMN_FLAGS + " INTEGER NOT NULL," + - Root.COLUMN_AVAILABLE_BYTES + " INTEGER NOT NULL," + - Root.COLUMN_CAPACITY_BYTES + " INTEGER NOT NULL," + + Root.COLUMN_AVAILABLE_BYTES + " INTEGER," + + Root.COLUMN_CAPACITY_BYTES + " INTEGER," + Root.COLUMN_MIME_TYPES + " TEXT NOT NULL);"; /** @@ -145,18 +145,26 @@ class MtpDatabaseConstants { COLUMN_MAP_ROOTS = new HashMap<>(); COLUMN_MAP_ROOTS.put(Root.COLUMN_ROOT_ID, TABLE_ROOT_EXTRA + "." + Root.COLUMN_ROOT_ID); COLUMN_MAP_ROOTS.put(Root.COLUMN_FLAGS, TABLE_ROOT_EXTRA + "." + Root.COLUMN_FLAGS); - COLUMN_MAP_ROOTS.put(Root.COLUMN_ICON, TABLE_DOCUMENTS + "." + Document.COLUMN_ICON); COLUMN_MAP_ROOTS.put( - Root.COLUMN_TITLE, TABLE_DOCUMENTS + "." + Document.COLUMN_DISPLAY_NAME); - COLUMN_MAP_ROOTS.put(Root.COLUMN_SUMMARY, TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY); + Root.COLUMN_ICON, + TABLE_DOCUMENTS + "." + Document.COLUMN_ICON + " AS " + Root.COLUMN_ICON); COLUMN_MAP_ROOTS.put( - Root.COLUMN_DOCUMENT_ID, TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID); + Root.COLUMN_TITLE, + TABLE_DOCUMENTS + "." + Document.COLUMN_DISPLAY_NAME + " AS " + Root.COLUMN_TITLE); + COLUMN_MAP_ROOTS.put( + Root.COLUMN_SUMMARY, + TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY + " AS " + Root.COLUMN_SUMMARY); + COLUMN_MAP_ROOTS.put( + Root.COLUMN_DOCUMENT_ID, + TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + + " AS " + Root.COLUMN_DOCUMENT_ID); COLUMN_MAP_ROOTS.put( Root.COLUMN_AVAILABLE_BYTES, TABLE_ROOT_EXTRA + "." + Root.COLUMN_AVAILABLE_BYTES); COLUMN_MAP_ROOTS.put( Root.COLUMN_CAPACITY_BYTES, TABLE_ROOT_EXTRA + "." + Root.COLUMN_CAPACITY_BYTES); COLUMN_MAP_ROOTS.put( Root.COLUMN_MIME_TYPES, TABLE_ROOT_EXTRA + "." + Root.COLUMN_MIME_TYPES); + COLUMN_MAP_ROOTS.put(COLUMN_DEVICE_ID, COLUMN_DEVICE_ID); } private static String createJoinFromClosure( diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java index e6c2726f531a..619ef54ed90e 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java @@ -127,7 +127,7 @@ final class RootScanner { continue; } mDatabase.getMapper().startAddingDocuments(documentId); - if (mDatabase.getMapper().putRootDocuments( + if (mDatabase.getMapper().putStorageDocuments( documentId, mResources, device.roots)) { changed = true; } diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java index 7bd9a1790739..f37a55c9568d 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java @@ -41,7 +41,7 @@ public class DocumentLoaderTest extends AndroidTestCase { public void setUp() { mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", new TestResources(), new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", new TestResources(), new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") }); mDatabase.getMapper().stopAddingDocuments("deviceDocId"); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java index b74517574c2e..1e1ea0a748a8 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java @@ -74,20 +74,24 @@ public class MtpDatabaseTest extends AndroidTestCase { return cursor.getString(cursor.getColumnIndex(columnName)); } - public void testPutRootDocuments() throws Exception { - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { - new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""), - new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""), - new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"") + public void testPutSingleStorageDocuments() throws Exception { + mDatabase.getMapper().startAddingDocuments(null); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(0, "Device", true, new MtpRoot[0])); + mDatabase.getMapper().stopAddingDocuments(null); + + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { + new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, "") }); + mDatabase.getMapper().stopAddingDocuments("1"); { final Cursor cursor = mDatabase.queryRootDocuments(COLUMN_NAMES); - assertEquals(3, cursor.getCount()); + assertEquals(1, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID)); assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID)); assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID)); assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE)); @@ -101,14 +105,6 @@ public class MtpDatabaseTest extends AndroidTestCase { assertEquals( MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE, getInt(cursor, COLUMN_DOCUMENT_TYPE)); - cursor.moveToNext(); - assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID)); - assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); - - cursor.moveToNext(); - assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID)); - assertEquals("Device /@#%&<>Storage", getString(cursor, COLUMN_DISPLAY_NAME)); - cursor.close(); } @@ -123,37 +119,58 @@ public class MtpDatabaseTest extends AndroidTestCase { Root.COLUMN_AVAILABLE_BYTES, Root.COLUMN_CAPACITY_BYTES }); + assertEquals(1, cursor.getCount()); + + cursor.moveToNext(); + assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals( + Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, + getInt(cursor, Root.COLUMN_FLAGS)); + assertEquals(R.drawable.ic_root_mtp, getInt(cursor, Root.COLUMN_ICON)); + assertEquals("Device Storage", getString(cursor, Root.COLUMN_TITLE)); + assertTrue(isNull(cursor, Root.COLUMN_SUMMARY)); + assertEquals(2, getInt(cursor, Root.COLUMN_DOCUMENT_ID)); + assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); + assertEquals(2000, getInt(cursor, Root.COLUMN_CAPACITY_BYTES)); + + cursor.close(); + } + } + + public void testPutStorageDocuments() throws Exception { + mDatabase.getMapper().startAddingDocuments("deviceDocId"); + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { + new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""), + new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""), + new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"") + }); + + { + final Cursor cursor = mDatabase.queryRootDocuments(COLUMN_NAMES); assertEquals(3, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, cursor.getInt(0)); - assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); - assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); - assertEquals("Device Storage", cursor.getString(3)); - assertTrue(cursor.isNull(4)); - assertEquals(1, cursor.getInt(5)); - assertEquals(1000, cursor.getInt(6)); - assertEquals(2000, cursor.getInt(7)); + assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID)); + assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID)); + assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE)); + assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, getString(cursor, COLUMN_MIME_TYPE)); + assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); + assertTrue(isNull(cursor, COLUMN_SUMMARY)); + assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED)); + assertEquals(R.drawable.ic_root_mtp, getInt(cursor, COLUMN_ICON)); + assertEquals(0, getInt(cursor, COLUMN_FLAGS)); + assertEquals(1000, getInt(cursor, COLUMN_SIZE)); + assertEquals( + MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE, getInt(cursor, COLUMN_DOCUMENT_TYPE)); cursor.moveToNext(); - assertEquals(2, cursor.getInt(0)); - assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); - assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); - assertEquals("Device Storage", cursor.getString(3)); - assertTrue(cursor.isNull(4)); - assertEquals(2, cursor.getInt(5)); - assertEquals(2000, cursor.getInt(6)); - assertEquals(4000, cursor.getInt(7)); + assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.moveToNext(); - assertEquals(3, cursor.getInt(0)); - assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); - assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); - assertEquals("Device /@#%&<>Storage", cursor.getString(3)); - assertTrue(cursor.isNull(4)); - assertEquals(3, cursor.getInt(5)); - assertEquals(3000, cursor.getInt(6)); - assertEquals(6000, cursor.getInt(7)); + assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals("Device /@#%&<>Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.close(); } @@ -245,13 +262,9 @@ public class MtpDatabaseTest extends AndroidTestCase { MtpDatabaseConstants.COLUMN_STORAGE_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME }; - final String[] rootColumns = new String[] { - Root.COLUMN_ROOT_ID, - Root.COLUMN_AVAILABLE_BYTES - }; mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""), new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "") }); @@ -270,18 +283,6 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } - { - final Cursor cursor = mDatabase.queryRoots(rootColumns); - assertEquals(2, cursor.getCount()); - cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.moveToNext(); - assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(1001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.close(); - } - mDatabase.getMapper().clearMapping(); { @@ -298,20 +299,8 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } - { - final Cursor cursor = mDatabase.queryRoots(rootColumns); - assertEquals(2, cursor.getCount()); - cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.moveToNext(); - assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(1001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.close(); - } - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""), new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "") }); @@ -334,21 +323,6 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } - { - final Cursor cursor = mDatabase.queryRoots(rootColumns); - assertEquals(3, cursor.getCount()); - cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.moveToNext(); - assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(1001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.moveToNext(); - assertEquals(4, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(2002, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.close(); - } - mDatabase.getMapper().stopAddingDocuments("deviceDocId"); { @@ -364,18 +338,6 @@ public class MtpDatabaseTest extends AndroidTestCase { assertEquals("Device Storage C", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.close(); } - - { - final Cursor cursor = mDatabase.queryRoots(rootColumns); - assertEquals(2, cursor.getCount()); - cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(2000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.moveToNext(); - assertEquals(4, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(2002, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.close(); - } } public void testRestoreIdForChildDocuments() throws Exception { @@ -461,26 +423,33 @@ public class MtpDatabaseTest extends AndroidTestCase { Root.COLUMN_ROOT_ID, Root.COLUMN_AVAILABLE_BYTES }; - mDatabase.getMapper().startAddingDocuments("deviceDocIdA"); - mDatabase.getMapper().startAddingDocuments("deviceDocIdB"); - mDatabase.getMapper().putRootDocuments("deviceDocIdA", resources, new MtpRoot[] { - new MtpRoot(0, 100, "Device", "Storage", 0, 0, "") + mDatabase.getMapper().startAddingDocuments(null); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(0, "Device A", true, new MtpRoot[0])); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(1, "Device B", true, new MtpRoot[0])); + mDatabase.getMapper().stopAddingDocuments(null); + + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().startAddingDocuments("2"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { + new MtpRoot(0, 100, "Device A", "Storage", 0, 0, "") }); - mDatabase.getMapper().putRootDocuments("deviceDocIdB", resources, new MtpRoot[] { - new MtpRoot(1, 100, "Device", "Storage", 0, 0, "") + mDatabase.getMapper().putStorageDocuments("2", resources, new MtpRoot[] { + new MtpRoot(1, 100, "Device B", "Storage", 0, 0, "") }); { final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID)); assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID)); - assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); + assertEquals("Device A Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.moveToNext(); - assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID)); assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID)); - assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); + assertEquals("Device B Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.close(); } @@ -488,36 +457,36 @@ public class MtpDatabaseTest extends AndroidTestCase { final Cursor cursor = mDatabase.queryRoots(rootColumns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals(3, getInt(cursor, Root.COLUMN_ROOT_ID)); assertEquals(0, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); cursor.moveToNext(); - assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals(4, getInt(cursor, Root.COLUMN_ROOT_ID)); assertEquals(0, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); cursor.close(); } mDatabase.getMapper().clearMapping(); - mDatabase.getMapper().startAddingDocuments("deviceDocIdA"); - mDatabase.getMapper().startAddingDocuments("deviceDocIdB"); - mDatabase.getMapper().putRootDocuments("deviceDocIdA", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().startAddingDocuments("2"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "") }); - mDatabase.getMapper().putRootDocuments("deviceDocIdB", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("2", resources, new MtpRoot[] { new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "") }); - mDatabase.getMapper().stopAddingDocuments("deviceDocIdA"); - mDatabase.getMapper().stopAddingDocuments("deviceDocIdB"); + mDatabase.getMapper().stopAddingDocuments("1"); + mDatabase.getMapper().stopAddingDocuments("2"); { final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID)); assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID)); assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.moveToNext(); - assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(6, getInt(cursor, COLUMN_DOCUMENT_ID)); assertEquals(300, getInt(cursor, COLUMN_STORAGE_ID)); assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.close(); @@ -527,10 +496,10 @@ public class MtpDatabaseTest extends AndroidTestCase { final Cursor cursor = mDatabase.queryRoots(rootColumns); assertEquals(2, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals(5, getInt(cursor, Root.COLUMN_ROOT_ID)); assertEquals(2000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); cursor.moveToNext(); - assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals(6, getInt(cursor, Root.COLUMN_ROOT_ID)); assertEquals(3000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); cursor.close(); } @@ -591,29 +560,34 @@ public class MtpDatabaseTest extends AndroidTestCase { Root.COLUMN_AVAILABLE_BYTES }; - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments(null); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(0, "Device", false, new MtpRoot[0])); + mDatabase.getMapper().stopAddingDocuments(null); + + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""), }); mDatabase.getMapper().clearMapping(); - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""), }); mDatabase.getMapper().clearMapping(); - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""), }); - mDatabase.getMapper().stopAddingDocuments("deviceDocId"); + mDatabase.getMapper().stopAddingDocuments("1"); { final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(1, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID)); assertEquals(300, getInt(cursor, COLUMN_STORAGE_ID)); assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.close(); @@ -622,7 +596,7 @@ public class MtpDatabaseTest extends AndroidTestCase { final Cursor cursor = mDatabase.queryRoots(rootColumns); assertEquals(1, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); assertEquals(3000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); cursor.close(); } @@ -634,19 +608,15 @@ public class MtpDatabaseTest extends AndroidTestCase { MtpDatabaseConstants.COLUMN_STORAGE_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME }; - final String[] rootColumns = new String[] { - Root.COLUMN_ROOT_ID, - Root.COLUMN_AVAILABLE_BYTES - }; mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""), }); mDatabase.getMapper().clearMapping(); mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""), new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""), }); @@ -665,33 +635,27 @@ public class MtpDatabaseTest extends AndroidTestCase { assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.close(); } - { - final Cursor cursor = mDatabase.queryRoots(rootColumns); - assertEquals(2, cursor.getCount()); - cursor.moveToNext(); - assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(2000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.moveToNext(); - assertEquals(3, getInt(cursor, Root.COLUMN_ROOT_ID)); - assertEquals(2001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); - cursor.close(); - } } public void testReplaceExistingRoots() { + mDatabase.getMapper().startAddingDocuments(null); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(0, "Device", true, new MtpRoot[0])); + mDatabase.getMapper().stopAddingDocuments(null); + // The client code should be able to replace existing rows with new information. // Add one. - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); - mDatabase.getMapper().stopAddingDocuments("deviceDocId"); + mDatabase.getMapper().stopAddingDocuments("1"); // Replace it. - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""), }); - mDatabase.getMapper().stopAddingDocuments("deviceDocId"); + mDatabase.getMapper().stopAddingDocuments("1"); { final String[] columns = new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, @@ -701,7 +665,7 @@ public class MtpDatabaseTest extends AndroidTestCase { final Cursor cursor = mDatabase.queryRootDocuments(columns); assertEquals(1, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID)); + assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID)); assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID)); assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME)); cursor.close(); @@ -709,12 +673,14 @@ public class MtpDatabaseTest extends AndroidTestCase { { final String[] columns = new String[] { Root.COLUMN_ROOT_ID, + Root.COLUMN_TITLE, Root.COLUMN_AVAILABLE_BYTES }; final Cursor cursor = mDatabase.queryRoots(columns); assertEquals(1, cursor.getCount()); cursor.moveToNext(); - assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID)); + assertEquals("Device Storage B", getString(cursor, Root.COLUMN_TITLE)); assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES)); cursor.close(); } @@ -723,8 +689,13 @@ public class MtpDatabaseTest extends AndroidTestCase { public void testFailToReplaceExisitingUnmappedRoots() { // The client code should not be able to replace rows before resolving 'unmapped' rows. // Add one. - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments(null); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(0, "Device", true, new MtpRoot[0])); + mDatabase.getMapper().stopAddingDocuments(null); + + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.getMapper().clearMapping(); @@ -732,18 +703,19 @@ public class MtpDatabaseTest extends AndroidTestCase { assertEquals(1, oldCursor.getCount()); // Add one. - mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 101, "Device", "Storage B", 1000, 1000, ""), }); // Add one more before resolving unmapped documents. - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { new MtpRoot(0, 102, "Device", "Storage B", 1000, 1000, ""), }); - mDatabase.getMapper().stopAddingDocuments("deviceDocId"); + mDatabase.getMapper().stopAddingDocuments("1"); // Because the roots shares the same name, the roots should have new IDs. - final Cursor newCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID)); + final Cursor newCursor = mDatabase.queryChildDocuments( + strings(Document.COLUMN_DOCUMENT_ID), "1"); assertEquals(2, newCursor.getCount()); oldCursor.moveToNext(); newCursor.moveToNext(); @@ -755,9 +727,9 @@ public class MtpDatabaseTest extends AndroidTestCase { newCursor.close(); } - public void testQueryDocument() { + public void testQueryDocuments() { mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.getMapper().stopAddingDocuments("deviceDocId"); @@ -765,13 +737,61 @@ public class MtpDatabaseTest extends AndroidTestCase { final Cursor cursor = mDatabase.queryDocument("1", strings(Document.COLUMN_DISPLAY_NAME)); assertEquals(1, cursor.getCount()); cursor.moveToNext(); - assertEquals("Device Storage A", cursor.getString(0)); + assertEquals("Device Storage A", getString(cursor, Document.COLUMN_DISPLAY_NAME)); cursor.close(); } + public void testQueryRoots() { + // Add device document. + mDatabase.getMapper().startAddingDocuments(null); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(0, "Device", false, new MtpRoot[0])); + mDatabase.getMapper().stopAddingDocuments(null); + + // It the device does not have storages, it shows a device root. + { + final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE)); + assertEquals(1, cursor.getCount()); + cursor.moveToNext(); + assertEquals("Device", cursor.getString(0)); + cursor.close(); + } + + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { + new MtpRoot(0, 100, "Device", "Storage A", 0, 0, "") + }); + mDatabase.getMapper().stopAddingDocuments("1"); + + // It the device has single storage, it shows a storage root. + { + final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE)); + assertEquals(1, cursor.getCount()); + cursor.moveToNext(); + assertEquals("Device Storage A", cursor.getString(0)); + cursor.close(); + } + + mDatabase.getMapper().startAddingDocuments("1"); + mDatabase.getMapper().putStorageDocuments("1", resources, new MtpRoot[] { + new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), + new MtpRoot(0, 101, "Device", "Storage B", 0, 0, "") + }); + mDatabase.getMapper().stopAddingDocuments("1"); + + // It the device has multiple storages, it shows a device root. + { + final Cursor cursor = mDatabase.queryRoots(strings(Root.COLUMN_TITLE)); + assertEquals(1, cursor.getCount()); + cursor.moveToNext(); + assertEquals("Device", cursor.getString(0)); + cursor.close(); + } + } + public void testGetParentId() throws FileNotFoundException { mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.getMapper().stopAddingDocuments("deviceDocId"); @@ -790,7 +810,7 @@ public class MtpDatabaseTest extends AndroidTestCase { public void testDeleteDocument() { mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.getMapper().stopAddingDocuments("deviceDocId"); @@ -834,7 +854,7 @@ public class MtpDatabaseTest extends AndroidTestCase { public void testPutNewDocument() { mDatabase.getMapper().startAddingDocuments("deviceDocId"); - mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("deviceDocId", resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.getMapper().stopAddingDocuments("deviceDocId"); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index 884b1e2ccb1d..71c48971ca32 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -147,7 +147,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { mProvider.openDevice(0); mResolver.waitForNotification(ROOTS_URI, 1); final Cursor cursor = mProvider.queryRoots(null); - assertEquals(1, cursor.getCount()); + assertEquals(2, cursor.getCount()); cursor.moveToNext(); assertEquals("3", cursor.getString(0)); assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); @@ -176,7 +176,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { public void testQueryRoots_error() throws Exception { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); mMtpManager.addValidDevice( - new MtpDeviceRecord(0, "Device", false /* unopened */, new MtpRoot[0])); + new MtpDeviceRecord(0, "Device A", false /* unopened */, new MtpRoot[0])); mMtpManager.addValidDevice(new MtpDeviceRecord( 1, "Device", @@ -197,7 +197,16 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { mResolver.waitForNotification(ROOTS_URI, 1); final Cursor cursor = mProvider.queryRoots(null); - assertEquals(1, cursor.getCount()); + assertEquals(2, cursor.getCount()); + + cursor.moveToNext(); + assertEquals("1", cursor.getString(0)); + assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); + assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); + assertEquals("Device A", cursor.getString(3)); + assertEquals("1", cursor.getString(4)); + assertEquals(0, cursor.getInt(5)); + cursor.moveToNext(); assertEquals("3", cursor.getString(0)); assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 6680d8857ccb..51d8ca03d4af 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -25,6 +25,7 @@ <!-- Comma-separated list of bluetooth, wifi, and cell. --> <string name="def_airplane_mode_radios" translatable="false">cell,bluetooth,wifi,nfc,wimax</string> <string name="airplane_mode_toggleable_radios" translatable="false">bluetooth,wifi,nfc</string> + <string name="def_bluetooth_disabled_profiles" translatable="false">0</string> <bool name="def_auto_time">true</bool> <bool name="def_auto_time_zone">true</bool> <bool name="def_accelerometer_rotation">true</bool> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index d4e428e280b6..7338a9cb654b 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -116,6 +116,12 @@ class DatabaseHelper extends SQLiteOpenHelper { // cleaned up automatically when the user is deleted. File databaseFile = new File( Environment.getUserSystemDirectory(userHandle), DATABASE_NAME); + // If databaseFile doesn't exist, database can be kept in memory. It's safe because the + // database will be migrated and disposed of immediately after onCreate finishes + if (!databaseFile.exists()) { + Log.i(TAG, "No previous database file exists - running in in-memory mode"); + return null; + } return databaseFile.getPath(); } } @@ -130,8 +136,16 @@ class DatabaseHelper extends SQLiteOpenHelper { return mValidTables.contains(name); } + private boolean isInMemory() { + return getDatabaseName() == null; + } + public void dropDatabase() { close(); + // No need to remove files if db is in memory + if (isInMemory()) { + return; + } File databaseFile = mContext.getDatabasePath(getDatabaseName()); if (databaseFile.exists()) { databaseFile.delete(); @@ -145,6 +159,10 @@ class DatabaseHelper extends SQLiteOpenHelper { public void backupDatabase() { close(); + // No need to backup files if db is in memory + if (isInMemory()) { + return; + } File databaseFile = mContext.getDatabasePath(getDatabaseName()); if (!databaseFile.exists()) { return; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 7365e666ae3c..bcb459a174a0 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1890,7 +1890,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 123; + private static final int SETTINGS_VERSION = 124; private final int mUserId; @@ -2060,6 +2060,16 @@ public class SettingsProvider extends ContentProvider { } currentVersion = 123; } + + if (currentVersion == 123) { + final SettingsState globalSettings = getGlobalSettingsLocked(); + String defaultDisabledProfiles = (getContext().getResources().getString( + R.string.def_bluetooth_disabled_profiles)); + globalSettings.insertSettingLocked(Settings.Global.BLUETOOTH_DISABLED_PROFILES, + defaultDisabledProfiles, SettingsState.SYSTEM_PACKAGE_NAME); + currentVersion = 124; + } + // vXXX: Add new settings above this point. // Return the current version. diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml index c9dbc79f5626..f15c97ef6694 100644 --- a/packages/SystemUI/res/layout/notification_guts.xml +++ b/packages/SystemUI/res/layout/notification_guts.xml @@ -24,35 +24,23 @@ android:clickable="true" android:gravity="top|start" android:orientation="vertical" - android:paddingEnd="8dp" + android:paddingStart="@*android:dimen/notification_content_margin_start" + android:paddingEnd="@*android:dimen/notification_content_margin_end" android:background="@color/notification_guts_text_color" > <!-- header --> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingStart="@*android:dimen/notification_content_margin_start" - android:paddingTop="8dp" - android:paddingBottom="16dp" > + android:paddingBottom="8dp" > <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/notification_guts_header" - android:orientation="vertical" + android:orientation="horizontal" android:layout_gravity="center_vertical|start" android:layout_marginEnd="52dp"> - - <LinearLayout - android:id="@+id/notification_guts_app_details" - android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:clipChildren="false" - android:layout_gravity="start|top" - android:gravity="center_vertical" - > - <ImageView android:id="@android:id/icon" android:layout_width="18dp" @@ -76,22 +64,12 @@ android:layout_gravity="bottom|start" android:visibility="gone" android:textColor="#ffffff" /> - </LinearLayout> - - <TextView - android:id="@+id/topic_details" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textAppearance="@android:style/TextAppearance.Material.Notification.Title" - android:textColor="@color/notification_guts_text_color" - android:layout_alignParentBottom="true" - android:layout_alignParentStart="true" /> - </LinearLayout> + </LinearLayout> <ImageButton style="@android:style/Widget.Material.Light.Button.Borderless.Small" android:id="@+id/notification_inspect_item" android:layout_width="52dp" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:layout_weight="0" android:gravity="center" android:layout_gravity="center_vertical|end" @@ -103,7 +81,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" - android:paddingStart="@*android:dimen/notification_content_margin_start" android:orientation="vertical" android:clickable="false" android:focusable="false" @@ -116,8 +93,7 @@ android:textAppearance="@android:style/TextAppearance.Material.Subhead" android:textColor="@color/notification_guts_text_color" android:ellipsize="marquee" - android:fadingEdge="horizontal" - android:text="@*android:string/notification_importance_title"/> + android:fadingEdge="horizontal"/> <TextView android:id="@+id/summary" @@ -133,7 +109,7 @@ <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingTop="6dp" > + android:paddingTop="8dp" > <ImageView android:id="@+id/low_importance" @@ -162,5 +138,21 @@ android:layout_height="24dp"/> </FrameLayout> + + <RadioGroup android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingTop="8dp"> + <RadioButton android:id="@+id/apply_to_topic" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/notification_guts_text_color" + android:visibility="gone"/> + <RadioButton android:id="@+id/apply_to_app" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/apply_to_app" + android:textColor="@color/notification_guts_text_color" + android:visibility="gone"/> + </RadioGroup> </LinearLayout> </com.android.systemui.statusbar.NotificationGuts> diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml index b8caf2312662..04f18c527c68 100644 --- a/packages/SystemUI/res/layout/recents_task_view_header.xml +++ b/packages/SystemUI/res/layout/recents_task_view_header.xml @@ -20,7 +20,7 @@ android:layout_height="@dimen/recents_task_bar_height" android:layout_gravity="top|center_horizontal"> <com.android.systemui.recents.views.FixedSizeImageView - android:id="@+id/application_icon" + android:id="@+id/icon" android:contentDescription="@string/recents_app_info_button_label" android:layout_width="@dimen/recents_task_view_application_icon_size" android:layout_height="@dimen/recents_task_view_application_icon_size" @@ -29,7 +29,7 @@ android:padding="8dp" android:background="@drawable/recents_button_bg" /> <TextView - android:id="@+id/activity_description" + android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical|start" diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 76c08f649179..0ccc236aee8b 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -69,7 +69,7 @@ <!-- The lock to task button foreground color. --> <color name="recents_task_view_lock_to_app_button_color">#ff666666</color> <!-- The background color for the freeform workspace. --> - <color name="recents_freeform_workspace_bg_color">#66000000</color> + <color name="recents_freeform_workspace_bg_color">#33FFFFFF</color> <color name="keyguard_affordance">#ffffffff</color> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f71a71ae378b..50e0661c5588 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1201,4 +1201,34 @@ <!-- Bluetooth enablement ok text [CHAR LIMIT=40] --> <string name="enable_bluetooth_confirmation_ok">Turn on</string> + <!-- Apply notification importance setting to a topic [CHAR LIMIT=NONE] --> + <string name="apply_to_topic">Apply to <xliff:g id="topic_name" example="Friend Request">%1$s</xliff:g> notifications</string> + <!-- Apply notification importance setting to an app [CHAR LIMIT=NONE] --> + <string name="apply_to_app">Apply to all notifications from this app</string> + <!-- Notification importance title, blocked status--> + <string name="blocked_importance">Blocked</string> + <!-- Notification importance title, low status--> + <string name="low_importance">Low importance</string> + <!-- Notification importance title, normal status--> + <string name="default_importance">Normal importance</string> + <!-- Notification importance title, high status--> + <string name="high_importance">High importance</string> + <!-- Notification importance title, max status--> + <string name="max_importance">Urgent importance</string> + + <!-- [CHAR LIMIT=100] Notification Importance slider: blocked importance level description --> + <string name="notification_importance_blocked">Never show these notifications</string> + + <!-- [CHAR LIMIT=100] Notification Importance slider: low importance level description --> + <string name="notification_importance_low">Silently show at the bottom of the notification list</string> + + <!-- [CHAR LIMIT=100] Notification Importance slider: normal importance level description --> + <string name="notification_importance_default">Silently show these notifications</string> + + <!-- [CHAR LIMIT=100] Notification Importance slider: high importance level description --> + <string name="notification_importance_high">Show at the top of the notifications list and make sound</string> + + <!-- [CHAR LIMIT=100] Notification Importance slider: max importance level description --> + <string name="notification_importance_max">Peek onto the screen and make sound</string> + </resources> diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index b3ce4a6e0d23..ffcc805c4e74 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -246,10 +246,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD boolean dismissHistory() { // Try and hide the history view first if (mHistoryView != null && mHistoryView.isVisible()) { - ReferenceCountedTrigger t = new ReferenceCountedTrigger(this); - t.increment(); - EventBus.getDefault().send(new HideHistoryEvent(true /* animate */, t)); - t.decrement(); + EventBus.getDefault().send(new HideHistoryEvent(true /* animate */)); return true; } return false; @@ -301,8 +298,8 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD */ void dismissRecentsToHome(boolean animated) { if (animated) { - ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this, - null, mFinishLaunchHomeRunnable, null); + ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(null, + mFinishLaunchHomeRunnable, null); mRecentsView.startExitToHomeAnimation( new ViewAnimation.TaskViewExitContext(exitTrigger)); } else { @@ -439,10 +436,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD // Reset some states mIgnoreAltTabRelease = false; if (mHistoryView != null) { - ReferenceCountedTrigger t = new ReferenceCountedTrigger(this); - t.increment(); - EventBus.getDefault().send(new HideHistoryEvent(false /* animate */, t)); - t.decrement(); + EventBus.getDefault().send(new HideHistoryEvent(false /* animate */)); } // Notify that recents is now hidden @@ -511,11 +505,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (savedInstanceState.getBoolean(KEY_SAVED_STATE_HISTORY_VISIBLE, false)) { - ReferenceCountedTrigger postHideStackAnimationTrigger = - new ReferenceCountedTrigger(this); - postHideStackAnimationTrigger.increment(); - EventBus.getDefault().send(new ShowHistoryEvent(postHideStackAnimationTrigger)); - postHideStackAnimationTrigger.decrement(); + EventBus.getDefault().send(new ShowHistoryEvent()); } } @@ -644,16 +634,14 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD } else if (event.triggeredFromHomeKey) { // Otherwise, dismiss Recents to Home if (mHistoryView != null && mHistoryView.isVisible()) { - ReferenceCountedTrigger t = new ReferenceCountedTrigger(this); - t.increment(); - t.addLastDecrementRunnable(new Runnable() { + HideHistoryEvent hideEvent = new HideHistoryEvent(true /* animate */); + hideEvent.addPostAnimationCallback(new Runnable() { @Override public void run() { dismissRecentsToHome(true /* animated */); } }); - EventBus.getDefault().send(new HideHistoryEvent(true, t)); - t.decrement(); + EventBus.getDefault().send(hideEvent); } else { dismissRecentsToHome(true /* animated */); @@ -665,7 +653,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) { // Try and start the enter animation (or restart it on configuration changed) - ReferenceCountedTrigger t = new ReferenceCountedTrigger(this); + ReferenceCountedTrigger t = new ReferenceCountedTrigger(); ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t); ctx.postAnimationTrigger.increment(); if (mSearchWidgetInfo != null) { @@ -784,11 +772,11 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD // provided. mHistoryView.setSystemInsets(mRecentsView.getSystemInsets()); } - mHistoryView.show(mRecentsView.getTaskStack(), event.postHideStackAnimationTrigger); + mHistoryView.show(mRecentsView.getTaskStack(), event.getAnimationTrigger()); } public final void onBusEvent(HideHistoryEvent event) { - mHistoryView.hide(event.animate, event.postHideHistoryAnimationTrigger); + mHistoryView.hide(event.animate, event.getAnimationTrigger()); } private void refreshSearchWidgetView() { diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 8a0a043ef4ff..949fb86d281b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -207,7 +207,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); loader.preloadTasks(plan, true /* isTopTaskHome */); RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); - launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize(); + launchOpts.numVisibleTasks = loader.getIconCacheSize(); launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize(); launchOpts.onlyLoadForCache = true; loader.loadTasks(mContext, plan, launchOpts); @@ -452,7 +452,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements } // Launch the task - ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.activityLabel, launchOpts); + ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.title, launchOpts); } /** @@ -524,7 +524,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1); // Launch the task - ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.activityLabel, launchOpts); + ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.title, launchOpts); } public void showNextAffiliatedTask() { diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java index d72218fd3366..5c49ac380b9b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java @@ -28,6 +28,8 @@ import android.os.UserHandle; import android.util.Log; import android.util.MutableBoolean; +import com.android.systemui.recents.misc.ReferenceCountedTrigger; + import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -220,6 +222,20 @@ public class EventBus extends BroadcastReceiver { // Only accessible from derived events protected Event() {} + /** + * Called by the EventBus prior to dispatching this event to any subscriber of this event. + */ + void onPreDispatch() { + // Do nothing + } + + /** + * Called by the EventBus after dispatching this event to every subscriber of this event. + */ + void onPostDispatch() { + // Do nothing + } + @Override protected Object clone() throws CloneNotSupportedException { Event evt = (Event) super.clone(); @@ -230,6 +246,51 @@ public class EventBus extends BroadcastReceiver { } /** + * An event that represents an animated state change, which allows subscribers to coordinate + * callbacks which happen after the animation has taken place. + * + * Internally, it is guaranteed that increment() and decrement() will be called before and the + * after the event is dispatched. + */ + public static class AnimatedEvent extends Event { + + private final ReferenceCountedTrigger mTrigger = new ReferenceCountedTrigger(); + + // Only accessible from derived events + protected AnimatedEvent() {} + + /** + * Returns the reference counted trigger that coordinates the animations for this event. + */ + public ReferenceCountedTrigger getAnimationTrigger() { + return mTrigger; + } + + /** + * Adds a callback that is guaranteed to be called after the state has changed regardless of + * whether an actual animation took place. + */ + public void addPostAnimationCallback(Runnable r) { + mTrigger.addLastDecrementRunnable(r); + } + + @Override + void onPreDispatch() { + mTrigger.increment(); + } + + @Override + void onPostDispatch() { + mTrigger.decrement(); + } + + @Override + protected Object clone() throws CloneNotSupportedException { + throw new CloneNotSupportedException(); + } + } + + /** * An inter-process event super class that allows us to track user state across subscriber * invocations. */ @@ -706,6 +767,11 @@ public class EventBus extends BroadcastReceiver { if (eventHandlers == null) { return; } + + // Prepare this event + boolean hasPostedEvent = false; + event.onPreDispatch(); + // We need to clone the list in case a subscriber unregisters itself during traversal eventHandlers = (ArrayList<EventHandler>) eventHandlers.clone(); for (final EventHandler eventHandler : eventHandlers) { @@ -717,11 +783,24 @@ public class EventBus extends BroadcastReceiver { processEvent(eventHandler, event); } }); + hasPostedEvent = true; } else { processEvent(eventHandler, event); } } } + + // Clean up after this event, deferring until all subscribers have been called + if (hasPostedEvent) { + mHandler.post(new Runnable() { + @Override + public void run() { + event.onPostDispatch(); + } + }); + } else { + event.onPostDispatch(); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryEvent.java index 34128521f34d..e85dea31c837 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideHistoryEvent.java @@ -22,17 +22,11 @@ import com.android.systemui.recents.misc.ReferenceCountedTrigger; /** * This is sent when the history view will be closed. */ -public class HideHistoryEvent extends EventBus.Event { +public class HideHistoryEvent extends EventBus.AnimatedEvent { public final boolean animate; - public final ReferenceCountedTrigger postHideHistoryAnimationTrigger; - /** - * @param postHideHistoryAnimationTrigger the trigger that gets called when all the history animations are finished - * when transitioning from the history view - */ - public HideHistoryEvent(boolean animate, ReferenceCountedTrigger postHideHistoryAnimationTrigger) { + public HideHistoryEvent(boolean animate) { this.animate = animate; - this.postHideHistoryAnimationTrigger = postHideHistoryAnimationTrigger; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryEvent.java index c91752ea6a0f..94e5a9725c49 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowHistoryEvent.java @@ -22,15 +22,8 @@ import com.android.systemui.recents.misc.ReferenceCountedTrigger; /** * This is sent when the history view button is clicked. */ -public class ShowHistoryEvent extends EventBus.Event { +public class ShowHistoryEvent extends EventBus.AnimatedEvent { - public final ReferenceCountedTrigger postHideStackAnimationTrigger; + // Simple event - /** - * @param postHideStackAnimationTrigger the trigger that gets called when all the task animations are finished when - * transitioning to the history view - */ - public ShowHistoryEvent(ReferenceCountedTrigger postHideStackAnimationTrigger) { - this.postHideStackAnimationTrigger = postHideStackAnimationTrigger; - } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java index 3deeb47f0eb6..8aa463179082 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java @@ -25,18 +25,15 @@ import com.android.systemui.recents.views.TaskView; /** * This event is sent whenever a drag ends. */ -public class DragEndEvent extends EventBus.Event { +public class DragEndEvent extends EventBus.AnimatedEvent { public final Task task; public final TaskView taskView; public final DropTarget dropTarget; - public final ReferenceCountedTrigger postAnimationTrigger; - public DragEndEvent(Task task, TaskView taskView, DropTarget dropTarget, - ReferenceCountedTrigger postAnimationTrigger) { + public DragEndEvent(Task task, TaskView taskView, DropTarget dropTarget) { this.task = task; this.taskView = taskView; this.dropTarget = dropTarget; - this.postAnimationTrigger = postAnimationTrigger; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java index 76439c0f6a67..72ec7b76f1c9 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java @@ -31,7 +31,6 @@ import com.android.systemui.recents.Recents; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.HideHistoryButtonEvent; import com.android.systemui.recents.events.activity.HideHistoryEvent; -import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; @@ -72,7 +71,7 @@ public class RecentsHistoryAdapter extends RecyclerView.Adapter<RecentsHistoryAd public void onTaskDataLoaded(Task task) { // This callback is only made for TaskRow view holders ImageView iv = (ImageView) content.findViewById(R.id.icon); - iv.setImageDrawable(task.applicationIcon); + iv.setImageDrawable(task.icon); } @Override @@ -128,7 +127,7 @@ public class RecentsHistoryAdapter extends RecyclerView.Adapter<RecentsHistoryAd @Override public void onClick(View v) { SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.startActivityFromRecents(v.getContext(), task.key.id, task.activityLabel, + ssp.startActivityFromRecents(v.getContext(), task.key.id, task.title, ActivityOptions.makeBasic()); } @@ -240,7 +239,7 @@ public class RecentsHistoryAdapter extends RecyclerView.Adapter<RecentsHistoryAd TaskRow taskRow = (TaskRow) row; taskRow.task.addCallback(holder); TextView tv = (TextView) holder.content.findViewById(R.id.description); - tv.setText(taskRow.task.activityLabel); + tv.setText(taskRow.task.title); holder.content.setOnClickListener(taskRow); loader.loadTaskData(taskRow.task); break; @@ -313,10 +312,7 @@ public class RecentsHistoryAdapter extends RecyclerView.Adapter<RecentsHistoryAd * Dismisses history back to the stack view. */ private void dismissHistory() { - ReferenceCountedTrigger t = new ReferenceCountedTrigger(mContext); - t.increment(); - EventBus.getDefault().send(new HideHistoryEvent(true /* animate */, t)); - t.decrement(); + EventBus.getDefault().send(new HideHistoryEvent(true /* animate */)); EventBus.getDefault().send(new HideHistoryButtonEvent()); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java index b06539a3c7e8..367f2e268af5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ReferenceCountedTrigger.java @@ -29,9 +29,6 @@ import java.util.ArrayList; */ public class ReferenceCountedTrigger { - private static final String TAG = "ReferenceCountedTrigger"; - - Context mContext; int mCount; ArrayList<Runnable> mFirstIncRunnables = new ArrayList<Runnable>(); ArrayList<Runnable> mLastDecRunnables = new ArrayList<Runnable>(); @@ -51,13 +48,12 @@ public class ReferenceCountedTrigger { } }; - public ReferenceCountedTrigger(Context context) { - this(context, null, null, null); + public ReferenceCountedTrigger() { + this(null, null, null); } - public ReferenceCountedTrigger(Context context, Runnable firstIncRunnable, - Runnable lastDecRunnable, Runnable errorRunanable) { - mContext = context; + public ReferenceCountedTrigger(Runnable firstIncRunnable, Runnable lastDecRunnable, + Runnable errorRunanable) { if (firstIncRunnable != null) mFirstIncRunnables.add(firstIncRunnable); if (lastDecRunnable != null) mLastDecRunnables.add(lastDecRunnable); mErrorRunnable = errorRunanable; @@ -81,22 +77,14 @@ public class ReferenceCountedTrigger { /** Adds a runnable to the last-decrement runnables list. */ public void addLastDecrementRunnable(Runnable r) { - // To ensure that the last decrement always calls, we increment and decrement after setting - // the last decrement runnable - boolean ensureLastDecrement = (mCount == 0); - if (ensureLastDecrement) increment(); mLastDecRunnables.add(r); - if (ensureLastDecrement) decrement(); } /** Decrements the ref count */ public void decrement() { mCount--; - if (mCount == 0 && !mLastDecRunnables.isEmpty()) { - int numRunnables = mLastDecRunnables.size(); - for (int i = 0; i < numRunnables; i++) { - mLastDecRunnables.get(i).run(); - } + if (mCount == 0) { + flushLastDecrementRunnables(); } else if (mCount < 0) { if (mErrorRunnable != null) { mErrorRunnable.run(); @@ -106,6 +94,19 @@ public class ReferenceCountedTrigger { } } + /** + * Runs and clears all the last-decrement runnables now. + */ + public void flushLastDecrementRunnables() { + if (!mLastDecRunnables.isEmpty()) { + int numRunnables = mLastDecRunnables.size(); + for (int i = 0; i < numRunnables; i++) { + mLastDecRunnables.get(i).run(); + } + } + mLastDecRunnables.clear(); + } + /** Convenience method to decrement this trigger as a runnable. */ public Runnable decrementAsRunnable() { return mDecrementRunnable; diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index 5888b30c514f..35e53f69a151 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -43,6 +43,7 @@ import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -193,6 +194,8 @@ public class SystemServicesProxy { int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks); List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery, ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS | + ActivityManager.RECENT_INGORE_DOCKED_STACK_TASKS | + ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS | ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_PROFILES | ActivityManager.RECENT_WITH_EXCLUDED, userId); @@ -572,7 +575,7 @@ public class SystemServicesProxy { * Returns the activity icon for the ActivityInfo for a user, badging if * necessary. */ - public Drawable getActivityIcon(ActivityInfo info, int userId) { + public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) { if (mPm == null) return null; // If we are mocking, then return a mock label @@ -585,9 +588,31 @@ public class SystemServicesProxy { } /** + * Returns the task description icon, loading and badging it if it necessary. + */ + public Drawable getBadgedTaskDescriptionIcon(ActivityManager.TaskDescription taskDescription, + int userId, Resources res) { + + // If we are mocking, then return a mock label + if (RecentsDebugFlags.Static.EnableSystemServicesProxy) { + return new ColorDrawable(0xFF666666); + } + + Bitmap tdIcon = taskDescription.getInMemoryIcon(); + if (tdIcon == null) { + tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon( + taskDescription.getIconFilename(), userId); + } + if (tdIcon != null) { + return getBadgedIcon(new BitmapDrawable(res, tdIcon), userId); + } + return null; + } + + /** * Returns the given icon for a user, badging if necessary. */ - public Drawable getBadgedIcon(Drawable icon, int userId) { + private Drawable getBadgedIcon(Drawable icon, int userId) { if (userId != UserHandle.myUserId()) { icon = mPm.getUserBadgedIcon(icon, new UserHandle(userId)); } @@ -597,7 +622,7 @@ public class SystemServicesProxy { /** * Returns the given label for a user, badging if necessary. */ - public String getBadgedLabel(String label, int userId) { + private String getBadgedLabel(String label, int userId) { if (userId != UserHandle.myUserId()) { label = mPm.getUserBadgedLabel(label, new UserHandle(userId)).toString(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java index 1d180870bb6b..7a92b2ae0a21 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -29,7 +29,6 @@ import android.util.Log; import com.android.systemui.Prefs; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsConfiguration; -import com.android.systemui.recents.RecentsDebugFlags; import com.android.systemui.recents.misc.SystemServicesProxy; import java.util.ArrayList; @@ -47,9 +46,6 @@ import java.util.List; */ public class RecentsTaskLoadPlan { - private static String TAG = "RecentsTaskLoadPlan"; - private static boolean DEBUG = false; - private static int MIN_NUM_TASKS = 5; private static int SESSION_BEGIN_TIME = 1000 /* ms/s */ * 60 /* s/min */ * 60 /* min/hr */ * 6 /* hrs */; @@ -107,13 +103,6 @@ public class RecentsTaskLoadPlan { // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it Collections.reverse(mRawTasks); - - if (DEBUG) { - Log.d(TAG, "preloadRawTasks, tasks: " + mRawTasks.size()); - for (ActivityManager.RecentTaskInfo info : mRawTasks) { - Log.d(TAG, " " + info.baseIntent + ", " + info.lastActiveTime); - } - } } /** @@ -126,8 +115,6 @@ public class RecentsTaskLoadPlan { * - least-recent to most-recent freeform tasks */ public synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) { - if (DEBUG) Log.d(TAG, "preloadPlan"); - RecentsConfiguration config = Recents.getConfiguration(); SystemServicesProxy ssp = Recents.getSystemServices(); Resources res = mContext.getResources(); @@ -149,38 +136,26 @@ public class RecentsTaskLoadPlan { // This task is only shown in the stack if it statisfies the historical time or min // number of tasks constraints. Freeform tasks are also always shown. - boolean isStackTask = true; boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId); - isStackTask = isFreeformTask || (!isHistoricalTask(t) || - (t.lastActiveTime >= lastStackActiveTime && - i >= (taskCount - MIN_NUM_TASKS))); + boolean isStackTask = isFreeformTask || (!isHistoricalTask(t) || + (t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS))); if (isStackTask && newLastStackActiveTime < 0) { newLastStackActiveTime = t.lastActiveTime; } - // Load the label, icon, and color - String activityLabel = loader.getAndUpdateActivityLabel(taskKey, t.taskDescription, - ssp); - String contentDescription = loader.getAndUpdateContentDescription(taskKey, - activityLabel, ssp, res); - Drawable activityIcon = isStackTask - ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, ssp, res, false) + // Load the title, icon, and color + String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription); + String contentDescription = loader.getAndUpdateContentDescription(taskKey, title, res); + Drawable icon = isStackTask + ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false) : null; + Bitmap thumbnail = loader.getAndUpdateThumbnail(taskKey, false); int activityColor = loader.getActivityPrimaryColor(t.taskDescription); - Bitmap icon = t.taskDescription != null - ? t.taskDescription.getInMemoryIcon() : null; - String iconFilename = t.taskDescription != null - ? t.taskDescription.getIconFilename() : null; - // Add the task to the stack - Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, - contentDescription, activityIcon, activityColor, (i == (taskCount - 1)), - config.lockToAppEnabled, !isStackTask, icon, iconFilename, t.bounds); - task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false); - if (DEBUG) { - Log.d(TAG, activityLabel + " bounds: " + t.bounds); - } + Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon, + thumbnail, title, contentDescription, activityColor, !isStackTask, + (i == (taskCount - 1)), config.lockToAppEnabled, t.bounds, t.taskDescription); allTasks.add(task); } @@ -200,19 +175,13 @@ public class RecentsTaskLoadPlan { */ public synchronized void executePlan(Options opts, RecentsTaskLoader loader, TaskResourceLoadQueue loadQueue) { - if (DEBUG) Log.d(TAG, "executePlan, # tasks: " + opts.numVisibleTasks + - ", # thumbnails: " + opts.numVisibleTaskThumbnails + - ", running task id: " + opts.runningTaskId); - RecentsConfiguration config = Recents.getConfiguration(); - SystemServicesProxy ssp = Recents.getSystemServices(); Resources res = mContext.getResources(); // Iterate through each of the tasks and load them according to the load conditions. ArrayList<Task> tasks = mStack.getStackTasks(); int taskCount = tasks.size(); for (int i = 0; i < taskCount; i++) { - ActivityManager.RecentTaskInfo t = mRawTasks.get(i); Task task = tasks.get(i); Task.TaskKey taskKey = task.key; @@ -226,17 +195,15 @@ public class RecentsTaskLoadPlan { } if (opts.loadIcons && (isRunningTask || isVisibleTask)) { - if (task.activityIcon == null) { - if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey); - task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, - ssp, res, true); + if (task.icon == null) { + task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, + res, true); } } if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) { if (task.thumbnail == null || isRunningTask) { - if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey); if (config.svelteLevel <= RecentsConfiguration.SVELTE_LIMIT_CACHE) { - task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, true); + task.thumbnail = loader.getAndUpdateThumbnail(taskKey, true); } else if (config.svelteLevel == RecentsConfiguration.SVELTE_DISABLE_CACHE) { loadQueue.addTask(task); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java index c72d166b4cd5..28338d8330fa 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java @@ -93,23 +93,23 @@ class BackgroundTaskLoader implements Runnable { Handler mMainThreadHandler; TaskResourceLoadQueue mLoadQueue; - TaskKeyLruCache<Drawable> mApplicationIconCache; + TaskKeyLruCache<Drawable> mIconCache; TaskKeyLruCache<Bitmap> mThumbnailCache; Bitmap mDefaultThumbnail; - BitmapDrawable mDefaultApplicationIcon; + BitmapDrawable mDefaultIcon; boolean mCancelled; boolean mWaitingOnLoadQueue; /** Constructor, creates a new loading thread that loads task resources in the background */ public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue, - TaskKeyLruCache<Drawable> applicationIconCache, TaskKeyLruCache<Bitmap> thumbnailCache, - Bitmap defaultThumbnail, BitmapDrawable defaultApplicationIcon) { + TaskKeyLruCache<Drawable> iconCache, TaskKeyLruCache<Bitmap> thumbnailCache, + Bitmap defaultThumbnail, BitmapDrawable defaultIcon) { mLoadQueue = loadQueue; - mApplicationIconCache = applicationIconCache; + mIconCache = iconCache; mThumbnailCache = thumbnailCache; mDefaultThumbnail = defaultThumbnail; - mDefaultApplicationIcon = defaultApplicationIcon; + mDefaultIcon = defaultIcon; mMainThreadHandler = new Handler(); mLoadThread = new HandlerThread("Recents-TaskResourceLoader", android.os.Process.THREAD_PRIORITY_BACKGROUND); @@ -163,30 +163,30 @@ class BackgroundTaskLoader implements Runnable { // Load the next item from the queue final Task t = mLoadQueue.nextTask(); if (t != null) { - Drawable cachedIcon = mApplicationIconCache.get(t.key); + Drawable cachedIcon = mIconCache.get(t.key); Bitmap cachedThumbnail = mThumbnailCache.get(t.key); - // Load the application icon if it is stale or we haven't cached one yet + // Load the icon if it is stale or we haven't cached one yet if (cachedIcon == null) { - cachedIcon = getTaskDescriptionIcon(t.key, t.icon, t.iconFilename, ssp, - mContext.getResources()); + cachedIcon = ssp.getBadgedTaskDescriptionIcon(t.taskDescription, + t.key.userId, mContext.getResources()); if (cachedIcon == null) { ActivityInfo info = ssp.getActivityInfo( t.key.getComponent(), t.key.userId); if (info != null) { if (DEBUG) Log.d(TAG, "Loading icon: " + t.key); - cachedIcon = ssp.getActivityIcon(info, t.key.userId); + cachedIcon = ssp.getBadgedActivityIcon(info, t.key.userId); } } if (cachedIcon == null) { - cachedIcon = mDefaultApplicationIcon; + cachedIcon = mDefaultIcon; } // At this point, even if we can't load the icon, we will set the // default icon. - mApplicationIconCache.put(t.key, cachedIcon); + mIconCache.put(t.key, cachedIcon); } // Load the thumbnail if it is stale or we haven't cached one yet if (cachedThumbnail == null) { @@ -234,25 +234,6 @@ class BackgroundTaskLoader implements Runnable { } } } - - Drawable getTaskDescriptionIcon(Task.TaskKey taskKey, Bitmap iconBitmap, String iconFilename, - SystemServicesProxy ssp, Resources res) { - Bitmap tdIcon = null; - if (iconBitmap != null) { - tdIcon = iconBitmap; - } else { - try { - tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon(iconFilename, - taskKey.userId); - } catch (Exception e) { - // TODO: Investigate for b/26221779 - } - } - if (tdIcon != null) { - return ssp.getBadgedIcon(new BitmapDrawable(res, tdIcon), taskKey.userId); - } - return null; - } } /** @@ -269,7 +250,7 @@ public class RecentsTaskLoader { // active time. Instead, we rely on the RecentsPackageMonitor to keep us informed whenever a // package in the cache has been updated, so that we may remove it. private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache; - private final TaskKeyLruCache<Drawable> mApplicationIconCache; + private final TaskKeyLruCache<Drawable> mIconCache; private final TaskKeyLruCache<Bitmap> mThumbnailCache; private final TaskKeyLruCache<String> mActivityLabelCache; private final TaskKeyLruCache<String> mContentDescriptionCache; @@ -282,7 +263,7 @@ public class RecentsTaskLoader { private int mNumVisibleThumbnailsLoaded; int mDefaultTaskBarBackgroundColor; - BitmapDrawable mDefaultApplicationIcon; + BitmapDrawable mDefaultIcon; Bitmap mDefaultThumbnail; public RecentsTaskLoader(Context context) { @@ -302,22 +283,22 @@ public class RecentsTaskLoader { mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); mDefaultThumbnail.setHasAlpha(false); mDefaultThumbnail.eraseColor(0xFFffffff); - mDefaultApplicationIcon = new BitmapDrawable(context.getResources(), icon); + mDefaultIcon = new BitmapDrawable(context.getResources(), icon); // Initialize the proxy, cache and loaders int numRecentTasks = ActivityManager.getMaxRecentTasksStatic(); mLoadQueue = new TaskResourceLoadQueue(); - mApplicationIconCache = new TaskKeyLruCache<>(iconCacheSize); + mIconCache = new TaskKeyLruCache<>(iconCacheSize); mThumbnailCache = new TaskKeyLruCache<>(thumbnailCacheSize); mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks); mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks); mActivityInfoCache = new LruCache(numRecentTasks); - mLoader = new BackgroundTaskLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache, - mDefaultThumbnail, mDefaultApplicationIcon); + mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mThumbnailCache, + mDefaultThumbnail, mDefaultIcon); } /** Returns the size of the app icon cache. */ - public int getApplicationIconCacheSize() { + public int getIconCacheSize() { return mMaxIconCacheSize; } @@ -355,33 +336,33 @@ public class RecentsTaskLoader { /** Acquires the task resource data directly from the pool. */ public void loadTaskData(Task t) { - Drawable applicationIcon = mApplicationIconCache.getAndInvalidateIfModified(t.key); + Drawable icon = mIconCache.getAndInvalidateIfModified(t.key); Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(t.key); // Grab the thumbnail/icon from the cache, if either don't exist, then trigger a reload and // use the default assets in their place until they load - boolean requiresLoad = (applicationIcon == null) || (thumbnail == null); - applicationIcon = applicationIcon != null ? applicationIcon : mDefaultApplicationIcon; + boolean requiresLoad = (icon == null) || (thumbnail == null); + icon = icon != null ? icon : mDefaultIcon; if (requiresLoad) { mLoadQueue.addTask(t); } - t.notifyTaskDataLoaded(thumbnail == mDefaultThumbnail ? null : thumbnail, applicationIcon); + t.notifyTaskDataLoaded(thumbnail == mDefaultThumbnail ? null : thumbnail, icon); } /** Releases the task resource data back into the pool. */ public void unloadTaskData(Task t) { mLoadQueue.removeTask(t); - t.notifyTaskDataUnloaded(null, mDefaultApplicationIcon); + t.notifyTaskDataUnloaded(null, mDefaultIcon); } /** Completely removes the resource data from the pool. */ public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) { mLoadQueue.removeTask(t); mThumbnailCache.remove(t.key); - mApplicationIconCache.remove(t.key); + mIconCache.remove(t.key); mActivityInfoCache.remove(t.key.getComponent()); if (notifyTaskDataUnloaded) { - t.notifyTaskDataUnloaded(null, mDefaultApplicationIcon); + t.notifyTaskDataUnloaded(null, mDefaultIcon); } } @@ -403,14 +384,14 @@ public class RecentsTaskLoader { } else if (config.svelteLevel >= RecentsConfiguration.SVELTE_DISABLE_CACHE) { mThumbnailCache.evictAll(); } - mApplicationIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded, + mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded, mMaxIconCacheSize / 2)); break; case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: // We are leaving recents, so trim the data a bit mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 2)); - mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2)); + mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2)); mActivityInfoCache.trimToSize(Math.max(1, ActivityManager.getMaxRecentTasksStatic() / 2)); break; @@ -418,7 +399,7 @@ public class RecentsTaskLoader { case ComponentCallbacks2.TRIM_MEMORY_MODERATE: // We are going to be low on memory mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 4)); - mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4)); + mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4)); mActivityInfoCache.trimToSize(Math.max(1, ActivityManager.getMaxRecentTasksStatic() / 4)); break; @@ -426,7 +407,7 @@ public class RecentsTaskLoader { case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: // We are low on memory, so release everything mThumbnailCache.evictAll(); - mApplicationIconCache.evictAll(); + mIconCache.evictAll(); mActivityInfoCache.evictAll(); // The cache is small, only clear the label cache when we are critical mActivityLabelCache.evictAll(); @@ -440,8 +421,9 @@ public class RecentsTaskLoader { /** * Returns the cached task label if the task key is not expired, updating the cache if it is. */ - String getAndUpdateActivityLabel(Task.TaskKey taskKey, ActivityManager.TaskDescription td, - SystemServicesProxy ssp) { + String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) { + SystemServicesProxy ssp = Recents.getSystemServices(); + // Return the task description label if it exists if (td != null && td.getLabel() != null) { return td.getLabel(); @@ -452,7 +434,7 @@ public class RecentsTaskLoader { return label; } // All short paths failed, load the label from the activity info and cache it - ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey, ssp); + ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); if (activityInfo != null) { label = ssp.getActivityLabel(activityInfo); mActivityLabelCache.put(taskKey, label); @@ -468,7 +450,9 @@ public class RecentsTaskLoader { * cache if it is. */ String getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel, - SystemServicesProxy ssp, Resources res) { + Resources res) { + SystemServicesProxy ssp = Recents.getSystemServices(); + // Return the cached content description if it exists String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey); if (label != null) { @@ -493,28 +477,29 @@ public class RecentsTaskLoader { * Returns the cached task icon if the task key is not expired, updating the cache if it is. */ Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td, - SystemServicesProxy ssp, Resources res, boolean loadIfNotCached) { + Resources res, boolean loadIfNotCached) { + SystemServicesProxy ssp = Recents.getSystemServices(); + // Return the cached activity icon if it exists - Drawable icon = mApplicationIconCache.getAndInvalidateIfModified(taskKey); + Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey); if (icon != null) { return icon; } if (loadIfNotCached) { // Return and cache the task description icon if it exists - icon = mLoader.getTaskDescriptionIcon(taskKey, td.getInMemoryIcon(), - td.getIconFilename(), ssp, res); + icon = ssp.getBadgedTaskDescriptionIcon(td, taskKey.userId, res); if (icon != null) { - mApplicationIconCache.put(taskKey, icon); + mIconCache.put(taskKey, icon); return icon; } // Load the icon from the activity info and cache it - ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey, ssp); + ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); if (activityInfo != null) { - icon = ssp.getActivityIcon(activityInfo, taskKey.userId); + icon = ssp.getBadgedActivityIcon(activityInfo, taskKey.userId); if (icon != null) { - mApplicationIconCache.put(taskKey, icon); + mIconCache.put(taskKey, icon); return icon; } } @@ -526,8 +511,9 @@ public class RecentsTaskLoader { /** * Returns the cached thumbnail if the task key is not expired, updating the cache if it is. */ - Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, SystemServicesProxy ssp, - boolean loadIfNotCached) { + Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) { + SystemServicesProxy ssp = Recents.getSystemServices(); + // Return the cached thumbnail if it exists Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey); if (thumbnail != null) { @@ -550,7 +536,8 @@ public class RecentsTaskLoader { } /** - * Returns the task's primary color. + * Returns the task's primary color if possible, defaulting to the default color if there is + * no specified primary color. */ int getActivityPrimaryColor(ActivityManager.TaskDescription td) { if (td != null && td.getPrimaryColor() != 0) { @@ -563,7 +550,8 @@ public class RecentsTaskLoader { * Returns the activity info for the given task key, retrieving one from the system if the * task key is expired. */ - private ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey, SystemServicesProxy ssp) { + private ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) { + SystemServicesProxy ssp = Recents.getSystemServices(); ComponentName cn = taskKey.getComponent(); ActivityInfo activityInfo = mActivityInfoCache.get(cn); if (activityInfo == null) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index 73c0adb6f8d0..34a0e5256652 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -16,6 +16,7 @@ package com.android.systemui.recents.model; +import android.app.ActivityManager; import android.content.ComponentName; import android.content.Intent; import android.graphics.Bitmap; @@ -92,24 +93,46 @@ public class Task { } public TaskKey key; + + /** + * The group will be computed separately from the initialization of the task + */ public TaskGrouping group; - // The taskAffiliationId is the task id of the parent task or itself if it is not affiliated with any task - public int taskAffiliationId; - public int taskAffiliationColor; - public boolean isLaunchTarget; - public Drawable applicationIcon; - public Drawable activityIcon; + /** + * The affiliationTaskId is the task id of the parent task or itself if it is not affiliated + * with any task. + */ + public int affiliationTaskId; + public int affiliationColor; + + /** + * The icon is the task description icon (if provided), which falls back to the activity icon, + * which can then fall back to the application icon. + */ + public Drawable icon; + public Bitmap thumbnail; + public String title; public String contentDescription; - public String activityLabel; public int colorPrimary; public boolean useLightOnPrimaryColor; - public Bitmap thumbnail; + + /** + * The bounds of the task, used only if it is a freeform task. + */ + public Rect bounds; + + /** + * The task description for this task, only used to reload task icons. + */ + public ActivityManager.TaskDescription taskDescription; + + /** + * The state isLaunchTarget will be set for the correct task upon launching Recents. + */ + public boolean isLaunchTarget; + public boolean isHistorical; public boolean lockToThisTask; public boolean lockToTaskEnabled; - public boolean isHistorical; - public Bitmap icon; - public String iconFilename; - public Rect bounds; private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>(); @@ -117,45 +140,46 @@ public class Task { // Do nothing } - public Task(TaskKey key, int taskAffiliation, int taskAffiliationColor, - String activityTitle, String contentDescription, Drawable activityIcon, - int colorPrimary, boolean lockToThisTask, boolean lockToTaskEnabled, - boolean isHistorical, Bitmap icon, String iconFilename, Rect bounds) { - boolean isInAffiliationGroup = (taskAffiliation != key.id); - boolean hasAffiliationGroupColor = isInAffiliationGroup && (taskAffiliationColor != 0); + public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon, + Bitmap thumbnail, String title, String contentDescription, int colorPrimary, + boolean isHistorical, boolean lockToThisTask, boolean lockToTaskEnabled, + Rect bounds, ActivityManager.TaskDescription taskDescription) { + boolean isInAffiliationGroup = (affiliationTaskId != key.id); + boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0); this.key = key; - this.taskAffiliationId = taskAffiliation; - this.taskAffiliationColor = taskAffiliationColor; - this.activityLabel = activityTitle; + this.affiliationTaskId = affiliationTaskId; + this.affiliationColor = affiliationColor; + this.icon = icon; + this.thumbnail = thumbnail; + this.title = title; this.contentDescription = contentDescription; - this.activityIcon = activityIcon; - this.colorPrimary = hasAffiliationGroupColor ? taskAffiliationColor : colorPrimary; + this.colorPrimary = hasAffiliationGroupColor ? affiliationColor : colorPrimary; this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary, Color.WHITE) > 3f; + this.bounds = bounds; + this.taskDescription = taskDescription; + this.isHistorical = isHistorical; this.lockToThisTask = lockToTaskEnabled && lockToThisTask; this.lockToTaskEnabled = lockToTaskEnabled; - this.isHistorical = isHistorical; - this.icon = icon; - this.iconFilename = iconFilename; - this.bounds = bounds; } /** Copies the other task. */ public void copyFrom(Task o) { this.key = o.key; - this.taskAffiliationId = o.taskAffiliationId; - this.taskAffiliationColor = o.taskAffiliationColor; - this.activityLabel = o.activityLabel; + this.group = o.group; + this.affiliationTaskId = o.affiliationTaskId; + this.affiliationColor = o.affiliationColor; + this.icon = o.icon; + this.thumbnail = o.thumbnail; + this.title = o.title; this.contentDescription = o.contentDescription; - this.activityIcon = o.activityIcon; this.colorPrimary = o.colorPrimary; this.useLightOnPrimaryColor = o.useLightOnPrimaryColor; + this.bounds = o.bounds; + this.isLaunchTarget = o.isLaunchTarget; + this.isHistorical = o.isHistorical; this.lockToThisTask = o.lockToThisTask; this.lockToTaskEnabled = o.lockToTaskEnabled; - this.isHistorical = o.isHistorical; - this.icon = o.icon; - this.iconFilename = o.iconFilename; - this.bounds = o.bounds; } /** @@ -200,7 +224,7 @@ public class Task { /** Notifies the callback listeners that this task has been loaded */ public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) { - this.applicationIcon = applicationIcon; + this.icon = applicationIcon; this.thumbnail = thumbnail; int callbackCount = mCallbacks.size(); for (int i = 0; i < callbackCount; i++) { @@ -210,7 +234,7 @@ public class Task { /** Notifies the callback listeners that this task has been unloaded */ public void notifyTaskDataUnloaded(Bitmap defaultThumbnail, Drawable defaultApplicationIcon) { - applicationIcon = defaultApplicationIcon; + icon = defaultApplicationIcon; thumbnail = defaultThumbnail; int callbackCount = mCallbacks.size(); for (int i = 0; i < callbackCount; i++) { @@ -222,7 +246,7 @@ public class Task { * Returns whether this task is affiliated with another task. */ public boolean isAffiliatedTask() { - return key.id != taskAffiliationId; + return key.id != affiliationTaskId; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index 3484c389ff7a..6f003ab63282 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -232,16 +232,16 @@ public class TaskStack { public static final DockState NONE = new DockState(-1, 96, null, null); public static final DockState LEFT = new DockState( DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, - new RectF(0, 0, 0.25f, 1), new RectF(0, 0, 0.25f, 1)); + new RectF(0, 0, 0.15f, 1), new RectF(0, 0, 0.15f, 1)); public static final DockState TOP = new DockState( DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, - new RectF(0, 0, 1, 0.25f), new RectF(0, 0, 1, 0.25f)); + new RectF(0, 0, 1, 0.15f), new RectF(0, 0, 1, 0.15f)); public static final DockState RIGHT = new DockState( DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, - new RectF(0.75f, 0, 1, 1), new RectF(0.75f, 0, 1, 1)); + new RectF(0.85f, 0, 1, 1), new RectF(0.85f, 0, 1, 1)); public static final DockState BOTTOM = new DockState( DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, - new RectF(0, 0.75f, 1, 1), new RectF(0, 0.75f, 1, 1)); + new RectF(0, 0.85f, 1, 1), new RectF(0, 0.85f, 1, 1)); @Override public boolean acceptsDrop(int x, int y, int width, int height) { @@ -352,28 +352,28 @@ public class TaskStack { @Override public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) { if (t.isAffiliatedTask()) { - // If this task is affiliated with another parent in the stack, then the historical state of this - // task depends on the state of the parent task - Task parentTask = taskIdMap.get(t.taskAffiliationId); + // If this task is affiliated with another parent in the stack, then the + // historical state of this task depends on the state of the parent task + Task parentTask = taskIdMap.get(t.affiliationTaskId); if (parentTask != null) { t = parentTask; } } - return !t.isHistorical && !SystemServicesProxy.isDockedStack(t.key.stackId); + return !t.isHistorical; } }); mHistoryTaskList.setFilter(new TaskFilter() { @Override public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) { if (t.isAffiliatedTask()) { - // If this task is affiliated with another parent in the stack, then the historical state of this - // task depends on the state of the parent task - Task parentTask = taskIdMap.get(t.taskAffiliationId); + // If this task is affiliated with another parent in the stack, then the + // historical state of this task depends on the state of the parent task + Task parentTask = taskIdMap.get(t.affiliationTaskId); if (parentTask != null) { t = parentTask; } } - return t.isHistorical && !SystemServicesProxy.isDockedStack(t.key.stackId); + return t.isHistorical; } }); } @@ -716,7 +716,7 @@ public class TaskStack { for (int i = 0; i < taskCount; i++) { Task t = tasks.get(i); TaskGrouping group; - int affiliation = t.taskAffiliationId > 0 ? t.taskAffiliationId : + int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId : IndividualTaskIdOffset + t.key.id; if (mAffinitiesGroups.containsKey(affiliation)) { group = getGroupWithAffiliation(affiliation); @@ -737,7 +737,7 @@ public class TaskStack { // Ignore the groups that only have one task if (taskCount <= 1) continue; // Calculate the group color distribution - int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).taskAffiliationColor; + int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).affiliationColor; float alphaStep = (1f - minAlpha) / taskCount; float alpha = 1f; for (int j = 0; j < taskCount; j++) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java index 135f0f92023b..0af7c1e9379e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java @@ -157,7 +157,7 @@ public class RecentsTransitionHelper { ActivityOptions opts, IAppTransitionAnimationSpecsFuture transitionFuture, final ActivityOptions.OnAnimationStartedListener animStartedListener) { SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.startActivityFromRecents(mContext, task.key.id, task.activityLabel, opts)) { + if (ssp.startActivityFromRecents(mContext, task.key.id, task.title, opts)) { // Keep track of the index of the task launch int taskIndexFromFront = 0; int taskIndex = stack.indexOfStackTask(task); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 53c02cbbfa51..9b1315a2220f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -132,10 +132,7 @@ public class RecentsView extends FrameLayout { mHistoryButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - ReferenceCountedTrigger postHideStackAnimationTrigger = new ReferenceCountedTrigger(v.getContext()); - postHideStackAnimationTrigger.increment(); - EventBus.getDefault().send(new ShowHistoryEvent(postHideStackAnimationTrigger)); - postHideStackAnimationTrigger.decrement(); + EventBus.getDefault().send(new ShowHistoryEvent()); } }); addView(mHistoryButton); @@ -576,8 +573,8 @@ public class RecentsView extends FrameLayout { public final void onBusEvent(ShowHistoryEvent event) { // Hide the history button when the history view is shown hideHistoryButton(getResources().getInteger(R.integer.recents_history_transition_duration), - event.postHideStackAnimationTrigger); - event.postHideStackAnimationTrigger.addLastDecrementRunnable(new Runnable() { + event.getAnimationTrigger()); + event.addPostAnimationCallback(new Runnable() { @Override public void run() { setAlpha(0f); @@ -589,7 +586,7 @@ public class RecentsView extends FrameLayout { // Show the history button when the history view is hidden setAlpha(1f); showHistoryButton(getResources().getInteger(R.integer.recents_history_transition_duration), - event.postHideHistoryAnimationTrigger); + event.getAnimationTrigger()); } public final void onBusEvent(ShowHistoryButtonEvent event) { @@ -609,10 +606,9 @@ public class RecentsView extends FrameLayout { * Shows the history button. */ private void showHistoryButton(final int duration) { - ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(getContext()); - postAnimationTrigger.increment(); + ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(); showHistoryButton(duration, postAnimationTrigger); - postAnimationTrigger.decrement(); + postAnimationTrigger.flushLastDecrementRunnables(); } private void showHistoryButton(final int duration, @@ -638,10 +634,9 @@ public class RecentsView extends FrameLayout { * Hides the history button. */ private void hideHistoryButton(int duration) { - ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(getContext()); - postAnimationTrigger.increment(); + ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(); hideHistoryButton(duration, postAnimationTrigger); - postAnimationTrigger.decrement(); + postAnimationTrigger.flushLastDecrementRunnables(); } private void hideHistoryButton(int duration, diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java index 37a01940c996..318801dbcb9f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java @@ -188,12 +188,8 @@ public class RecentsViewTouchHandler { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { if (mDragging) { - ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger( - mRv.getContext()); - postAnimationTrigger.increment(); EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView, - mLastDropTarget, postAnimationTrigger)); - postAnimationTrigger.decrement(); + mLastDropTarget)); break; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index 9625e5da2d39..10df15609e0f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -118,7 +118,7 @@ public class TaskStackLayoutAlgorithm { */ public static class StackState { - public static final StackState FREEFORM_ONLY = new StackState(1f, 0); + public static final StackState FREEFORM_ONLY = new StackState(1f, 255); public static final StackState STACK_ONLY = new StackState(0f, 0); public static final StackState SPLIT = new StackState(0.5f, 255); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index b9ca9fd5a5b5..830d60799717 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -44,7 +44,6 @@ import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivity; import com.android.systemui.recents.RecentsActivityLaunchState; import com.android.systemui.recents.RecentsConfiguration; -import com.android.systemui.recents.RecentsDebugFlags; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent; @@ -216,7 +215,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal R.drawable.recents_freeform_workspace_bg); mFreeformWorkspaceBackground.setCallback(this); if (ssp.hasFreeformWorkspaceSupport()) { - setBackgroundColor(getContext().getColor(R.color.recents_freeform_workspace_bg_color)); + mFreeformWorkspaceBackground.setColor( + getContext().getColor(R.color.recents_freeform_workspace_bg_color)); } } @@ -797,7 +797,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal TaskView frontMostTask = taskViews.get(taskViewCount - 1); event.setFromIndex(mStack.indexOfStackTask(backMostTask.getTask())); event.setToIndex(mStack.indexOfStackTask(frontMostTask.getTask())); - event.setContentDescription(frontMostTask.getTask().activityLabel); + event.setContentDescription(frontMostTask.getTask().title); } event.setItemCount(mStack.getStackTaskCount()); event.setScrollY(mStackScroller.mScroller.getCurrY()); @@ -1021,8 +1021,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (launchTargetTask != null) { occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); - hideTask = SystemServicesProxy.isFreeformStack(launchTargetTask.key.stackId) && - SystemServicesProxy.isFreeformStack(task.key.stackId); + hideTask = launchTargetTask.isFreeformTask() && task.isFreeformTask(); } tv.prepareEnterRecentsAnimation(task.isLaunchTarget, hideTask, occludesLaunchTarget, offscreenY); @@ -1494,7 +1493,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) || (isFreeformTask && event.dropTarget == mStackDropTarget); - event.postAnimationTrigger.increment(); if (hasChangedStacks) { // Move the task to the right position in the stack (ie. the front of the stack if // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks @@ -1507,7 +1505,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal updateLayout(true); // Move the task to the new stack in the system after the animation completes - event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { + event.addPostAnimationCallback(new Runnable() { @Override public void run() { SystemServicesProxy ssp = Recents.getSystemServices(); @@ -1515,8 +1513,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } }); } - event.taskView.animate() - .withEndAction(event.postAnimationTrigger.decrementAsRunnable()); + event.getAnimationTrigger().increment(); + event.taskView.animate().withEndAction(event.getAnimationTrigger().decrementAsRunnable()); // We translated the view but we need to animate it back from the current layout-space rect // to its final layout-space rect @@ -1556,7 +1554,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal for (int i = 0; i < taskViewCount; i++) { TaskView tv = taskViews.get(i); Task task = tv.getTask(); - if (SystemServicesProxy.isFreeformStack(task.key.stackId)) { + if (task.isFreeformTask()) { tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE); } } @@ -1577,9 +1575,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal .setUpdateListener(null) .setListener(null) .withLayer() - .withEndAction(event.postHideStackAnimationTrigger.decrementAsRunnable()) + .withEndAction(event.getAnimationTrigger().decrementAsRunnable()) .start(); - event.postHideStackAnimationTrigger.increment(); + event.getAnimationTrigger().increment(); } } @@ -1592,7 +1590,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int taskViewCount = taskViews.size(); for (int i = taskViewCount - 1; i >= 0; i--) { final TaskView tv = taskViews.get(i); - event.postHideHistoryAnimationTrigger.addLastDecrementRunnable(new Runnable() { + event.addPostAnimationCallback(new Runnable() { @Override public void run() { tv.animate() @@ -1616,7 +1614,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Announce for accessibility tv.announceForAccessibility(getContext().getString( - R.string.accessibility_recents_item_dismissed, tv.getTask().activityLabel)); + R.string.accessibility_recents_item_dismissed, tv.getTask().title)); // Remove the task from the stack mStack.removeTask(task); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index df7b9a63089b..a3e8b2d72d56 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -651,7 +651,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, */ public void setFocusedState(boolean isFocused, boolean animated, boolean requestViewFocus) { if (DEBUG) { - Log.d(TAG, "setFocusedState: " + mTask.activityLabel + " focused: " + isFocused + + Log.d(TAG, "setFocusedState: " + mTask.title + " focused: " + isFocused + " animated: " + animated + " requestViewFocus: " + requestViewFocus + " isFocused(): " + isFocused() + " isAccessibilityFocused(): " + isAccessibilityFocused()); @@ -771,7 +771,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, public final void onBusEvent(DragEndEvent event) { if (!(event.dropTarget instanceof TaskStack.DockState)) { - event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { + event.addPostAnimationCallback(new Runnable() { @Override public void run() { // Animate the drag view back from where it is, to the view location, then after diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index 0271ccd39b8d..9a2ffe712248 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -60,8 +60,8 @@ public class TaskViewHeader extends FrameLayout // Header views ImageView mMoveTaskButton; ImageView mDismissButton; - ImageView mApplicationIcon; - TextView mActivityDescription; + ImageView mIconView; + TextView mTitleView; int mMoveTaskTargetStackId = INVALID_STACK_ID; // Header drawables @@ -128,16 +128,16 @@ public class TaskViewHeader extends FrameLayout @Override protected void onFinishInflate() { // Initialize the icon and description views - mApplicationIcon = (ImageView) findViewById(R.id.application_icon); - mApplicationIcon.setOnLongClickListener(this); - mActivityDescription = (TextView) findViewById(R.id.activity_description); + mIconView = (ImageView) findViewById(R.id.icon); + mIconView.setOnLongClickListener(this); + mTitleView = (TextView) findViewById(R.id.title); mDismissButton = (ImageView) findViewById(R.id.dismiss_task); mDismissButton.setOnClickListener(this); mMoveTaskButton = (ImageView) findViewById(R.id.move_task); // Hide the backgrounds if they are ripple drawables - if (mApplicationIcon.getBackground() instanceof RippleDrawable) { - mApplicationIcon.setBackground(null); + if (mIconView.getBackground() instanceof RippleDrawable) { + mIconView.setBackground(null); } mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable( @@ -158,8 +158,8 @@ public class TaskViewHeader extends FrameLayout public void onTaskViewSizeChanged(int width, int height) { mTaskViewRect.set(0, 0, width, height); boolean updateMoveTaskButton = mMoveTaskButton.getVisibility() != View.GONE; - int appIconWidth = mApplicationIcon.getMeasuredWidth(); - int activityDescWidth = mActivityDescription.getMeasuredWidth(); + int appIconWidth = mIconView.getMeasuredWidth(); + int activityDescWidth = mTitleView.getMeasuredWidth(); int dismissIconWidth = mDismissButton.getMeasuredWidth(); int moveTaskIconWidth = mMoveTaskButton.getVisibility() == View.VISIBLE ? mMoveTaskButton.getMeasuredWidth() @@ -168,26 +168,26 @@ public class TaskViewHeader extends FrameLayout // Priority-wise, we show the activity icon first, the dismiss icon if there is room, the // move-task icon if there is room, and then finally, the activity label if there is room if (width < (appIconWidth + dismissIconWidth)) { - mActivityDescription.setVisibility(View.INVISIBLE); + mTitleView.setVisibility(View.INVISIBLE); if (updateMoveTaskButton) { mMoveTaskButton.setVisibility(View.INVISIBLE); } mDismissButton.setVisibility(View.INVISIBLE); } else if (width < (appIconWidth + dismissIconWidth + moveTaskIconWidth)) { - mActivityDescription.setVisibility(View.INVISIBLE); + mTitleView.setVisibility(View.INVISIBLE); if (updateMoveTaskButton) { mMoveTaskButton.setVisibility(View.INVISIBLE); } mDismissButton.setVisibility(View.VISIBLE); } else if (width < (appIconWidth + dismissIconWidth + moveTaskIconWidth + activityDescWidth)) { - mActivityDescription.setVisibility(View.INVISIBLE); + mTitleView.setVisibility(View.INVISIBLE); if (updateMoveTaskButton) { mMoveTaskButton.setVisibility(View.VISIBLE); } mDismissButton.setVisibility(View.VISIBLE); } else { - mActivityDescription.setVisibility(View.VISIBLE); + mTitleView.setVisibility(View.VISIBLE); if (updateMoveTaskButton) { mMoveTaskButton.setVisibility(View.VISIBLE); } @@ -233,15 +233,13 @@ public class TaskViewHeader extends FrameLayout // If an activity icon is defined, then we use that as the primary icon to show in the bar, // otherwise, we fall back to the application icon - if (t.activityIcon != null) { - mApplicationIcon.setImageDrawable(t.activityIcon); - } else if (t.applicationIcon != null) { - mApplicationIcon.setImageDrawable(t.applicationIcon); + if (t.icon != null) { + mIconView.setImageDrawable(t.icon); } - if (!mActivityDescription.getText().toString().equals(t.activityLabel)) { - mActivityDescription.setText(t.activityLabel); + if (!mTitleView.getText().toString().equals(t.title)) { + mTitleView.setText(t.title); } - mActivityDescription.setContentDescription(t.contentDescription); + mTitleView.setContentDescription(t.contentDescription); // Try and apply the system ui tint int existingBgColor = (getBackground() instanceof ColorDrawable) ? @@ -254,7 +252,7 @@ public class TaskViewHeader extends FrameLayout R.color.recents_task_bar_light_text_color); int taskBarViewDarkTextColor = getResources().getColor( R.color.recents_task_bar_dark_text_color); - mActivityDescription.setTextColor(t.useLightOnPrimaryColor ? + mTitleView.setTextColor(t.useLightOnPrimaryColor ? taskBarViewLightTextColor : taskBarViewDarkTextColor); mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ? mLightDismissDrawable : mDarkDismissDrawable); @@ -281,15 +279,15 @@ public class TaskViewHeader extends FrameLayout // In accessibility, a single click on the focused app info button will show it if (ssp.isTouchExplorationEnabled()) { - mApplicationIcon.setOnClickListener(this); + mIconView.setOnClickListener(this); } } /** Unbinds the bar view from the task */ void unbindFromTask() { mTask = null; - mApplicationIcon.setImageDrawable(null); - mApplicationIcon.setOnClickListener(null); + mIconView.setImageDrawable(null); + mIconView.setOnClickListener(null); mMoveTaskButton.setOnClickListener(null); } @@ -357,7 +355,7 @@ public class TaskViewHeader extends FrameLayout @Override public void onClick(View v) { - if (v == mApplicationIcon) { + if (v == mIconView) { // In accessibility, a single click on the focused app info button will show it EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); } else if (v == mDismissButton) { @@ -379,7 +377,7 @@ public class TaskViewHeader extends FrameLayout @Override public boolean onLongClick(View v) { - if (v == mApplicationIcon) { + if (v == mIconView) { EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 6d4dc87283a3..a6ca50ac6725 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -947,7 +947,7 @@ public abstract class BaseStatusBar extends SystemUI implements final StatusBarNotification sbn = row.getStatusBarNotification(); PackageManager pmUser = getPackageManagerForUser(mContext, sbn.getUser().getIdentifier()); row.setTag(sbn.getPackageName()); - final View guts = row.getGuts(); + final NotificationGuts guts = row.getGuts(); final String pkg = sbn.getPackageName(); String appname = pkg; Drawable pkgicon = null; @@ -969,8 +969,6 @@ public abstract class BaseStatusBar extends SystemUI implements ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(pkgicon); ((TextView) row.findViewById(R.id.pkgname)).setText(appname); - bindTopicImportance(sbn, row); - final View settingsButton = guts.findViewById(R.id.notification_inspect_item); if (appUid >= 0) { final int appUidF = appUid; @@ -983,69 +981,8 @@ public abstract class BaseStatusBar extends SystemUI implements } else { settingsButton.setVisibility(View.GONE); } - } - - private void bindTopicImportance(final StatusBarNotification sbn, - ExpandableNotificationRow row) { - final INotificationManager sINM = INotificationManager.Stub.asInterface( - ServiceManager.getService(Context.NOTIFICATION_SERVICE)); - final Notification.Topic topic = sbn.getNotification().getTopic() == null - ? new Notification.Topic(Notification.TOPIC_DEFAULT, mContext.getString( - com.android.internal.R.string.default_notification_topic_label)) - : sbn.getNotification().getTopic(); - - ((TextView) row.findViewById(R.id.topic_details)).setText(topic.getLabel()); - final TextView topicSummary = ((TextView) row.findViewById(R.id.summary)); - int importance = mNotificationData.getImportance(sbn.getKey()); - SeekBar seekBar = (SeekBar) row.findViewById(R.id.seekbar); - seekBar.setMax(4); - seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - topicSummary.setText(getProgressSummary(progress)); - if (fromUser) { - try { - sINM.setTopicImportance(sbn.getPackageName(), sbn.getUid(), topic, - progress); - } catch (RemoteException e) { - // :( - } - } - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - // no-op - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - // no-op - } - private String getProgressSummary(int progress) { - switch (progress) { - case NotificationListenerService.Ranking.IMPORTANCE_NONE: - return mContext.getString( - com.android.internal.R.string.notification_importance_blocked); - case NotificationListenerService.Ranking.IMPORTANCE_LOW: - return mContext.getString( - com.android.internal.R.string.notification_importance_low); - case NotificationListenerService.Ranking.IMPORTANCE_DEFAULT: - return mContext.getString( - com.android.internal.R.string.notification_importance_default); - case NotificationListenerService.Ranking.IMPORTANCE_HIGH: - return mContext.getString( - com.android.internal.R.string.notification_importance_high); - case NotificationListenerService.Ranking.IMPORTANCE_MAX: - return mContext.getString( - com.android.internal.R.string.notification_importance_max); - default: - return ""; - } - } - }); - seekBar.setProgress(importance); + guts.bindImportance(sbn, row, mNotificationData.getImportance(sbn.getKey())); } protected SwipeHelper.LongPressListener getNotificationLongClicker() { @@ -1612,8 +1549,14 @@ public abstract class BaseStatusBar extends SystemUI implements for (int i = 0; i < numActions; i++) { Notification.Action action = actions.get(i); + if (action == null) { + continue; + } RemoteInput[] remoteInputs = action.getRemoteInputs(); - for (RemoteInput ri : action.getRemoteInputs()) { + if (remoteInputs == null) { + continue; + } + for (RemoteInput ri : remoteInputs) { if (ri.getAllowFreeFormInput()) { viableAction = action; break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java index 0081496d2351..57db80a0744b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java @@ -16,12 +16,22 @@ package com.android.systemui.statusbar; +import android.app.INotificationManager; +import android.app.Notification; import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.Drawable; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; import android.util.AttributeSet; +import android.view.View; import android.widget.FrameLayout; import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.SeekBar; +import android.widget.TextView; import com.android.systemui.R; @@ -83,6 +93,88 @@ public class NotificationGuts extends LinearLayout { } } + void bindImportance(final StatusBarNotification sbn, final ExpandableNotificationRow row, + final int importance) { + final INotificationManager sINM = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + final Notification.Topic topic = sbn.getNotification().getTopic() == null + ? new Notification.Topic(Notification.TOPIC_DEFAULT, mContext.getString( + com.android.internal.R.string.default_notification_topic_label)) + : sbn.getNotification().getTopic(); + + final RadioButton applyToTopic = (RadioButton) row.findViewById(R.id.apply_to_topic); + if (sbn.getNotification().getTopic() != null) { + applyToTopic.setVisibility(View.VISIBLE); + applyToTopic.setChecked(true); + applyToTopic.setText(mContext.getString(R.string.apply_to_topic, topic.getLabel())); + row.findViewById(R.id.apply_to_app).setVisibility(View.VISIBLE); + } + + final TextView topicSummary = ((TextView) row.findViewById(R.id.summary)); + final TextView topicTitle = ((TextView) row.findViewById(R.id.title)); + SeekBar seekBar = (SeekBar) row.findViewById(R.id.seekbar); + seekBar.setMax(4); + seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + updateTitleAndSummary(progress); + if (fromUser) { + try { + if (applyToTopic.isChecked()) { + sINM.setTopicImportance(sbn.getPackageName(), sbn.getUid(), topic, + progress); + } else { + sINM.setAppImportance(sbn.getPackageName(), sbn.getUid(), progress); + } + } catch (RemoteException e) { + // :( + } + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // no-op + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // no-op + } + + private void updateTitleAndSummary(int progress) { + switch (progress) { + case NotificationListenerService.Ranking.IMPORTANCE_NONE: + topicSummary.setText(mContext.getString( + R.string.notification_importance_blocked)); + topicTitle.setText(mContext.getString(R.string.blocked_importance)); + break; + case NotificationListenerService.Ranking.IMPORTANCE_LOW: + topicSummary.setText(mContext.getString( + R.string.notification_importance_low)); + topicTitle.setText(mContext.getString(R.string.low_importance)); + break; + case NotificationListenerService.Ranking.IMPORTANCE_DEFAULT: + topicSummary.setText(mContext.getString( + R.string.notification_importance_default)); + topicTitle.setText(mContext.getString(R.string.default_importance)); + break; + case NotificationListenerService.Ranking.IMPORTANCE_HIGH: + topicSummary.setText(mContext.getString( + R.string.notification_importance_high)); + topicTitle.setText(mContext.getString(R.string.high_importance)); + break; + case NotificationListenerService.Ranking.IMPORTANCE_MAX: + topicSummary.setText(mContext.getString( + R.string.notification_importance_max)); + topicTitle.setText(mContext.getString(R.string.max_importance)); + break; + } + } + }); + seekBar.setProgress(importance); + } + public void setActualHeight(int actualHeight) { mActualHeight = actualHeight; invalidate(); diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index df18d3ea9205..6f8f8eba75f4 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -16,6 +16,7 @@ package com.android.server.appwidget; +import android.app.ActivityManager; import android.app.AlarmManager; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -76,6 +77,7 @@ import android.view.Display; import android.view.WindowManager; import android.widget.RemoteViews; +import com.android.internal.R; import com.android.internal.appwidget.IAppWidgetHost; import com.android.internal.appwidget.IAppWidgetService; import com.android.internal.os.BackgroundThread; @@ -83,7 +85,6 @@ import com.android.internal.os.SomeArgs; import com.android.internal.util.FastXmlSerializer; import com.android.internal.widget.IRemoteViewsAdapterConnection; import com.android.internal.widget.IRemoteViewsFactory; -import com.android.internal.R; import com.android.server.LocalServices; import com.android.server.WidgetBackupProvider; @@ -139,8 +140,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private static final int CURRENT_VERSION = 1; private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); + final String action = intent.getAction(); + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); if (DEBUG) { Slog.i(TAG, "Received broadcast: " + action); @@ -148,23 +151,16 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { onConfigurationChanged(); - } else if (Intent.ACTION_USER_STARTED.equals(action)) { - onUserStarted(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, - UserHandle.USER_NULL)); + } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { + onUserUnlocked(userId); } else if (Intent.ACTION_USER_STOPPED.equals(action)) { - onUserStopped(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, - UserHandle.USER_NULL)); + onUserStopped(userId); } else if (Intent.ACTION_USER_SWITCHED.equals(action)) { - refreshProfileWidgetsMaskedState(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, - UserHandle.USER_NULL)); + refreshProfileWidgetsMaskedState(userId); } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED.equals(action)) { - UserHandle profile = (UserHandle)intent.getParcelableExtra(Intent.EXTRA_USER); - if (profile != null) { - refreshWidgetMaskedState(profile.getIdentifier()); - } + refreshWidgetMaskedState(userId); } else { - onPackageBroadcastReceived(intent, intent.getIntExtra( - Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL)); + onPackageBroadcastReceived(intent, userId); } } }; @@ -263,7 +259,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku sdFilter, null, null); IntentFilter userFilter = new IntentFilter(); - userFilter.addAction(Intent.ACTION_USER_STARTED); + userFilter.addAction(Intent.ACTION_USER_UNLOCKED); userFilter.addAction(Intent.ACTION_USER_STOPPED); userFilter.addAction(Intent.ACTION_USER_SWITCHED); mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, @@ -340,6 +336,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void onPackageBroadcastReceived(Intent intent, int userId) { + if (!mUserManager.isUserUnlocked(userId)) return; + final String action = intent.getAction(); boolean added = false; boolean changed = false; @@ -419,9 +417,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku * Refresh the masked state for all profiles under the given user. */ private void refreshProfileWidgetsMaskedState(int userId) { - if (userId == UserHandle.USER_NULL) { - return; - } + if (!mUserManager.isUserUnlocked(userId)) return; List<UserInfo> profiles = mUserManager.getEnabledProfiles(userId); if (profiles != null) { for (int i = 0; i < profiles.size(); i++) { @@ -435,6 +431,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku * Mask/unmask widgets in the given profile, depending on the quiet state of the profile. */ private void refreshWidgetMaskedState(int profileId) { + if (!mUserManager.isUserUnlocked(profileId)) return; final long identity = Binder.clearCallingIdentity(); try { UserInfo user = mUserManager.getUserInfo(profileId); @@ -484,6 +481,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void ensureGroupStateLoadedLocked(int userId) { + if (!mUserManager.isUserUnlocked(userId)) { + throw new IllegalStateException( + "User " + userId + " must be unlocked for widgets to be available"); + } + final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId); // Careful lad, we may have already loaded the state for some @@ -2321,7 +2323,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } - private void onUserStarted(int userId) { + private void onUserUnlocked(int userId) { synchronized (mLock) { ensureGroupStateLoadedLocked(userId); diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index 86183af06f7c..4dc46ac63643 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -48,6 +48,13 @@ public class SystemConfig { static SystemConfig sInstance; + // permission flag, determines which types of configuration are allowed to be read + private static final int ALLOW_FEATURES = 0x01; + private static final int ALLOW_LIBS = 0x02; + private static final int ALLOW_PERMISSIONS = 0x04; + private static final int ALLOW_APP_CONFIGS = 0x08; + private static final int ALLOW_ALL = ~0; + // Group-ids that are given to all packages as read from etc/permissions/*.xml. int[] mGlobalGids; @@ -161,21 +168,27 @@ public class SystemConfig { SystemConfig() { // Read configuration from system readPermissions(Environment.buildPath( - Environment.getRootDirectory(), "etc", "sysconfig"), false); + Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL); // Read configuration from the old permissions dir readPermissions(Environment.buildPath( - Environment.getRootDirectory(), "etc", "permissions"), false); - // Only read features from OEM config + Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL); + // Allow ODM to customize system configs around libs, features and apps + int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS; + readPermissions(Environment.buildPath( + Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag); + readPermissions(Environment.buildPath( + Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag); + // Only allow OEM to customize features readPermissions(Environment.buildPath( - Environment.getOemDirectory(), "etc", "sysconfig"), true); + Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES); readPermissions(Environment.buildPath( - Environment.getOemDirectory(), "etc", "permissions"), true); + Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES); } - void readPermissions(File libraryDir, boolean onlyFeatures) { + void readPermissions(File libraryDir, int permissionFlag) { // Read permissions from given directory. if (!libraryDir.exists() || !libraryDir.isDirectory()) { - if (!onlyFeatures) { + if (permissionFlag == ALLOW_ALL) { Slog.w(TAG, "No directory " + libraryDir + ", skipping"); } return; @@ -203,16 +216,16 @@ public class SystemConfig { continue; } - readPermissionsFromXml(f, onlyFeatures); + readPermissionsFromXml(f, permissionFlag); } // Read platform permissions last so it will take precedence if (platformFile != null) { - readPermissionsFromXml(platformFile, onlyFeatures); + readPermissionsFromXml(platformFile, permissionFlag); } } - private void readPermissionsFromXml(File permFile, boolean onlyFeatures) { + private void readPermissionsFromXml(File permFile, int permissionFlag) { FileReader permReader = null; try { permReader = new FileReader(permFile); @@ -242,6 +255,11 @@ public class SystemConfig { + ": found " + parser.getName() + ", expected 'permissions' or 'config'"); } + boolean allowAll = permissionFlag == ALLOW_ALL; + boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0; + boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0; + boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0; + boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0; while (true) { XmlUtils.nextElement(parser); if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { @@ -249,7 +267,7 @@ public class SystemConfig { } String name = parser.getName(); - if ("group".equals(name) && !onlyFeatures) { + if ("group".equals(name) && allowAll) { String gidStr = parser.getAttributeValue(null, "gid"); if (gidStr != null) { int gid = android.os.Process.getGidForName(gidStr); @@ -261,7 +279,7 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; - } else if ("permission".equals(name) && !onlyFeatures) { + } else if ("permission".equals(name) && allowPermissions) { String perm = parser.getAttributeValue(null, "name"); if (perm == null) { Slog.w(TAG, "<permission> without name in " + permFile + " at " @@ -272,7 +290,7 @@ public class SystemConfig { perm = perm.intern(); readPermission(parser, perm); - } else if ("assign-permission".equals(name) && !onlyFeatures) { + } else if ("assign-permission".equals(name) && allowPermissions) { String perm = parser.getAttributeValue(null, "name"); if (perm == null) { Slog.w(TAG, "<assign-permission> without name in " + permFile + " at " @@ -304,7 +322,7 @@ public class SystemConfig { perms.add(perm); XmlUtils.skipCurrentTag(parser); - } else if ("library".equals(name) && !onlyFeatures) { + } else if ("library".equals(name) && allowLibs) { String lname = parser.getAttributeValue(null, "name"); String lfile = parser.getAttributeValue(null, "file"); if (lname == null) { @@ -320,7 +338,7 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; - } else if ("feature".equals(name)) { + } else if ("feature".equals(name) && allowFeatures) { String fname = parser.getAttributeValue(null, "name"); boolean allowed; if (!lowRam) { @@ -341,7 +359,7 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; - } else if ("unavailable-feature".equals(name)) { + } else if ("unavailable-feature".equals(name) && allowFeatures) { String fname = parser.getAttributeValue(null, "name"); if (fname == null) { Slog.w(TAG, "<unavailable-feature> without name in " + permFile + " at " @@ -352,7 +370,7 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; - } else if ("allow-in-power-save-except-idle".equals(name) && !onlyFeatures) { + } else if ("allow-in-power-save-except-idle".equals(name) && allowAll) { String pkgname = parser.getAttributeValue(null, "package"); if (pkgname == null) { Slog.w(TAG, "<allow-in-power-save-except-idle> without package in " @@ -363,7 +381,7 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; - } else if ("allow-in-power-save".equals(name) && !onlyFeatures) { + } else if ("allow-in-power-save".equals(name) && allowAll) { String pkgname = parser.getAttributeValue(null, "package"); if (pkgname == null) { Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at " @@ -374,7 +392,7 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; - } else if ("fixed-ime-app".equals(name) && !onlyFeatures) { + } else if ("fixed-ime-app".equals(name) && allowAll) { String pkgname = parser.getAttributeValue(null, "package"); if (pkgname == null) { Slog.w(TAG, "<fixed-ime-app> without package in " + permFile + " at " @@ -385,7 +403,7 @@ public class SystemConfig { XmlUtils.skipCurrentTag(parser); continue; - } else if ("app-link".equals(name)) { + } else if ("app-link".equals(name) && allowAppConfigs) { String pkgname = parser.getAttributeValue(null, "package"); if (pkgname == null) { Slog.w(TAG, "<app-link> without package in " + permFile + " at " @@ -394,7 +412,7 @@ public class SystemConfig { mLinkedApps.add(pkgname); } XmlUtils.skipCurrentTag(parser); - } else if ("system-user-whitelisted-app".equals(name)) { + } else if ("system-user-whitelisted-app".equals(name) && allowAppConfigs) { String pkgname = parser.getAttributeValue(null, "package"); if (pkgname == null) { Slog.w(TAG, "<system-user-whitelisted-app> without package in " + permFile @@ -403,7 +421,7 @@ public class SystemConfig { mSystemUserWhitelistedApps.add(pkgname); } XmlUtils.skipCurrentTag(parser); - } else if ("system-user-blacklisted-app".equals(name)) { + } else if ("system-user-blacklisted-app".equals(name) && allowAppConfigs) { String pkgname = parser.getAttributeValue(null, "package"); if (pkgname == null) { Slog.w(TAG, "<system-user-blacklisted-app without package in " + permFile diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 70f3c051a6e3..de0a23aa723f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -324,6 +324,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILIT import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.ActivityStackSupervisor.ActivityContainer.FORCE_NEW_TASK_FLAGS; import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS; import static com.android.server.am.ActivityStackSupervisor.ON_TOP; import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; @@ -511,6 +512,8 @@ public final class ActivityManagerService extends ActivityManagerNative /** Run all ActivityStacks through this */ ActivityStackSupervisor mStackSupervisor; + ActivityStarter mActivityStarter; + /** Task stack change listeners. */ private RemoteCallbackList<ITaskStackListener> mTaskStackListeners = new RemoteCallbackList<ITaskStackListener>(); @@ -1788,7 +1791,7 @@ public final class ActivityManagerService extends ActivityManagerNative } break; case DO_PENDING_ACTIVITY_LAUNCHES_MSG: { synchronized (ActivityManagerService.this) { - mStackSupervisor.doPendingActivityLaunchesLocked(true); + mActivityStarter.doPendingActivityLaunchesLocked(true); } } break; case KILL_APPLICATION_MSG: { @@ -2296,6 +2299,7 @@ public final class ActivityManagerService extends ActivityManagerNative public void setWindowManager(WindowManagerService wm) { mWindowManager = wm; mStackSupervisor.setWindowManager(wm); + mActivityStarter.setWindowManager(wm); } public void setUsageStatsManager(UsageStatsManagerInternal usageStatsManager) { @@ -2483,6 +2487,7 @@ public final class ActivityManagerService extends ActivityManagerNative mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler); mRecentTasks = new RecentTasks(this); mStackSupervisor = new ActivityStackSupervisor(this, mRecentTasks); + mActivityStarter = new ActivityStarter(this, mStackSupervisor); mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, mRecentTasks); mProcessCpuThread = new Thread("CpuTracker") { @@ -3613,7 +3618,7 @@ public final class ActivityManagerService extends ActivityManagerNative aInfo.applicationInfo.uid, true); if (app == null || app.instrumentationClass == null) { intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); - mStackSupervisor.startHomeActivity(intent, aInfo, reason); + mActivityStarter.startHomeActivityLocked(intent, aInfo, reason); } } @@ -3691,7 +3696,7 @@ public final class ActivityManagerService extends ActivityManagerNative intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(new ComponentName( ri.activityInfo.packageName, ri.activityInfo.name)); - mStackSupervisor.startActivityLocked(null, intent, null /*ephemeralIntent*/, + mActivityStarter.startActivityLocked(null, intent, null /*ephemeralIntent*/, null, ri.activityInfo, null /*rInfo*/, null, null, null, null, 0, 0, 0, null, 0, 0, 0, null, false, false, null, null, null); } @@ -4018,6 +4023,25 @@ public final class ActivityManagerService extends ActivityManagerNative UserHandle.getCallingUserId()); } + final int startActivity(Intent intent, ActivityStackSupervisor.ActivityContainer container) { + enforceNotIsolatedCaller("ActivityContainer.startActivity"); + final int userId = mUserController.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), mStackSupervisor.mCurrentUser, false, + ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null); + + // TODO: Switch to user app stacks here. + String mimeType = intent.getType(); + final Uri data = intent.getData(); + if (mimeType == null && data != null && "content".equals(data.getScheme())) { + mimeType = getProviderMimeType(data, userId); + } + container.checkEmbeddedAllowedInner(userId, intent, mimeType); + + intent.addFlags(FORCE_NEW_TASK_FLAGS); + return mActivityStarter.startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, + null, 0, 0, null, null, null, null, false, userId, container, null); + } + @Override public final int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, @@ -4026,7 +4050,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivity", null); // TODO: Switch to user app stacks here. - return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, + return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, null, null, bOptions, false, userId, null, null); } @@ -4089,7 +4113,7 @@ public final class ActivityManagerService extends ActivityManagerNative // TODO: Switch to user app stacks here. try { - int ret = mStackSupervisor.startActivityMayWait(null, targetUid, targetPackage, intent, + int ret = mActivityStarter.startActivityMayWait(null, targetUid, targetPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, null, null, null, bOptions, ignoreTargetSecurity, userId, null, null); return ret; @@ -4118,7 +4142,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId, false, ALLOW_FULL_ONLY, "startActivityAndWait", null); WaitResult res = new WaitResult(); // TODO: Switch to user app stacks here. - mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, + mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, res, null, bOptions, false, userId, null, null); return res; @@ -4132,7 +4156,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivityWithConfig", null); // TODO: Switch to user app stacks here. - int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, + int ret = mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, null, null, config, bOptions, false, userId, null, null); return ret; @@ -4190,7 +4214,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false, ALLOW_FULL_ONLY, "startVoiceActivity", null); // TODO: Switch to user app stacks here. - return mStackSupervisor.startActivityMayWait(null, callingUid, callingPackage, intent, + return mActivityStarter.startActivityMayWait(null, callingUid, callingPackage, intent, resolvedType, session, interactor, null, null, 0, startFlags, profilerInfo, null, null, bOptions, false, userId, null, null); } @@ -4301,7 +4325,7 @@ public final class ActivityManagerService extends ActivityManagerNative } final long origId = Binder.clearCallingIdentity(); - int res = mStackSupervisor.startActivityLocked(r.app.thread, intent, + int res = mActivityStarter.startActivityLocked(r.app.thread, intent, null /*ephemeralIntent*/, r.resolvedType, aInfo, null /*rInfo*/, null, null, resultTo != null ? resultTo.appToken : null, resultWho, requestCode, -1, r.launchedFromUid, r.launchedFromPackage, -1, r.launchedFromUid, 0, options, @@ -4386,7 +4410,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null); // TODO: Switch to user app stacks here. - int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent, + int ret = mActivityStarter.startActivityMayWait(null, uid, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, null, null, null, bOptions, false, userId, container, inTask); return ret; @@ -4400,7 +4424,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivity", null); // TODO: Switch to user app stacks here. - int ret = mStackSupervisor.startActivities(caller, -1, callingPackage, intents, + int ret = mActivityStarter.startActivities(caller, -1, callingPackage, intents, resolvedTypes, resultTo, bOptions, userId); return ret; } @@ -4412,7 +4436,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null); // TODO: Switch to user app stacks here. - int ret = mStackSupervisor.startActivities(null, uid, callingPackage, intents, resolvedTypes, + int ret = mActivityStarter.startActivities(null, uid, callingPackage, intents, resolvedTypes, resultTo, bOptions, userId); return ret; } @@ -8780,6 +8804,20 @@ public final class ActivityManagerService extends ActivityManagerNative continue; } } + if ((flags & ActivityManager.RECENT_INGORE_DOCKED_STACK_TASKS) != 0) { + if (tr.stack != null && tr.stack.isDockedStack()) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "Skipping, docked stack task: " + tr); + continue; + } + } + if ((flags & ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS) != 0) { + if (tr.stack != null && tr.stack.isPinnedStack()) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "Skipping, pinned stack task: " + tr); + continue; + } + } if (tr.autoRemoveRecents && tr.getTopActivity() == null) { // Don't include auto remove tasks that are finished or finishing. if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, @@ -17093,6 +17131,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } + // Verify that protected broadcasts are only being sent by system code, + // and that system code is only sending protected broadcasts. final String action = intent.getAction(); final boolean isProtectedBroadcast; try { @@ -17102,35 +17142,47 @@ public final class ActivityManagerService extends ActivityManagerNative return ActivityManager.BROADCAST_SUCCESS; } - /* - * Prevent non-system code (defined here to be non-persistent - * processes) from sending protected broadcasts. - */ - int callingAppId = UserHandle.getAppId(callingUid); - if (callingAppId == Process.SYSTEM_UID || callingAppId == Process.PHONE_UID - || callingAppId == Process.SHELL_UID || callingAppId == Process.BLUETOOTH_UID - || callingAppId == Process.NFC_UID || callingUid == 0) { - // Always okay. - - // Yell if the system is trying to send a non-protected broadcast. - // The vast majority of broadcasts sent from system callers should - // be protected to avoid security holes, so exceptions here should - // be incredibly rare. - if (!isProtectedBroadcast - && !Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { - // TODO: eventually switch over to hard throw + final boolean isCallerSystem; + switch (UserHandle.getAppId(callingUid)) { + case Process.ROOT_UID: + case Process.SYSTEM_UID: + case Process.PHONE_UID: + case Process.SHELL_UID: + case Process.BLUETOOTH_UID: + case Process.NFC_UID: + isCallerSystem = true; + break; + default: + isCallerSystem = (callerApp != null) && callerApp.persistent; + break; + } + + if (isCallerSystem) { + if (isProtectedBroadcast + || Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) + || Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) + || AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action) + || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { + // Broadcast is either protected, or it's a public action that + // we've relaxed, so it's fine for system internals to send. + } else { + // The vast majority of broadcasts sent from system internals + // should be protected to avoid security holes, so yell loudly + // to ensure we examine these cases. Log.wtf(TAG, "Sending non-protected broadcast " + action + " from system", new Throwable()); } - } else if (callerApp == null || !callerApp.persistent) { + } else { if (isProtectedBroadcast) { String msg = "Permission Denial: not allowed to send broadcast " + action + " from pid=" + callingPid + ", uid=" + callingUid; Slog.w(TAG, msg); throw new SecurityException(msg); - } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)) { + + } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action) + || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { // Special case for compatibility: we don't want apps to send this, // but historically it has not been protected and apps may be using it // to poke their own app widget. So, instead of making it protected, @@ -20841,7 +20893,7 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("Bad app thread " + appThread); } } - return mStackSupervisor.startActivityMayWait(appThread, -1, callingPackage, intent, + return mActivityStarter.startActivityMayWait(appThread, -1, callingPackage, intent, resolvedType, null, null, null, null, 0, 0, null, null, null, bOptions, false, callingUser, null, tr); } diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index b64aa0e133f2..4d9120b09478 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -16,22 +16,21 @@ package com.android.server.am; -import static com.android.server.am.ActivityManagerDebugConfig.*; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SAVED_STATE; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_THUMBNAILS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STATES; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_THUMBNAILS; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.TaskRecord.INVALID_TASK_ID; import android.app.ActivityManager.TaskDescription; -import android.app.PendingIntent; -import android.os.PersistableBundle; -import android.os.Trace; - -import com.android.internal.app.ResolverActivity; -import com.android.internal.content.ReferrerIntent; -import com.android.internal.util.XmlUtils; -import com.android.server.AttributeCache; -import com.android.server.am.ActivityStack.ActivityState; -import com.android.server.am.ActivityStackSupervisor.ActivityContainer; - import android.app.ActivityOptions; +import android.app.PendingIntent; import android.app.ResultInfo; import android.content.ComponentName; import android.content.Intent; @@ -45,9 +44,11 @@ import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.Message; +import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; +import android.os.Trace; import android.os.UserHandle; import android.util.EventLog; import android.util.Log; @@ -57,9 +58,12 @@ import android.view.AppTransitionAnimationSpec; import android.view.IApplicationToken; import android.view.WindowManager; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; +import com.android.internal.app.ResolverActivity; +import com.android.internal.content.ReferrerIntent; +import com.android.internal.util.XmlUtils; +import com.android.server.AttributeCache; +import com.android.server.am.ActivityStack.ActivityState; +import com.android.server.am.ActivityStackSupervisor.ActivityContainer; import java.io.File; import java.io.IOException; @@ -69,6 +73,10 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.Objects; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + /** * An entry in the history stack, representing an activity. */ @@ -400,6 +408,11 @@ final class ActivityRecord { } } + boolean isFreeform() { + return task != null && task.stack != null + && task.stack.mStackId == FREEFORM_WORKSPACE_STACK_ID; + } + static class Token extends IApplicationToken.Stub { private final WeakReference<ActivityRecord> weakActivity; private final ActivityManagerService mService; diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index bfd17b2e1bf9..1492e23e37e4 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -28,6 +28,7 @@ import static com.android.server.am.ActivityManagerService.LOCK_SCREEN_SHOWN; import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; +import static com.android.server.am.ActivityStackSupervisor.FindTaskResult; import static com.android.server.am.ActivityStackSupervisor.MOVING; import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; @@ -74,7 +75,6 @@ import android.os.Trace; import android.os.UserHandle; import android.service.voice.IVoiceInteractionSession; import android.util.EventLog; -import android.util.Log; import android.util.Slog; import android.view.Display; @@ -519,6 +519,14 @@ final class ActivityStack { return mStackId == HOME_STACK_ID; } + final boolean isDockedStack() { + return mStackId == DOCKED_STACK_ID; + } + + final boolean isPinnedStack() { + return mStackId == PINNED_STACK_ID; + } + final boolean isOnHomeDisplay() { return isAttached() && mActivityContainer.mActivityDisplay.mDisplayId == Display.DEFAULT_DISPLAY; @@ -569,10 +577,10 @@ final class ActivityStack { } /** - * Returns the top activity in any existing task matching the given - * Intent. Returns null if no such task is found. + * Returns the top activity in any existing task matching the given Intent in the input result. + * Returns null if no such task is found. */ - ActivityRecord findTaskLocked(ActivityRecord target) { + void findTaskLocked(ActivityRecord target, FindTaskResult result) { Intent intent = target.intent; ActivityInfo info = target.info; ComponentName cls = intent.getComponent(); @@ -627,10 +635,15 @@ final class ActivityStack { + taskIntent.getComponent().flattenToShortString() + "/aff=" + r.task.rootAffinity + " to new cls=" + intent.getComponent().flattenToShortString() + "/aff=" + info.taskAffinity); - if (!isDocument && !taskIsDocument && task.rootAffinity != null) { + if (!isDocument && !taskIsDocument + && result.r == null && task.canMatchRootAffinity()) { if (task.rootAffinity.equals(target.taskAffinity)) { - if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching affinity!"); - return r; + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching affinity candidate!"); + // It is possible for multiple tasks to have the same root affinity especially + // if they are in separate stacks. We save off this candidate, but keep looking + // to see if there is a better candidate. + result.r = r; + result.matchedByRootAffinity = true; } } else if (taskIntent != null && taskIntent.getComponent() != null && taskIntent.getComponent().compareTo(cls) == 0 && @@ -639,7 +652,9 @@ final class ActivityStack { //dump(); if (DEBUG_TASKS) Slog.d(TAG_TASKS, "For Intent " + intent + " bringing to top: " + r.intent); - return r; + result.r = r; + result.matchedByRootAffinity = false; + break; } else if (affinityIntent != null && affinityIntent.getComponent() != null && affinityIntent.getComponent().compareTo(cls) == 0 && Objects.equals(documentData, taskDocumentData)) { @@ -647,11 +662,11 @@ final class ActivityStack { //dump(); if (DEBUG_TASKS) Slog.d(TAG_TASKS, "For Intent " + intent + " bringing to top: " + r.intent); - return r; + result.r = r; + result.matchedByRootAffinity = false; + break; } else if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Not a match: " + task); } - - return null; } /** @@ -813,10 +828,9 @@ final class ActivityStack { if (hasVisibleBehindActivity()) { // Stop visible behind activity before going to sleep. - final ActivityRecord r = mActivityContainer.mActivityDisplay.mVisibleBehindActivity; + final ActivityRecord r = getVisibleBehindActivity(); mStackSupervisor.mStoppingActivities.add(r); - if (DEBUG_STATES) Slog.v(TAG_STATES, - "Sleep still waiting to stop visible behind " + r); + if (DEBUG_STATES) Slog.v(TAG_STATES, "Sleep still waiting to stop visible behind " + r); return true; } @@ -1053,7 +1067,7 @@ final class ActivityStack { mHandler.removeMessages(STOP_TIMEOUT_MSG, r); r.stopped = true; r.state = ActivityState.STOPPED; - if (mActivityContainer.mActivityDisplay.mVisibleBehindActivity == r) { + if (getVisibleBehindActivity() == r) { mStackSupervisor.requestVisibleBehindLocked(r, false); } if (r.finishing) { @@ -1214,9 +1228,9 @@ final class ActivityStack { next.returningOptions = null; - if (mActivityContainer.mActivityDisplay.mVisibleBehindActivity == next) { + if (getVisibleBehindActivity() == next) { // When resuming an activity, require it to call requestVisibleBehind() again. - mActivityContainer.mActivityDisplay.setVisibleBehindActivity(null); + setVisibleBehindActivity(null); } } @@ -3348,7 +3362,7 @@ final class ActivityStack { try { ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo( destIntent.getComponent(), 0, srec.userId); - int res = mStackSupervisor.startActivityLocked(srec.app.thread, destIntent, + int res = mService.mActivityStarter.startActivityLocked(srec.app.thread, destIntent, null /*ephemeralIntent*/, null, aInfo, null /*rInfo*/, null, null, parent.appToken, null, 0, -1, parent.launchedFromUid, parent.launchedFromPackage, -1, parent.launchedFromUid, 0, null, diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 80d531e71f3e..32671acfcc30 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -31,51 +31,37 @@ import static android.app.ActivityManager.StackId.HOME_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.ActivityManager.StackId.LAST_STATIC_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; -import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; -import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; -import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_TO_SIDE; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONTAINERS; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_IDLE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKSCREEN; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PAUSE; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RELEASE; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RESULTS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USER_LEAVING; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBLE_BEHIND; -import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONTAINERS; -import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IDLE; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PAUSE; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RELEASE; -import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RESULTS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STATES; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS; -import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_USER_LEAVING; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.am.ActivityManagerService.ANIMATE; import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG; import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; @@ -107,9 +93,6 @@ import android.app.IActivityContainer; import android.app.IActivityContainerCallback; import android.app.IActivityManager; import android.app.IActivityManager.WaitResult; -import android.app.IApplicationThread; -import android.app.KeyguardManager; -import android.app.PendingIntent; import android.app.ProfilerInfo; import android.app.ResultInfo; import android.app.StatusBarManager; @@ -118,7 +101,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.IIntentSender; import android.content.Intent; -import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -133,10 +115,7 @@ import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.VirtualDisplay; import android.hardware.input.InputManager; import android.hardware.input.InputManagerInternal; -import android.net.Uri; import android.os.Binder; -import android.os.Build; -import android.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.IBinder; @@ -168,8 +147,6 @@ import android.view.DisplayInfo; import android.view.InputEvent; import android.view.Surface; -import com.android.internal.app.HeavyWeightSwitcherActivity; -import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.ReferrerIntent; import com.android.internal.os.TransferPipe; import com.android.internal.statusbar.IStatusBarService; @@ -189,21 +166,17 @@ import java.util.Set; public final class ActivityStackSupervisor implements DisplayListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM; - private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; private static final String TAG_CONTAINERS = TAG + POSTFIX_CONTAINERS; - private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; private static final String TAG_IDLE = TAG + POSTFIX_IDLE; private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK; private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE; private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE; - private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS; private static final String TAG_STACK = TAG + POSTFIX_STACK; private static final String TAG_STATES = TAG + POSTFIX_STATES; private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; - private static final String TAG_TASKS = TAG + POSTFIX_TASKS; + static final String TAG_TASKS = TAG + POSTFIX_TASKS; private static final String TAG_VISIBLE_BEHIND = TAG + POSTFIX_VISIBLE_BEHIND; - private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING; /** How long we wait until giving up on the last activity telling us it is idle. */ static final int IDLE_TIMEOUT = 10 * 1000; @@ -235,7 +208,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // Used to indicate if an object (e.g. stack) that we are trying to get // should be created if it doesn't exist already. - private static final boolean CREATE_IF_NEEDED = true; + static final boolean CREATE_IF_NEEDED = true; // Used to indicate that windows of activities should be preserved during the resize. static final boolean PRESERVE_WINDOWS = true; @@ -308,14 +281,14 @@ public final class ActivityStackSupervisor implements DisplayListener { private int mCurTaskId = 0; /** The current user */ - private int mCurrentUser; + int mCurrentUser; /** The stack containing the launcher app. Assumed to always be attached to * Display.DEFAULT_DISPLAY. */ - private ActivityStack mHomeStack; + ActivityStack mHomeStack; /** The stack currently receiving input or launching the next activity. */ - private ActivityStack mFocusedStack; + ActivityStack mFocusedStack; /** If this is the same as mFocusedStack then the activity on the top of the focused stack has * been resumed. If stacks are changing position this will hold the old stack until the new @@ -397,8 +370,6 @@ public final class ActivityStackSupervisor implements DisplayListener { */ private LockTaskNotify mLockTaskNotify; - final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>(); - /** Used to keep resumeTopActivityLocked() from being entered recursively */ boolean inResumeTopActivity; @@ -418,6 +389,12 @@ public final class ActivityStackSupervisor implements DisplayListener { private final ActivityMetricsLogger mActivityMetricsLogger; + static class FindTaskResult { + ActivityRecord r; + boolean matchedByRootAffinity; + } + private final FindTaskResult mTmpFindTaskResult = new FindTaskResult(); + /** * Description of a request to start a new activity, which has been held * due to app switches being disabled. @@ -1011,270 +988,6 @@ public final class ActivityStackSupervisor implements DisplayListener { return resolveActivity(intent, rInfo, startFlags, profilerInfo); } - void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason) { - moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE, reason); - startActivityLocked(null /*caller*/, intent, null /*ephemeralIntent*/, - null /*resolvedType*/, aInfo, null /*rInfo*/, null /*voiceSession*/, - null /*voiceInteractor*/, null /*resultTo*/, null /*resultWho*/, - 0 /*requestCode*/, 0 /*callingPid*/, 0 /*callingUid*/, null /*callingPackage*/, - 0 /*realCallingPid*/, 0 /*realCallingUid*/, 0 /*startFlags*/, null /*options*/, - false /*ignoreTargetSecurity*/, false /*componentSpecified*/, null /*outActivity*/, - null /*container*/, null /*inTask*/); - if (inResumeTopActivity) { - // If we are in resume section already, home activity will be initialized, but not - // resumed (to avoid recursive resume) and will stay that way until something pokes it - // again. We need to schedule another resume. - scheduleResumeTopActivities(); - } - } - - final int startActivityMayWait(IApplicationThread caller, int callingUid, - String callingPackage, Intent intent, String resolvedType, - IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, - IBinder resultTo, String resultWho, int requestCode, int startFlags, - ProfilerInfo profilerInfo, WaitResult outResult, Configuration config, - Bundle bOptions, boolean ignoreTargetSecurity, int userId, - IActivityContainer iContainer, TaskRecord inTask) { - // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - boolean componentSpecified = intent.getComponent() != null; - - // Save a copy in case ephemeral needs it - final Intent ephemeralIntent = new Intent(intent); - // Don't modify the client's object! - intent = new Intent(intent); - - ResolveInfo rInfo = resolveIntent(intent, resolvedType, userId); - // Collect information about the target of the Intent. - ActivityInfo aInfo = resolveActivity(intent, rInfo, startFlags, profilerInfo); - - ActivityOptions options = ActivityOptions.fromBundle(bOptions); - ActivityContainer container = (ActivityContainer)iContainer; - synchronized (mService) { - if (container != null && container.mParentActivity != null && - container.mParentActivity.state != RESUMED) { - // Cannot start a child activity if the parent is not resumed. - return ActivityManager.START_CANCELED; - } - final int realCallingPid = Binder.getCallingPid(); - final int realCallingUid = Binder.getCallingUid(); - int callingPid; - if (callingUid >= 0) { - callingPid = -1; - } else if (caller == null) { - callingPid = realCallingPid; - callingUid = realCallingUid; - } else { - callingPid = callingUid = -1; - } - - final ActivityStack stack; - if (container == null || container.mStack.isOnHomeDisplay()) { - stack = mFocusedStack; - } else { - stack = container.mStack; - } - stack.mConfigWillChange = config != null && mService.mConfiguration.diff(config) != 0; - if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, - "Starting activity when config will change = " + stack.mConfigWillChange); - - final long origId = Binder.clearCallingIdentity(); - - if (aInfo != null && - (aInfo.applicationInfo.privateFlags - &ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { - // This may be a heavy-weight process! Check to see if we already - // have another, different heavy-weight process running. - if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) { - if (mService.mHeavyWeightProcess != null && - (mService.mHeavyWeightProcess.info.uid != aInfo.applicationInfo.uid || - !mService.mHeavyWeightProcess.processName.equals(aInfo.processName))) { - int appCallingUid = callingUid; - if (caller != null) { - ProcessRecord callerApp = mService.getRecordForAppLocked(caller); - if (callerApp != null) { - appCallingUid = callerApp.info.uid; - } else { - Slog.w(TAG, "Unable to find app for caller " + caller - + " (pid=" + callingPid + ") when starting: " - + intent.toString()); - ActivityOptions.abort(options); - return ActivityManager.START_PERMISSION_DENIED; - } - } - - IIntentSender target = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, "android", - appCallingUid, userId, null, null, 0, new Intent[] { intent }, - new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT - | PendingIntent.FLAG_ONE_SHOT, null); - - Intent newIntent = new Intent(); - if (requestCode >= 0) { - // Caller is requesting a result. - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true); - } - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, - new IntentSender(target)); - if (mService.mHeavyWeightProcess.activities.size() > 0) { - ActivityRecord hist = mService.mHeavyWeightProcess.activities.get(0); - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP, - hist.packageName); - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK, - hist.task.taskId); - } - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP, - aInfo.packageName); - newIntent.setFlags(intent.getFlags()); - newIntent.setClassName("android", - HeavyWeightSwitcherActivity.class.getName()); - intent = newIntent; - resolvedType = null; - caller = null; - callingUid = Binder.getCallingUid(); - callingPid = Binder.getCallingPid(); - componentSpecified = true; - rInfo = resolveIntent(intent, null /*resolvedType*/, userId); - aInfo = rInfo != null ? rInfo.activityInfo : null; - if (aInfo != null) { - aInfo = mService.getActivityInfoForUser(aInfo, userId); - } - } - } - } - - int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType, - aInfo, rInfo, voiceSession, voiceInteractor, - resultTo, resultWho, requestCode, callingPid, - callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, - options, ignoreTargetSecurity, componentSpecified, null, container, inTask); - - Binder.restoreCallingIdentity(origId); - - if (stack.mConfigWillChange) { - // If the caller also wants to switch to a new configuration, - // do so now. This allows a clean switch, as we are waiting - // for the current activity to pause (so we will not destroy - // it), and have not yet started the next activity. - mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, - "updateConfiguration()"); - stack.mConfigWillChange = false; - if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, - "Updating to new configuration after starting activity."); - mService.updateConfigurationLocked(config, null, false); - } - - if (outResult != null) { - outResult.result = res; - if (res == ActivityManager.START_SUCCESS) { - mWaitingActivityLaunched.add(outResult); - do { - try { - mService.wait(); - } catch (InterruptedException e) { - } - } while (!outResult.timeout && outResult.who == null); - } else if (res == ActivityManager.START_TASK_TO_FRONT) { - ActivityRecord r = stack.topRunningActivityLocked(); - if (r.nowVisible && r.state == RESUMED) { - outResult.timeout = false; - outResult.who = new ComponentName(r.info.packageName, r.info.name); - outResult.totalTime = 0; - outResult.thisTime = 0; - } else { - outResult.thisTime = SystemClock.uptimeMillis(); - mWaitingActivityVisible.add(outResult); - do { - try { - mService.wait(); - } catch (InterruptedException e) { - } - } while (!outResult.timeout && outResult.who == null); - } - } - } - - return res; - } - } - - final int startActivities(IApplicationThread caller, int callingUid, String callingPackage, - Intent[] intents, String[] resolvedTypes, IBinder resultTo, - Bundle bOptions, int userId) { - if (intents == null) { - throw new NullPointerException("intents is null"); - } - if (resolvedTypes == null) { - throw new NullPointerException("resolvedTypes is null"); - } - if (intents.length != resolvedTypes.length) { - throw new IllegalArgumentException("intents are length different than resolvedTypes"); - } - - - int callingPid; - if (callingUid >= 0) { - callingPid = -1; - } else if (caller == null) { - callingPid = Binder.getCallingPid(); - callingUid = Binder.getCallingUid(); - } else { - callingPid = callingUid = -1; - } - final long origId = Binder.clearCallingIdentity(); - try { - synchronized (mService) { - ActivityRecord[] outActivity = new ActivityRecord[1]; - for (int i=0; i<intents.length; i++) { - Intent intent = intents[i]; - if (intent == null) { - continue; - } - - // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - - boolean componentSpecified = intent.getComponent() != null; - - // Don't modify the client's object! - intent = new Intent(intent); - - // Collect information about the target of the Intent. - ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], 0, null, userId); - // TODO: New, check if this is correct - aInfo = mService.getActivityInfoForUser(aInfo, userId); - - if (aInfo != null && - (aInfo.applicationInfo.privateFlags - & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { - throw new IllegalArgumentException( - "FLAG_CANT_SAVE_STATE not supported here"); - } - - ActivityOptions options = ActivityOptions.fromBundle( - i == intents.length - 1 ? bOptions : null); - int res = startActivityLocked(caller, intent, null /*ephemeralIntent*/, - resolvedTypes[i], aInfo, null /*rInfo*/, null, null, resultTo, null, -1, - callingPid, callingUid, callingPackage, callingPid, callingUid, 0, - options, false, componentSpecified, outActivity, null, null); - if (res < 0) { - return res; - } - - resultTo = outActivity[0] != null ? outActivity[0].appToken : null; - } - } - } finally { - Binder.restoreCallingIdentity(origId); - } - - return ActivityManager.START_SUCCESS; - } - final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app, boolean andResume, boolean checkConfig) throws RemoteException { @@ -1494,348 +1207,7 @@ public final class ActivityStackSupervisor implements DisplayListener { "activity", r.intent.getComponent(), false, false, true); } - final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent, - String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo, - IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, - IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, - String callingPackage, int realCallingPid, int realCallingUid, int startFlags, - ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified, - ActivityRecord[] outActivity, ActivityContainer container, TaskRecord inTask) { - int err = ActivityManager.START_SUCCESS; - - ProcessRecord callerApp = null; - if (caller != null) { - callerApp = mService.getRecordForAppLocked(caller); - if (callerApp != null) { - callingPid = callerApp.pid; - callingUid = callerApp.info.uid; - } else { - Slog.w(TAG, "Unable to find app for caller " + caller - + " (pid=" + callingPid + ") when starting: " - + intent.toString()); - err = ActivityManager.START_PERMISSION_DENIED; - } - } - - final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; - - if (err == ActivityManager.START_SUCCESS) { - Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false) - + "} from uid " + callingUid - + " on display " + (container == null ? (mFocusedStack == null ? - Display.DEFAULT_DISPLAY : mFocusedStack.mDisplayId) : - (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY : - container.mActivityDisplay.mDisplayId))); - } - - ActivityRecord sourceRecord = null; - ActivityRecord resultRecord = null; - if (resultTo != null) { - sourceRecord = isInAnyStackLocked(resultTo); - if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, - "Will send result to " + resultTo + " " + sourceRecord); - if (sourceRecord != null) { - if (requestCode >= 0 && !sourceRecord.finishing) { - resultRecord = sourceRecord; - } - } - } - - final int launchFlags = intent.getFlags(); - - if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) { - // Transfer the result target from the source activity to the new - // one being started, including any failures. - if (requestCode >= 0) { - ActivityOptions.abort(options); - return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; - } - resultRecord = sourceRecord.resultTo; - if (resultRecord != null && !resultRecord.isInStackLocked()) { - resultRecord = null; - } - resultWho = sourceRecord.resultWho; - requestCode = sourceRecord.requestCode; - sourceRecord.resultTo = null; - if (resultRecord != null) { - resultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode); - } - if (sourceRecord.launchedFromUid == callingUid) { - // The new activity is being launched from the same uid as the previous - // activity in the flow, and asking to forward its result back to the - // previous. In this case the activity is serving as a trampoline between - // the two, so we also want to update its launchedFromPackage to be the - // same as the previous activity. Note that this is safe, since we know - // these two packages come from the same uid; the caller could just as - // well have supplied that same package name itself. This specifially - // deals with the case of an intent picker/chooser being launched in the app - // flow to redirect to an activity picked by the user, where we want the final - // activity to consider it to have been launched by the previous app activity. - callingPackage = sourceRecord.launchedFromPackage; - } - } - - if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) { - // We couldn't find a class that can handle the given Intent. - // That's the end of that! - err = ActivityManager.START_INTENT_NOT_RESOLVED; - } - - if (err == ActivityManager.START_SUCCESS && aInfo == null) { - // We couldn't find the specific class specified in the Intent. - // Also the end of the line. - err = ActivityManager.START_CLASS_NOT_FOUND; - } - - if (err == ActivityManager.START_SUCCESS && sourceRecord != null - && sourceRecord.task.voiceSession != null) { - // If this activity is being launched as part of a voice session, we need - // to ensure that it is safe to do so. If the upcoming activity will also - // be part of the voice session, we can only launch it if it has explicitly - // said it supports the VOICE category, or it is a part of the calling app. - if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 - && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) { - try { - intent.addCategory(Intent.CATEGORY_VOICE); - if (!AppGlobals.getPackageManager().activitySupportsIntent( - intent.getComponent(), intent, resolvedType)) { - Slog.w(TAG, - "Activity being started in current voice task does not support voice: " - + intent); - err = ActivityManager.START_NOT_VOICE_COMPATIBLE; - } - } catch (RemoteException e) { - Slog.w(TAG, "Failure checking voice capabilities", e); - err = ActivityManager.START_NOT_VOICE_COMPATIBLE; - } - } - } - - if (err == ActivityManager.START_SUCCESS && voiceSession != null) { - // If the caller is starting a new voice session, just make sure the target - // is actually allowing it to run this way. - try { - if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(), - intent, resolvedType)) { - Slog.w(TAG, - "Activity being started in new voice task does not support: " - + intent); - err = ActivityManager.START_NOT_VOICE_COMPATIBLE; - } - } catch (RemoteException e) { - Slog.w(TAG, "Failure checking voice capabilities", e); - err = ActivityManager.START_NOT_VOICE_COMPATIBLE; - } - } - - final ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack; - - if (err != ActivityManager.START_SUCCESS) { - if (resultRecord != null) { - resultStack.sendActivityResultLocked(-1, - resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); - } - ActivityOptions.abort(options); - return err; - } - - boolean abort = !checkStartAnyActivityPermission(intent, aInfo, resultWho, requestCode, - callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp, - resultRecord, resultStack); - abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid, - callingPid, resolvedType, aInfo.applicationInfo); - - if (mService.mController != null) { - try { - // The Intent we give to the watcher has the extra data - // stripped off, since it can contain private information. - Intent watchIntent = intent.cloneFilter(); - abort |= !mService.mController.activityStarting(watchIntent, - aInfo.applicationInfo.packageName); - } catch (RemoteException e) { - mService.mController = null; - } - } - - UserInfo user = getUserInfo(userId); - KeyguardManager km = (KeyguardManager) mService.mContext - .getSystemService(Context.KEYGUARD_SERVICE); - if (user.isManagedProfile() - && LockPatternUtils.isSeparateWorkChallengeEnabled() - && km.isDeviceLocked(userId)) { - IIntentSender target = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, - Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent }, - new String[]{ resolvedType }, - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_IMMUTABLE, null); - final int flags = intent.getFlags(); - final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, user.id); - if (newIntent != null) { - intent = newIntent; - intent.setFlags(flags - | Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); - intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); - - resolvedType = null; - callingUid = realCallingUid; - callingPid = realCallingPid; - - UserInfo parent = UserManager.get(mService.mContext).getProfileParent(userId); - rInfo = resolveIntent(intent, resolvedType, parent.id); - aInfo = resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); - } - } - - if (abort) { - if (resultRecord != null) { - resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); - } - // We pretend to the caller that it was really started, but - // they will just get a cancel result. - ActivityOptions.abort(options); - return ActivityManager.START_SUCCESS; - } - - // If permissions need a review before any of the app components can run, we - // launch the review activity and pass a pending intent to start the activity - // we are to launching now after the review is completed. - if (Build.PERMISSIONS_REVIEW_REQUIRED && aInfo != null) { - if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired( - aInfo.packageName, userId)) { - IIntentSender target = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, - callingUid, userId, null, null, 0, new Intent[]{intent}, - new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT - | PendingIntent.FLAG_ONE_SHOT, null); - - final int flags = intent.getFlags(); - Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); - newIntent.setFlags(flags - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); - newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); - if (resultRecord != null) { - newIntent.putExtra(Intent.EXTRA_RESULT_NEEDED, true); - } - intent = newIntent; - - resolvedType = null; - callingUid = realCallingUid; - callingPid = realCallingPid; - - rInfo = resolveIntent(intent, resolvedType, userId); - aInfo = resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); - - if (DEBUG_PERMISSIONS_REVIEW) { - Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, - true, false) + "} from uid " + callingUid + " on display " - + (container == null ? (mFocusedStack == null ? - Display.DEFAULT_DISPLAY : mFocusedStack.mDisplayId) : - (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY : - container.mActivityDisplay.mDisplayId))); - } - } - } - - // If we have an ephemeral app, abort the process of launching the resolved intent. - // Instead, launch the ephemeral installer. Once the installer is finished, it - // starts either the intent we resolved here [on install error] or the ephemeral - // app [on install success]. - if (rInfo != null && rInfo.ephemeralResolveInfo != null) { - // Create a pending intent to start the intent resolved here. - final IIntentSender failureTarget = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, - Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent }, - new String[]{ resolvedType }, - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_IMMUTABLE, null); - - // Create a pending intent to start the ephemeral application; force it to be - // directed to the ephemeral package. - ephemeralIntent.setPackage(rInfo.ephemeralResolveInfo.getPackageName()); - final IIntentSender ephemeralTarget = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, - Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ ephemeralIntent }, - new String[]{ resolvedType }, - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_IMMUTABLE, null); - - int flags = intent.getFlags(); - intent = new Intent(); - intent.setFlags(flags - | Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, - rInfo.ephemeralResolveInfo.getPackageName()); - intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, new IntentSender(failureTarget)); - intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, new IntentSender(ephemeralTarget)); - - resolvedType = null; - callingUid = realCallingUid; - callingPid = realCallingPid; - - rInfo = rInfo.ephemeralInstaller; - aInfo = resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); - } - - ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage, - intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, - requestCode, componentSpecified, voiceSession != null, this, container, options); - if (outActivity != null) { - outActivity[0] = r; - } - - if (r.appTimeTracker == null && sourceRecord != null) { - // If the caller didn't specify an explicit time tracker, we want to continue - // tracking under any it has. - r.appTimeTracker = sourceRecord.appTimeTracker; - } - - final ActivityStack stack = mFocusedStack; - if (voiceSession == null && (stack.mResumedActivity == null - || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) { - if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, - realCallingPid, realCallingUid, "Activity start")) { - PendingActivityLaunch pal = new PendingActivityLaunch(r, - sourceRecord, startFlags, stack, callerApp); - mPendingActivityLaunches.add(pal); - ActivityOptions.abort(options); - return ActivityManager.START_SWITCHES_CANCELED; - } - } - - if (mService.mDidAppSwitch) { - // This is the second allowed switch since we stopped switches, - // so now just generally allow switches. Use case: user presses - // home (switches disabled, switch to home, mDidAppSwitch now true); - // user taps a home icon (coming from home so allowed, we hit here - // and now allow anyone to switch again). - mService.mAppSwitchesAllowedTime = 0; - } else { - mService.mDidAppSwitch = true; - } - - doPendingActivityLaunchesLocked(false); - - err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor, - startFlags, true, options, inTask); - - if (err < 0) { - // If someone asked to have the keyguard dismissed on the next - // activity start, but we are not actually doing an activity - // switch... just dismiss the keyguard now, because we - // probably want to see whatever is behind it. - notifyActivityDrawnForKeyguard(); - } - return err; - } - - private boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo, + boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo, String resultWho, int requestCode, int callingPid, int callingUid, String callingPackage, boolean ignoreTargetSecurity, ProcessRecord callerApp, ActivityRecord resultRecord, ActivityStack resultStack) { @@ -1895,7 +1267,7 @@ public final class ActivityStackSupervisor implements DisplayListener { return true; } - private UserInfo getUserInfo(int userId) { + UserInfo getUserInfo(int userId) { final long identity = Binder.clearCallingIdentity(); try { return UserManager.get(mService.mContext).getUserInfo(userId); @@ -1973,104 +1345,6 @@ public final class ActivityStackSupervisor implements DisplayListener { return ACTIVITY_RESTRICTION_NONE; } - private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds, - int launchFlags) { - final TaskRecord task = r.task; - if (!(r.isApplicationActivity() || (task != null && task.isApplicationTask()))) { - return mHomeStack; - } - - ActivityStack stack = getLaunchToSideStack(r, launchFlags, task); - if (stack != null) { - return stack; - } - - if (task != null && task.stack != null) { - stack = task.stack; - if (stack.isOnHomeDisplay()) { - if (mFocusedStack != stack) { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, - "computeStackFocus: Setting " + "focused stack to r=" + r - + " task=" + task); - } else { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, - "computeStackFocus: Focused stack already=" + mFocusedStack); - } - } - return stack; - } - - final ActivityContainer container = r.mInitialActivityContainer; - if (container != null) { - // The first time put it on the desired stack, after this put on task stack. - r.mInitialActivityContainer = null; - return container.mStack; - } - - // The fullscreen stack can contain any task regardless of if the task is resizeable - // or not. So, we let the task go in the fullscreen task if it is the focus stack. - // If the freeform or docked stack has focus, and the activity to be launched is resizeable, - // we can also put it in the focused stack. - final int focusedStackId = mFocusedStack.mStackId; - final boolean canUseFocusedStack = - focusedStackId == FULLSCREEN_WORKSPACE_STACK_ID - || focusedStackId == DOCKED_STACK_ID - || (focusedStackId == FREEFORM_WORKSPACE_STACK_ID && r.info.resizeable); - if (canUseFocusedStack - && (!newTask || mFocusedStack.mActivityContainer.isEligibleForNewTasks())) { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, - "computeStackFocus: Have a focused stack=" + mFocusedStack); - return mFocusedStack; - } - - // We first try to put the task in the first dynamic stack. - final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks; - for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) { - stack = homeDisplayStacks.get(stackNdx); - if (!StackId.isStaticStack(stack.mStackId)) { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, - "computeStackFocus: Setting focused stack=" + stack); - return stack; - } - } - - // If there is no suitable dynamic stack then we figure out which static stack to use. - final int stackId = task != null ? task.getLaunchStackId() : - bounds != null ? FREEFORM_WORKSPACE_STACK_ID : - FULLSCREEN_WORKSPACE_STACK_ID; - stack = getStack(stackId, CREATE_IF_NEEDED, ON_TOP); - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r=" - + r + " stackId=" + stack.mStackId); - return stack; - } - - private ActivityStack getLaunchToSideStack(ActivityRecord r, int launchFlags, TaskRecord task) { - if ((launchFlags & FLAG_ACTIVITY_LAUNCH_TO_SIDE) == 0) { - return null; - } - // The parent activity doesn't want to launch the activity on top of itself, but - // instead tries to put it onto other side in side-by-side mode. - final ActivityStack parentStack = task != null ? task.stack - : r.mInitialActivityContainer != null ? r.mInitialActivityContainer.mStack - : mFocusedStack; - if (parentStack != null && parentStack.mStackId == DOCKED_STACK_ID) { - // If parent was in docked stack, the natural place to launch another activity - // will be fullscreen, so it can appear alongside the docked window. - return getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED, ON_TOP); - } else { - // If the parent is not in the docked stack, we check if there is docked window - // and if yes, we will launch into that stack. If not, we just put the new - // activity into parent's stack, because we can't find a better place. - final ActivityStack stack = getStack(DOCKED_STACK_ID); - if (stack != null && !stack.isStackVisibleLocked()) { - // There is a docked stack, but it isn't visible, so we can't launch into that. - return null; - } else { - return stack; - } - } - } - boolean setFocusedStack(ActivityRecord r, String reason) { if (r == null) { // Not sure what you are trying to do, but it is not going to work... @@ -2085,734 +1359,14 @@ public final class ActivityStackSupervisor implements DisplayListener { return true; } - final int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord, - IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, - boolean doResume, ActivityOptions options, TaskRecord inTask) { - final Intent intent = r.intent; - final int callingUid = r.launchedFromUid; - - boolean overrideBounds = false; + Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) { Rect newBounds = null; if (options != null && (r.info.resizeable || (inTask != null && inTask.mResizeable))) { if (canUseActivityOptionsLaunchBounds(options)) { - overrideBounds = true; newBounds = options.getLaunchBounds(); } } - - // In some flows in to this function, we retrieve the task record and hold on to it - // without a lock before calling back in to here... so the task at this point may - // not actually be in recents. Check for that, and if it isn't in recents just - // consider it invalid. - if (inTask != null && !inTask.inRecents) { - Slog.w(TAG, "Starting activity in task not in recents: " + inTask); - inTask = null; - } - - final boolean launchSingleTop = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP; - final boolean launchSingleInstance = r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE; - final boolean launchSingleTask = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK; - - int launchFlags = intent.getFlags(); - if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && - (launchSingleInstance || launchSingleTask)) { - // We have a conflict between the Intent and the Activity manifest, manifest wins. - Slog.i(TAG, "Ignoring FLAG_ACTIVITY_NEW_DOCUMENT, launchMode is " + - "\"singleInstance\" or \"singleTask\""); - launchFlags &= - ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK); - } else { - switch (r.info.documentLaunchMode) { - case ActivityInfo.DOCUMENT_LAUNCH_NONE: - break; - case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING: - launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT; - break; - case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS: - launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT; - break; - case ActivityInfo.DOCUMENT_LAUNCH_NEVER: - launchFlags &= ~FLAG_ACTIVITY_MULTIPLE_TASK; - break; - } - } - - final boolean launchTaskBehind = r.mLaunchTaskBehind - && !launchSingleTask && !launchSingleInstance - && (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0; - - if (r.resultTo != null && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 - && r.resultTo.task.stack != null) { - // For whatever reason this activity is being launched into a new - // task... yet the caller has requested a result back. Well, that - // is pretty messed up, so instead immediately send back a cancel - // and let the new task continue launched as normal without a - // dependency on its originator. - Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); - r.resultTo.task.stack.sendActivityResultLocked(-1, - r.resultTo, r.resultWho, r.requestCode, - Activity.RESULT_CANCELED, null); - r.resultTo = null; - } - - if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && r.resultTo == null) { - launchFlags |= FLAG_ACTIVITY_NEW_TASK; - } - - // If we are actually going to launch in to a new task, there are some cases where - // we further want to do multiple task. - if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { - if (launchTaskBehind - || r.info.documentLaunchMode == ActivityInfo.DOCUMENT_LAUNCH_ALWAYS) { - launchFlags |= FLAG_ACTIVITY_MULTIPLE_TASK; - } - } - - // We'll invoke onUserLeaving before onPause only if the launching - // activity did not explicitly state that this is an automated launch. - mUserLeaving = (launchFlags & Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; - if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING, - "startActivity() => mUserLeaving=" + mUserLeaving); - - // If the caller has asked not to resume at this point, we make note - // of this in the record so that we can skip it when trying to find - // the top running activity. - if (!doResume || !okToShowLocked(r)) { - r.delayedResume = true; - doResume = false; - } - - ActivityRecord notTop = - (launchFlags & Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null; - - // If the onlyIfNeeded flag is set, then we can do this if the activity - // being launched is the same as the one making the call... or, as - // a special case, if we do not know the caller then we count the - // current top activity as the caller. - if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { - ActivityRecord checkedCaller = sourceRecord; - if (checkedCaller == null) { - checkedCaller = mFocusedStack.topRunningNonDelayedActivityLocked(notTop); - } - if (!checkedCaller.realActivity.equals(r.realActivity)) { - // Caller is not the same as launcher, so always needed. - startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED; - } - } - - boolean addingToTask = false; - TaskRecord reuseTask = null; - - // If the caller is not coming from another activity, but has given us an - // explicit task into which they would like us to launch the new activity, - // then let's see about doing that. - if (sourceRecord == null && inTask != null && inTask.stack != null) { - final Intent baseIntent = inTask.getBaseIntent(); - final ActivityRecord root = inTask.getRootActivity(); - if (baseIntent == null) { - ActivityOptions.abort(options); - throw new IllegalArgumentException("Launching into task without base intent: " - + inTask); - } - - // If this task is empty, then we are adding the first activity -- it - // determines the root, and must be launching as a NEW_TASK. - if (launchSingleInstance || launchSingleTask) { - if (!baseIntent.getComponent().equals(r.intent.getComponent())) { - ActivityOptions.abort(options); - throw new IllegalArgumentException("Trying to launch singleInstance/Task " - + r + " into different task " + inTask); - } - if (root != null) { - ActivityOptions.abort(options); - throw new IllegalArgumentException("Caller with inTask " + inTask - + " has root " + root + " but target is singleInstance/Task"); - } - } - - // If task is empty, then adopt the interesting intent launch flags in to the - // activity being started. - if (root == null) { - final int flagsOfInterest = FLAG_ACTIVITY_NEW_TASK - | FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT - | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; - launchFlags = (launchFlags&~flagsOfInterest) - | (baseIntent.getFlags()&flagsOfInterest); - intent.setFlags(launchFlags); - inTask.setIntent(r); - addingToTask = true; - - // If the task is not empty and the caller is asking to start it as the root - // of a new task, then we don't actually want to start this on the task. We - // will bring the task to the front, and possibly give it a new intent. - } else if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { - addingToTask = false; - - } else { - addingToTask = true; - } - - reuseTask = inTask; - } else { - inTask = null; - // Launch ResolverActivity in the source task, so that it stays in the task - // bounds when in freeform workspace. - // Also put noDisplay activities in the source task. These by itself can - // be placed in any task/stack, however it could launch other activities - // like ResolverActivity, and we want those to stay in the original task. - if (r.isResolverActivity() || r.noDisplay) { - addingToTask = true; - } - } - - if (inTask == null) { - if (sourceRecord == null) { - // This activity is not being started from another... in this - // case we -always- start a new task. - if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) { - Slog.w(TAG, "startActivity called from non-Activity context; forcing " + - "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); - launchFlags |= FLAG_ACTIVITY_NEW_TASK; - } - } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { - // The original activity who is starting us is running as a single - // instance... this new activity it is starting must go on its - // own task. - launchFlags |= FLAG_ACTIVITY_NEW_TASK; - } else if (launchSingleInstance || launchSingleTask) { - // The activity being started is a single instance... it always - // gets launched into its own task. - launchFlags |= FLAG_ACTIVITY_NEW_TASK; - } - } - - ActivityInfo newTaskInfo = null; - Intent newTaskIntent = null; - final ActivityStack sourceStack; - if (sourceRecord != null) { - if (sourceRecord.finishing) { - // If the source is finishing, we can't further count it as our source. This - // is because the task it is associated with may now be empty and on its way out, - // so we don't want to blindly throw it in to that task. Instead we will take - // the NEW_TASK flow and try to find a task for it. But save the task information - // so it can be used when creating the new task. - if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0) { - Slog.w(TAG, "startActivity called from finishing " + sourceRecord - + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); - launchFlags |= FLAG_ACTIVITY_NEW_TASK; - newTaskInfo = sourceRecord.info; - newTaskIntent = sourceRecord.task.intent; - } - sourceRecord = null; - sourceStack = null; - } else { - sourceStack = sourceRecord.task.stack; - } - } else { - sourceStack = null; - } - - boolean movedHome = false; - ActivityStack targetStack; - - intent.setFlags(launchFlags); - final boolean noAnimation = (launchFlags & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0; - - ActivityRecord intentActivity = getReusableIntentActivity(r, inTask, intent, - launchSingleInstance, launchSingleTask, launchFlags); - if (intentActivity != null) { - // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused - // but still needs to be a lock task mode violation since the task gets - // cleared out and the device would otherwise leave the locked task. - if (isLockTaskModeViolation(intentActivity.task, - (launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) - == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) { - showLockTaskToast(); - Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode"); - return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; - } - if (r.task == null) { - r.task = intentActivity.task; - } - if (intentActivity.task.intent == null) { - // This task was started because of movement of the activity based on affinity... - // Now that we are actually launching it, we can assign the base intent. - intentActivity.task.setIntent(r); - } - - targetStack = intentActivity.task.stack; - targetStack.mLastPausedActivity = null; - // If the target task is not in the front, then we need - // to bring it to the front... except... well, with - // SINGLE_TASK_LAUNCH it's not entirely clear. We'd like - // to have the same behavior as if a new instance was - // being started, which means not bringing it to the front - // if the caller is not itself in the front. - final ActivityStack focusStack = getFocusedStack(); - ActivityRecord curTop = (focusStack == null) - ? null : focusStack.topRunningNonDelayedActivityLocked(notTop); - boolean movedToFront = false; - if (curTop != null && (curTop.task != intentActivity.task || - curTop.task != focusStack.topTask())) { - r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); - if (sourceRecord == null || (sourceStack.topActivity() != null && - sourceStack.topActivity().task == sourceRecord.task)) { - // We really do want to push this one into the user's face, right now. - if (launchTaskBehind && sourceRecord != null) { - intentActivity.setTaskToAffiliateWith(sourceRecord.task); - } - movedHome = true; - final ActivityStack sideStack = getLaunchToSideStack(r, launchFlags, r.task); - if (sideStack == null || sideStack == targetStack) { - // We only want to move to the front, if we aren't going to launch on a - // different stack. If we launch on a different stack, we will put the - // task on top there. - targetStack.moveTaskToFrontLocked(intentActivity.task, noAnimation, - options, r.appTimeTracker, "bringingFoundTaskToFront"); - movedToFront = true; - } - if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) - == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { - // Caller wants to appear on home activity. - intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); - } - options = null; - } - } - if (!movedToFront && doResume) { - if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + targetStack - + " from " + intentActivity); - targetStack.moveToFront("intentActivityFound"); - } - - // If the caller has requested that the target task be - // reset, then do so. - if ((launchFlags & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { - intentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r); - } - if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { - // We don't need to start a new activity, and - // the client said not to do anything if that - // is the case, so this is it! And for paranoia, make - // sure we have correctly resumed the top activity. - if (doResume) { - resumeTopActivitiesLocked(targetStack, null, options); - - // Make sure to notify Keyguard as well if we are not running an app - // transition later. - if (!movedToFront) { - notifyActivityDrawnForKeyguard(); - } - } else { - ActivityOptions.abort(options); - } - updateUserStackLocked(r.userId, targetStack); - return ActivityManager.START_RETURN_INTENT_TO_CALLER; - } - if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) - == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) { - // The caller has requested to completely replace any - // existing task with its new activity. Well that should - // not be too hard... - reuseTask = intentActivity.task; - reuseTask.performClearTaskLocked(); - reuseTask.setIntent(r); - } else if ((launchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0 - || launchSingleInstance || launchSingleTask) { - // In this situation we want to remove all activities - // from the task up to the one being started. In most - // cases this means we are resetting the task to its - // initial state. - ActivityRecord top = intentActivity.task.performClearTaskLocked(r, launchFlags); - if (top != null) { - if (top.frontOfTask) { - // Activity aliases may mean we use different - // intents for the top activity, so make sure - // the task now has the identity of the new - // intent. - top.task.setIntent(r); - } - ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); - } else { - // A special case: we need to start the activity because it is not - // currently running, and the caller has asked to clear the current - // task to have this activity at the top. - addingToTask = true; - // Now pretend like this activity is being started by the top of its - // task, so it is put in the right place. - sourceRecord = intentActivity; - TaskRecord task = sourceRecord.task; - if (task != null && task.stack == null) { - // Target stack got cleared when we all activities were removed - // above. Go ahead and reset it. - targetStack = computeStackFocus( - sourceRecord, false /* newTask */, null /* bounds */, launchFlags); - targetStack.addTask(task, - !launchTaskBehind /* toTop */, "startActivityUnchecked"); - } - - } - } else if (r.realActivity.equals(intentActivity.task.realActivity)) { - // In this case the top activity on the task is the - // same as the one being launched, so we take that - // as a request to bring the task to the foreground. - // If the top activity in the task is the root - // activity, deliver this new intent to it if it - // desires. - if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 || launchSingleTop) - && intentActivity.realActivity.equals(r.realActivity)) { - ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, - intentActivity.task); - if (intentActivity.frontOfTask) { - intentActivity.task.setIntent(r); - } - intentActivity.deliverNewIntentLocked(callingUid, r.intent, - r.launchedFromPackage); - } else if (!r.intent.filterEquals(intentActivity.task.intent)) { - // In this case we are launching the root activity - // of the task, but with a different intent. We - // should start a new instance on top. - addingToTask = true; - sourceRecord = intentActivity; - } - } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) { - // In this case an activity is being launched in to an - // existing task, without resetting that task. This - // is typically the situation of launching an activity - // from a notification or shortcut. We want to place - // the new activity on top of the current task. - addingToTask = true; - sourceRecord = intentActivity; - } else if (!intentActivity.task.rootWasReset) { - // In this case we are launching in to an existing task - // that has not yet been started from its front door. - // The current task has been brought to the front. - // Ideally, we'd probably like to place this new task - // at the bottom of its stack, but that's a little hard - // to do with the current organization of the code so - // for now we'll just drop it. - intentActivity.task.setIntent(r); - } - if (!addingToTask && reuseTask == null) { - // We didn't do anything... but it was needed (a.k.a., client - // don't use that intent!) And for paranoia, make - // sure we have correctly resumed the top activity. - if (doResume) { - targetStack.resumeTopActivityLocked(null, options); - if (!movedToFront) { - // Make sure to notify Keyguard as well if we are not running an app - // transition later. - notifyActivityDrawnForKeyguard(); - } - } else { - ActivityOptions.abort(options); - } - updateUserStackLocked(r.userId, targetStack); - return ActivityManager.START_TASK_TO_FRONT; - } - } - - //String uri = r.intent.toURI(); - //Intent intent2 = new Intent(uri); - //Slog.i(TAG, "Given intent: " + r.intent); - //Slog.i(TAG, "URI is: " + uri); - //Slog.i(TAG, "To intent: " + intent2); - - if (r.packageName != null) { - // If the activity being launched is the same as the one currently - // at the top, then we need to check if it should only be launched - // once. - ActivityStack topStack = mFocusedStack; - ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop); - final boolean dontStart = top != null && r.resultTo == null - && top.realActivity.equals(r.realActivity) && top.userId == r.userId - && top.app != null && top.app.thread != null - && ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 - || launchSingleTop || launchSingleTask); - if (dontStart) { - ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); - // For paranoia, make sure we have correctly resumed the top activity. - topStack.mLastPausedActivity = null; - if (doResume) { - resumeTopActivitiesLocked(); - } - ActivityOptions.abort(options); - if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { - // We don't need to start a new activity, and the client said not to do - // anything if that is the case, so this is it! - return ActivityManager.START_RETURN_INTENT_TO_CALLER; - } - top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); - return ActivityManager.START_DELIVERED_TO_TOP; - } - } else { - if (r.resultTo != null && r.resultTo.task.stack != null) { - r.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho, - r.requestCode, Activity.RESULT_CANCELED, null); - } - ActivityOptions.abort(options); - return ActivityManager.START_CLASS_NOT_FOUND; - } - - boolean newTask = false; - boolean keepCurTransition = false; - - TaskRecord taskToAffiliate = launchTaskBehind && sourceRecord != null ? - sourceRecord.task : null; - - // Should this be considered a new task? - if (r.resultTo == null && inTask == null && !addingToTask - && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { - newTask = true; - targetStack = computeStackFocus(r, newTask, newBounds, launchFlags); - if (doResume) { - targetStack.moveToFront("startingNewTask"); - } - - if (reuseTask == null) { - r.setTask(targetStack.createTaskRecord(getNextTaskId(), - newTaskInfo != null ? newTaskInfo : r.info, - newTaskIntent != null ? newTaskIntent : intent, - voiceSession, voiceInteractor, !launchTaskBehind /* toTop */), - taskToAffiliate); - if (overrideBounds) { - r.task.updateOverrideConfiguration(newBounds); - } - if (DEBUG_TASKS) Slog.v(TAG_TASKS, - "Starting new activity " + r + " in new task " + r.task); - } else { - r.setTask(reuseTask, taskToAffiliate); - } - if (isLockTaskModeViolation(r.task)) { - Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); - return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; - } - if (!movedHome) { - if ((launchFlags & - (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) - == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { - // Caller wants to appear on home activity, so before starting - // their own activity we will bring home to the front. - r.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); - } - } - } else if (sourceRecord != null) { - final TaskRecord sourceTask = sourceRecord.task; - if (isLockTaskModeViolation(sourceTask)) { - Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); - return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; - } - targetStack = null; - if (sourceTask.stack.topTask() != sourceTask) { - // We only want to allow changing stack if the target task is not the top one, - // otherwise we would move the launching task to the other side, rather than show - // two side by side. - targetStack = getLaunchToSideStack(r, launchFlags, r.task); - } - if (targetStack == null) { - targetStack = sourceTask.stack; - } else if (targetStack != sourceTask.stack) { - moveTaskToStackLocked(sourceTask.taskId, targetStack.mStackId, ON_TOP, - FORCE_FOCUS, "launchToSide", !ANIMATE); - } - if (doResume) { - targetStack.moveToFront("sourceStackToFront"); - } - final TaskRecord topTask = targetStack.topTask(); - if (topTask != sourceTask) { - targetStack.moveTaskToFrontLocked(sourceTask, noAnimation, options, - r.appTimeTracker, "sourceTaskToFront"); - } - if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { - // In this case, we are adding the activity to an existing - // task, but the caller has asked to clear that task if the - // activity is already running. - ActivityRecord top = sourceTask.performClearTaskLocked(r, launchFlags); - keepCurTransition = true; - if (top != null) { - ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); - // For paranoia, make sure we have correctly - // resumed the top activity. - targetStack.mLastPausedActivity = null; - if (doResume) { - targetStack.resumeTopActivityLocked(null); - } - ActivityOptions.abort(options); - return ActivityManager.START_DELIVERED_TO_TOP; - } - } else if (!addingToTask && - (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) { - // In this case, we are launching an activity in our own task - // that may already be running somewhere in the history, and - // we want to shuffle it to the front of the stack if so. - final ActivityRecord top = sourceTask.findActivityInHistoryLocked(r); - if (top != null) { - final TaskRecord task = top.task; - task.moveActivityToFrontLocked(top); - ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, task); - top.updateOptionsLocked(options); - top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); - targetStack.mLastPausedActivity = null; - if (doResume) { - targetStack.resumeTopActivityLocked(null); - } - return ActivityManager.START_DELIVERED_TO_TOP; - } - } - // An existing activity is starting this new activity, so we want - // to keep the new one in the same task as the one that is starting - // it. - r.setTask(sourceTask, null); - if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r - + " in existing task " + r.task + " from source " + sourceRecord); - - } else if (inTask != null) { - // The caller is asking that the new activity be started in an explicit - // task it has provided to us. - if (isLockTaskModeViolation(inTask)) { - Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); - return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; - } - if (overrideBounds) { - inTask.updateOverrideConfiguration(newBounds); - int stackId = inTask.getLaunchStackId(); - if (stackId != inTask.stack.mStackId) { - moveTaskToStackUncheckedLocked( - inTask, stackId, ON_TOP, !FORCE_FOCUS, "inTaskToFront"); - } - } - targetStack = inTask.stack; - targetStack.moveTaskToFrontLocked(inTask, noAnimation, options, r.appTimeTracker, - "inTaskToFront"); - - // Check whether we should actually launch the new activity in to the task, - // or just reuse the current activity on top. - ActivityRecord top = inTask.getTopActivity(); - if (top != null && top.realActivity.equals(r.realActivity) && top.userId == r.userId) { - if ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 - || launchSingleTop || launchSingleTask) { - ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); - if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { - // We don't need to start a new activity, and - // the client said not to do anything if that - // is the case, so this is it! - return ActivityManager.START_RETURN_INTENT_TO_CALLER; - } - top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); - return ActivityManager.START_DELIVERED_TO_TOP; - } - } - - if (!addingToTask) { - // We don't actually want to have this activity added to the task, so just - // stop here but still tell the caller that we consumed the intent. - ActivityOptions.abort(options); - return ActivityManager.START_TASK_TO_FRONT; - } - - r.setTask(inTask, null); - if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r - + " in explicit task " + r.task); - - } else { - // This not being started from an existing activity, and not part - // of a new task... just put it in the top task, though these days - // this case should never happen. - targetStack = computeStackFocus(r, newTask, null /* bounds */, launchFlags); - if (doResume) { - targetStack.moveToFront("addingToTopTask"); - } - ActivityRecord prev = targetStack.topActivity(); - r.setTask(prev != null ? prev.task : targetStack.createTaskRecord(getNextTaskId(), - r.info, intent, null, null, true), null); - mWindowManager.moveTaskToTop(r.task.taskId); - if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r - + " in new guessed " + r.task); - } - - mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, - intent, r.getUriPermissionsLocked(), r.userId); - - if (sourceRecord != null && sourceRecord.isRecentsActivity()) { - r.task.setTaskToReturnTo(RECENTS_ACTIVITY_TYPE); - } - if (newTask) { - EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId); - } - ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); - targetStack.mLastPausedActivity = null; - targetStack.startActivityLocked(r, newTask, keepCurTransition, options); - if (doResume) { - if (!launchTaskBehind) { - mService.setFocusedActivityLocked(r, "startedActivity"); - } - resumeTopActivitiesLocked(targetStack, r, options); - } else { - targetStack.addRecentActivityLocked(r); - } - updateUserStackLocked(r.userId, targetStack); - - if (!r.task.mResizeable && isStackDockedInEffect(targetStack.mStackId)) { - showNonResizeableDockToast(r.task.taskId); - } - - return ActivityManager.START_SUCCESS; - } - - /** - * Decide whether the new activity should be inserted into an existing task. Returns null if not - * or an ActivityRecord with the task into which the new activity should be added. - */ - private ActivityRecord getReusableIntentActivity(ActivityRecord r, TaskRecord inTask, Intent intent, - boolean launchSingleInstance, boolean launchSingleTask, int launchFlags) { - // We may want to try to place the new activity in to an existing task. We always - // do this if the target activity is singleTask or singleInstance; we will also do - // this if NEW_TASK has been requested, and there is not an additional qualifier telling - // us to still place it in a new task: multi task, always doc mode, or being asked to - // launch this as a new task behind the current one. - boolean putIntoExistingTask = ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 && - (launchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0) - || launchSingleInstance || launchSingleTask; - // If bring to front is requested, and no result is requested and we have not - // been given an explicit task to launch in to, and - // we can find a task that was started with this same - // component, then instead of launching bring that one to the front. - putIntoExistingTask &= inTask == null && r.resultTo == null; - ActivityRecord intentActivity = null; - if (putIntoExistingTask) { - // See if there is a task to bring to the front. If this is - // a SINGLE_INSTANCE activity, there can be one and only one - // instance of it in the history, and it is always in its own - // unique task, so we do a special search. - intentActivity = launchSingleInstance ? - findActivityLocked(intent, r.info) : findTaskLocked(r); - } - return intentActivity; - } - - final void doPendingActivityLaunchesLocked(boolean doResume) { - while (!mPendingActivityLaunches.isEmpty()) { - PendingActivityLaunch pal = mPendingActivityLaunches.remove(0); - - try { - startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags, - doResume && mPendingActivityLaunches.isEmpty(), null, null); - } catch (Exception e) { - Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e); - pal.sendErrorResult(e.getMessage()); - } - } - } - - void removePendingActivityLaunchesLocked(ActivityStack stack) { - for (int palNdx = mPendingActivityLaunches.size() - 1; palNdx >= 0; --palNdx) { - PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx); - if (pal.stack == stack) { - mPendingActivityLaunches.remove(palNdx); - } - } + return newBounds; } void setLaunchSource(int uid) { @@ -3640,7 +2194,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } if (bounds != null) { - resizeStackLocked(PINNED_STACK_ID, bounds, !PRESERVE_WINDOWS, true); + resizeStackLocked(stackId, bounds, !PRESERVE_WINDOWS, true); } // The task might have already been running and its visibility needs to be synchronized with @@ -3669,6 +2223,8 @@ public final class ActivityStackSupervisor implements DisplayListener { } ActivityRecord findTaskLocked(ActivityRecord r) { + mTmpFindTaskResult.r = null; + mTmpFindTaskResult.matchedByRootAffinity = false; if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r); for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; @@ -3683,14 +2239,18 @@ public final class ActivityStackSupervisor implements DisplayListener { "Skipping stack: (new task not allowed) " + stack); continue; } - final ActivityRecord ar = stack.findTaskLocked(r); - if (ar != null) { - return ar; + stack.findTaskLocked(r, mTmpFindTaskResult); + // It is possible to have task in multiple stacks with the same root affinity. + // If the match we found was based on root affinity we keep on looking to see if + // there is a better match in another stack. We eventually return the match based + // on root affinity if we don't find a better match. + if (mTmpFindTaskResult.r != null && !mTmpFindTaskResult.matchedByRootAffinity) { + return mTmpFindTaskResult.r; } } } - if (DEBUG_TASKS) Slog.d(TAG_TASKS, "No task found"); - return null; + if (DEBUG_TASKS && mTmpFindTaskResult.r == null) Slog.d(TAG_TASKS, "No task found"); + return mTmpFindTaskResult.r; } ActivityRecord findActivityLocked(Intent intent, ActivityInfo info) { @@ -4538,7 +3098,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - private void showNonResizeableDockToast(int taskId) { + void showNonResizeableDockToast(int taskId) { mWindowManager.scheduleShowNonResizeableDockToast(taskId); } @@ -4955,7 +3515,7 @@ public final class ActivityStackSupervisor implements DisplayListener { long origId = Binder.clearCallingIdentity(); try { mStack.finishAllActivitiesLocked(false); - removePendingActivityLaunchesLocked(mStack); + mService.mActivityStarter.removePendingActivityLaunchesLocked(mStack); } finally { Binder.restoreCallingIdentity(origId); } @@ -4974,22 +3534,7 @@ public final class ActivityStackSupervisor implements DisplayListener { @Override public final int startActivity(Intent intent) { - mService.enforceNotIsolatedCaller("ActivityContainer.startActivity"); - final int userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(), - Binder.getCallingUid(), mCurrentUser, false, - ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null); - - // TODO: Switch to user app stacks here. - String mimeType = intent.getType(); - final Uri data = intent.getData(); - if (mimeType == null && data != null && "content".equals(data.getScheme())) { - mimeType = mService.getProviderMimeType(data, userId); - } - checkEmbeddedAllowedInner(userId, intent, mimeType); - - intent.addFlags(FORCE_NEW_TASK_FLAGS); - return startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, null, - 0, 0, null, null, null, null, false, userId, this, null); + return mService.startActivity(intent, this); } @Override @@ -5013,7 +3558,7 @@ public final class ActivityStackSupervisor implements DisplayListener { FORCE_NEW_TASK_FLAGS, FORCE_NEW_TASK_FLAGS, null, this); } - private void checkEmbeddedAllowedInner(int userId, Intent intent, String resolvedType) { + void checkEmbeddedAllowedInner(int userId, Intent intent, String resolvedType) { ActivityInfo aInfo = resolveActivity(intent, resolvedType, 0, null, userId); if (aInfo != null && (aInfo.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) { throw new SecurityException( @@ -5170,7 +3715,7 @@ public final class ActivityStackSupervisor implements DisplayListener { /** All of the stacks on this display. Order matters, topmost stack is in front of all other * stacks, bottommost behind. Accessed directly by ActivityManager package classes */ - final ArrayList<ActivityStack> mStacks = new ArrayList<ActivityStack>(); + final ArrayList<ActivityStack> mStacks = new ArrayList<>(); ActivityRecord mVisibleBehindActivity; diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java new file mode 100644 index 000000000000..b16e160d9505 --- /dev/null +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -0,0 +1,1545 @@ +package com.android.server.am; + +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; +import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_TO_SIDE; +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RESULTS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USER_LEAVING; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RESULTS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_USER_LEAVING; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.ActivityManagerService.ANIMATE; +import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; +import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE; +import static com.android.server.am.ActivityStack.ActivityState.RESUMED; +import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED; +import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS; +import static com.android.server.am.ActivityStackSupervisor.ON_TOP; +import static com.android.server.am.ActivityStackSupervisor.TAG_TASKS; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.AppGlobals; +import android.app.IActivityContainer; +import android.app.IActivityManager; +import android.app.IApplicationThread; +import android.app.KeyguardManager; +import android.app.PendingIntent; +import android.app.ProfilerInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.IIntentSender; +import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.UserManager; +import android.service.voice.IVoiceInteractionSession; +import android.util.EventLog; +import android.util.Slog; +import android.view.Display; + +import com.android.internal.app.HeavyWeightSwitcherActivity; +import com.android.internal.app.IVoiceInteractor; +import com.android.internal.widget.LockPatternUtils; +import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch; +import com.android.server.wm.WindowManagerService; + +import java.util.ArrayList; + +/** + * Controller for interpreting how and then launching activities. + * + * This class collects all the logic for determining how an intent and flags should be turned into + * an activity and associated task and stack. + */ +public class ActivityStarter { + private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStarter" : TAG_AM; + private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS; + private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; + private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; + private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING; + + private final ActivityManagerService mService; + private final ActivityStackSupervisor mSupervisor; + private WindowManagerService mWindowManager; + + final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>(); + + ActivityStarter(ActivityManagerService service, ActivityStackSupervisor supervisor) { + mService = service; + mSupervisor = supervisor; + } + + final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent, + String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, + String callingPackage, int realCallingPid, int realCallingUid, int startFlags, + ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified, + ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container, + TaskRecord inTask) { + int err = ActivityManager.START_SUCCESS; + + ProcessRecord callerApp = null; + if (caller != null) { + callerApp = mService.getRecordForAppLocked(caller); + if (callerApp != null) { + callingPid = callerApp.pid; + callingUid = callerApp.info.uid; + } else { + Slog.w(TAG, "Unable to find app for caller " + caller + + " (pid=" + callingPid + ") when starting: " + + intent.toString()); + err = ActivityManager.START_PERMISSION_DENIED; + } + } + + final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; + + if (err == ActivityManager.START_SUCCESS) { + Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false) + + "} from uid " + callingUid + + " on display " + (container == null ? (mSupervisor.mFocusedStack == null ? + Display.DEFAULT_DISPLAY : mSupervisor.mFocusedStack.mDisplayId) : + (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY : + container.mActivityDisplay.mDisplayId))); + } + + ActivityRecord sourceRecord = null; + ActivityRecord resultRecord = null; + if (resultTo != null) { + sourceRecord = mSupervisor.isInAnyStackLocked(resultTo); + if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, + "Will send result to " + resultTo + " " + sourceRecord); + if (sourceRecord != null) { + if (requestCode >= 0 && !sourceRecord.finishing) { + resultRecord = sourceRecord; + } + } + } + + final int launchFlags = intent.getFlags(); + + if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) { + // Transfer the result target from the source activity to the new + // one being started, including any failures. + if (requestCode >= 0) { + ActivityOptions.abort(options); + return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; + } + resultRecord = sourceRecord.resultTo; + if (resultRecord != null && !resultRecord.isInStackLocked()) { + resultRecord = null; + } + resultWho = sourceRecord.resultWho; + requestCode = sourceRecord.requestCode; + sourceRecord.resultTo = null; + if (resultRecord != null) { + resultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode); + } + if (sourceRecord.launchedFromUid == callingUid) { + // The new activity is being launched from the same uid as the previous + // activity in the flow, and asking to forward its result back to the + // previous. In this case the activity is serving as a trampoline between + // the two, so we also want to update its launchedFromPackage to be the + // same as the previous activity. Note that this is safe, since we know + // these two packages come from the same uid; the caller could just as + // well have supplied that same package name itself. This specifially + // deals with the case of an intent picker/chooser being launched in the app + // flow to redirect to an activity picked by the user, where we want the final + // activity to consider it to have been launched by the previous app activity. + callingPackage = sourceRecord.launchedFromPackage; + } + } + + if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) { + // We couldn't find a class that can handle the given Intent. + // That's the end of that! + err = ActivityManager.START_INTENT_NOT_RESOLVED; + } + + if (err == ActivityManager.START_SUCCESS && aInfo == null) { + // We couldn't find the specific class specified in the Intent. + // Also the end of the line. + err = ActivityManager.START_CLASS_NOT_FOUND; + } + + if (err == ActivityManager.START_SUCCESS && sourceRecord != null + && sourceRecord.task.voiceSession != null) { + // If this activity is being launched as part of a voice session, we need + // to ensure that it is safe to do so. If the upcoming activity will also + // be part of the voice session, we can only launch it if it has explicitly + // said it supports the VOICE category, or it is a part of the calling app. + if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 + && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) { + try { + intent.addCategory(Intent.CATEGORY_VOICE); + if (!AppGlobals.getPackageManager().activitySupportsIntent( + intent.getComponent(), intent, resolvedType)) { + Slog.w(TAG, + "Activity being started in current voice task does not support voice: " + + intent); + err = ActivityManager.START_NOT_VOICE_COMPATIBLE; + } + } catch (RemoteException e) { + Slog.w(TAG, "Failure checking voice capabilities", e); + err = ActivityManager.START_NOT_VOICE_COMPATIBLE; + } + } + } + + if (err == ActivityManager.START_SUCCESS && voiceSession != null) { + // If the caller is starting a new voice session, just make sure the target + // is actually allowing it to run this way. + try { + if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(), + intent, resolvedType)) { + Slog.w(TAG, + "Activity being started in new voice task does not support: " + + intent); + err = ActivityManager.START_NOT_VOICE_COMPATIBLE; + } + } catch (RemoteException e) { + Slog.w(TAG, "Failure checking voice capabilities", e); + err = ActivityManager.START_NOT_VOICE_COMPATIBLE; + } + } + + final ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack; + + if (err != ActivityManager.START_SUCCESS) { + if (resultRecord != null) { + resultStack.sendActivityResultLocked(-1, + resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + ActivityOptions.abort(options); + return err; + } + + boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho, + requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp, + resultRecord, resultStack); + abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid, + callingPid, resolvedType, aInfo.applicationInfo); + + if (mService.mController != null) { + try { + // The Intent we give to the watcher has the extra data + // stripped off, since it can contain private information. + Intent watchIntent = intent.cloneFilter(); + abort |= !mService.mController.activityStarting(watchIntent, + aInfo.applicationInfo.packageName); + } catch (RemoteException e) { + mService.mController = null; + } + } + + UserInfo user = mSupervisor.getUserInfo(userId); + KeyguardManager km = (KeyguardManager) mService.mContext + .getSystemService(Context.KEYGUARD_SERVICE); + if (user.isManagedProfile() + && LockPatternUtils.isSeparateWorkChallengeEnabled() + && km.isDeviceLocked(userId)) { + IIntentSender target = mService.getIntentSenderLocked( + ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, + Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent }, + new String[]{ resolvedType }, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_IMMUTABLE, null); + final int flags = intent.getFlags(); + final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, user.id); + if (newIntent != null) { + intent = newIntent; + intent.setFlags(flags + | Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); + intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); + + resolvedType = null; + callingUid = realCallingUid; + callingPid = realCallingPid; + + UserInfo parent = UserManager.get(mService.mContext).getProfileParent(userId); + rInfo = mSupervisor.resolveIntent(intent, resolvedType, parent.id); + aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, + null /*profilerInfo*/); + } + } + + if (abort) { + if (resultRecord != null) { + resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + // We pretend to the caller that it was really started, but + // they will just get a cancel result. + ActivityOptions.abort(options); + return ActivityManager.START_SUCCESS; + } + + // If permissions need a review before any of the app components can run, we + // launch the review activity and pass a pending intent to start the activity + // we are to launching now after the review is completed. + if (Build.PERMISSIONS_REVIEW_REQUIRED && aInfo != null) { + if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired( + aInfo.packageName, userId)) { + IIntentSender target = mService.getIntentSenderLocked( + ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, + callingUid, userId, null, null, 0, new Intent[]{intent}, + new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT + | PendingIntent.FLAG_ONE_SHOT, null); + + final int flags = intent.getFlags(); + Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); + newIntent.setFlags(flags + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName); + newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target)); + if (resultRecord != null) { + newIntent.putExtra(Intent.EXTRA_RESULT_NEEDED, true); + } + intent = newIntent; + + resolvedType = null; + callingUid = realCallingUid; + callingPid = realCallingPid; + + rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId); + aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, + null /*profilerInfo*/); + + if (DEBUG_PERMISSIONS_REVIEW) { + Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, + true, false) + "} from uid " + callingUid + " on display " + + (container == null ? (mSupervisor.mFocusedStack == null ? + Display.DEFAULT_DISPLAY : mSupervisor.mFocusedStack.mDisplayId) : + (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY : + container.mActivityDisplay.mDisplayId))); + } + } + } + + // If we have an ephemeral app, abort the process of launching the resolved intent. + // Instead, launch the ephemeral installer. Once the installer is finished, it + // starts either the intent we resolved here [on install error] or the ephemeral + // app [on install success]. + if (rInfo != null && rInfo.ephemeralResolveInfo != null) { + // Create a pending intent to start the intent resolved here. + final IIntentSender failureTarget = mService.getIntentSenderLocked( + ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, + Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent }, + new String[]{ resolvedType }, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_IMMUTABLE, null); + + // Create a pending intent to start the ephemeral application; force it to be + // directed to the ephemeral package. + ephemeralIntent.setPackage(rInfo.ephemeralResolveInfo.getPackageName()); + final IIntentSender ephemeralTarget = mService.getIntentSenderLocked( + ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, + Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ ephemeralIntent }, + new String[]{ resolvedType }, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_IMMUTABLE, null); + + int flags = intent.getFlags(); + intent = new Intent(); + intent.setFlags(flags + | Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, + rInfo.ephemeralResolveInfo.getPackageName()); + intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, new IntentSender(failureTarget)); + intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, new IntentSender(ephemeralTarget)); + + resolvedType = null; + callingUid = realCallingUid; + callingPid = realCallingPid; + + rInfo = rInfo.ephemeralInstaller; + aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/); + } + + ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage, + intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, + requestCode, componentSpecified, voiceSession != null, mSupervisor, container, + options); + if (outActivity != null) { + outActivity[0] = r; + } + + if (r.appTimeTracker == null && sourceRecord != null) { + // If the caller didn't specify an explicit time tracker, we want to continue + // tracking under any it has. + r.appTimeTracker = sourceRecord.appTimeTracker; + } + + final ActivityStack stack = mSupervisor.mFocusedStack; + if (voiceSession == null && (stack.mResumedActivity == null + || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) { + if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, + realCallingPid, realCallingUid, "Activity start")) { + PendingActivityLaunch pal = new PendingActivityLaunch(r, + sourceRecord, startFlags, stack, callerApp); + mPendingActivityLaunches.add(pal); + ActivityOptions.abort(options); + return ActivityManager.START_SWITCHES_CANCELED; + } + } + + if (mService.mDidAppSwitch) { + // This is the second allowed switch since we stopped switches, + // so now just generally allow switches. Use case: user presses + // home (switches disabled, switch to home, mDidAppSwitch now true); + // user taps a home icon (coming from home so allowed, we hit here + // and now allow anyone to switch again). + mService.mAppSwitchesAllowedTime = 0; + } else { + mService.mDidAppSwitch = true; + } + + doPendingActivityLaunchesLocked(false); + + err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, + voiceInteractor, startFlags, true, options, inTask); + + if (err < 0) { + // If someone asked to have the keyguard dismissed on the next + // activity start, but we are not actually doing an activity + // switch... just dismiss the keyguard now, because we + // probably want to see whatever is behind it. + mSupervisor.notifyActivityDrawnForKeyguard(); + } + return err; + } + + void startHomeActivityLocked(Intent intent, ActivityInfo aInfo, String reason) { + mSupervisor.moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE, reason); + startActivityLocked(null /*caller*/, intent, null /*ephemeralIntent*/, + null /*resolvedType*/, aInfo, null /*rInfo*/, null /*voiceSession*/, + null /*voiceInteractor*/, null /*resultTo*/, null /*resultWho*/, + 0 /*requestCode*/, 0 /*callingPid*/, 0 /*callingUid*/, null /*callingPackage*/, + 0 /*realCallingPid*/, 0 /*realCallingUid*/, 0 /*startFlags*/, null /*options*/, + false /*ignoreTargetSecurity*/, false /*componentSpecified*/, null /*outActivity*/, + null /*container*/, null /*inTask*/); + if (mSupervisor.inResumeTopActivity) { + // If we are in resume section already, home activity will be initialized, but not + // resumed (to avoid recursive resume) and will stay that way until something pokes it + // again. We need to schedule another resume. + mSupervisor.scheduleResumeTopActivities(); + } + } + + final int startActivityMayWait(IApplicationThread caller, int callingUid, + String callingPackage, Intent intent, String resolvedType, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + IBinder resultTo, String resultWho, int requestCode, int startFlags, + ProfilerInfo profilerInfo, IActivityManager.WaitResult outResult, Configuration config, + Bundle bOptions, boolean ignoreTargetSecurity, int userId, + IActivityContainer iContainer, TaskRecord inTask) { + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + boolean componentSpecified = intent.getComponent() != null; + + // Save a copy in case ephemeral needs it + final Intent ephemeralIntent = new Intent(intent); + // Don't modify the client's object! + intent = new Intent(intent); + + ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId); + // Collect information about the target of the Intent. + ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo); + + ActivityOptions options = ActivityOptions.fromBundle(bOptions); + ActivityStackSupervisor.ActivityContainer container = + (ActivityStackSupervisor.ActivityContainer)iContainer; + synchronized (mService) { + if (container != null && container.mParentActivity != null && + container.mParentActivity.state != RESUMED) { + // Cannot start a child activity if the parent is not resumed. + return ActivityManager.START_CANCELED; + } + final int realCallingPid = Binder.getCallingPid(); + final int realCallingUid = Binder.getCallingUid(); + int callingPid; + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { + callingPid = realCallingPid; + callingUid = realCallingUid; + } else { + callingPid = callingUid = -1; + } + + final ActivityStack stack; + if (container == null || container.mStack.isOnHomeDisplay()) { + stack = mSupervisor.mFocusedStack; + } else { + stack = container.mStack; + } + stack.mConfigWillChange = config != null && mService.mConfiguration.diff(config) != 0; + if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, + "Starting activity when config will change = " + stack.mConfigWillChange); + + final long origId = Binder.clearCallingIdentity(); + + if (aInfo != null && + (aInfo.applicationInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { + // This may be a heavy-weight process! Check to see if we already + // have another, different heavy-weight process running. + if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) { + final ProcessRecord heavy = mService.mHeavyWeightProcess; + if (heavy != null && (heavy.info.uid != aInfo.applicationInfo.uid + || !heavy.processName.equals(aInfo.processName))) { + int appCallingUid = callingUid; + if (caller != null) { + ProcessRecord callerApp = mService.getRecordForAppLocked(caller); + if (callerApp != null) { + appCallingUid = callerApp.info.uid; + } else { + Slog.w(TAG, "Unable to find app for caller " + caller + + " (pid=" + callingPid + ") when starting: " + + intent.toString()); + ActivityOptions.abort(options); + return ActivityManager.START_PERMISSION_DENIED; + } + } + + IIntentSender target = mService.getIntentSenderLocked( + ActivityManager.INTENT_SENDER_ACTIVITY, "android", + appCallingUid, userId, null, null, 0, new Intent[] { intent }, + new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT + | PendingIntent.FLAG_ONE_SHOT, null); + + Intent newIntent = new Intent(); + if (requestCode >= 0) { + // Caller is requesting a result. + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true); + } + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, + new IntentSender(target)); + if (heavy.activities.size() > 0) { + ActivityRecord hist = heavy.activities.get(0); + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP, + hist.packageName); + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK, + hist.task.taskId); + } + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP, + aInfo.packageName); + newIntent.setFlags(intent.getFlags()); + newIntent.setClassName("android", + HeavyWeightSwitcherActivity.class.getName()); + intent = newIntent; + resolvedType = null; + caller = null; + callingUid = Binder.getCallingUid(); + callingPid = Binder.getCallingPid(); + componentSpecified = true; + rInfo = mSupervisor.resolveIntent(intent, null /*resolvedType*/, userId); + aInfo = rInfo != null ? rInfo.activityInfo : null; + if (aInfo != null) { + aInfo = mService.getActivityInfoForUser(aInfo, userId); + } + } + } + } + + int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType, + aInfo, rInfo, voiceSession, voiceInteractor, + resultTo, resultWho, requestCode, callingPid, + callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, + options, ignoreTargetSecurity, componentSpecified, null, container, inTask); + + Binder.restoreCallingIdentity(origId); + + if (stack.mConfigWillChange) { + // If the caller also wants to switch to a new configuration, + // do so now. This allows a clean switch, as we are waiting + // for the current activity to pause (so we will not destroy + // it), and have not yet started the next activity. + mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, + "updateConfiguration()"); + stack.mConfigWillChange = false; + if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, + "Updating to new configuration after starting activity."); + mService.updateConfigurationLocked(config, null, false); + } + + if (outResult != null) { + outResult.result = res; + if (res == ActivityManager.START_SUCCESS) { + mSupervisor.mWaitingActivityLaunched.add(outResult); + do { + try { + mService.wait(); + } catch (InterruptedException e) { + } + } while (!outResult.timeout && outResult.who == null); + } else if (res == ActivityManager.START_TASK_TO_FRONT) { + ActivityRecord r = stack.topRunningActivityLocked(); + if (r.nowVisible && r.state == RESUMED) { + outResult.timeout = false; + outResult.who = new ComponentName(r.info.packageName, r.info.name); + outResult.totalTime = 0; + outResult.thisTime = 0; + } else { + outResult.thisTime = SystemClock.uptimeMillis(); + mSupervisor.mWaitingActivityVisible.add(outResult); + do { + try { + mService.wait(); + } catch (InterruptedException e) { + } + } while (!outResult.timeout && outResult.who == null); + } + } + } + + return res; + } + } + + final int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, + boolean doResume, ActivityOptions options, TaskRecord inTask) { + final Intent intent = r.intent; + final int callingUid = r.launchedFromUid; + + final Rect newBounds = mSupervisor.getOverrideBounds(r, options, inTask); + final boolean overrideBounds = newBounds != null; + + // In some flows in to this function, we retrieve the task record and hold on to it + // without a lock before calling back in to here... so the task at this point may + // not actually be in recents. Check for that, and if it isn't in recents just + // consider it invalid. + if (inTask != null && !inTask.inRecents) { + Slog.w(TAG, "Starting activity in task not in recents: " + inTask); + inTask = null; + } + + final boolean launchSingleTop = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP; + final boolean launchSingleInstance = r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE; + final boolean launchSingleTask = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK; + int launchFlags = adjustLaunchFlagsToDocumentMode(r, launchSingleInstance, launchSingleTask, + intent.getFlags()); + final boolean launchTaskBehind = r.mLaunchTaskBehind + && !launchSingleTask && !launchSingleInstance + && (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0; + + if (r.resultTo != null && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 + && r.resultTo.task.stack != null) { + // For whatever reason this activity is being launched into a new + // task... yet the caller has requested a result back. Well, that + // is pretty messed up, so instead immediately send back a cancel + // and let the new task continue launched as normal without a + // dependency on its originator. + Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); + r.resultTo.task.stack.sendActivityResultLocked(-1, + r.resultTo, r.resultWho, r.requestCode, + Activity.RESULT_CANCELED, null); + r.resultTo = null; + } + + if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && r.resultTo == null) { + launchFlags |= FLAG_ACTIVITY_NEW_TASK; + } + + // If we are actually going to launch in to a new task, there are some cases where + // we further want to do multiple task. + if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { + if (launchTaskBehind + || r.info.documentLaunchMode == ActivityInfo.DOCUMENT_LAUNCH_ALWAYS) { + launchFlags |= FLAG_ACTIVITY_MULTIPLE_TASK; + } + } + + // We'll invoke onUserLeaving before onPause only if the launching + // activity did not explicitly state that this is an automated launch. + mSupervisor.mUserLeaving = (launchFlags & Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; + if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING, + "startActivity() => mUserLeaving=" + mSupervisor.mUserLeaving); + + // If the caller has asked not to resume at this point, we make note + // of this in the record so that we can skip it when trying to find + // the top running activity. + if (!doResume || !mSupervisor.okToShowLocked(r)) { + r.delayedResume = true; + doResume = false; + } + + final ActivityRecord notTop = + (launchFlags & Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null; + + // If the onlyIfNeeded flag is set, then we can do this if the activity + // being launched is the same as the one making the call... or, as + // a special case, if we do not know the caller then we count the + // current top activity as the caller. + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + ActivityRecord checkedCaller = sourceRecord; + if (checkedCaller == null) { + checkedCaller = mSupervisor.mFocusedStack.topRunningNonDelayedActivityLocked( + notTop); + } + if (!checkedCaller.realActivity.equals(r.realActivity)) { + // Caller is not the same as launcher, so always needed. + startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED; + } + } + + boolean addingToTask = false; + TaskRecord reuseTask = null; + + // If the caller is not coming from another activity, but has given us an + // explicit task into which they would like us to launch the new activity, + // then let's see about doing that. + if (sourceRecord == null && inTask != null && inTask.stack != null) { + final Intent baseIntent = inTask.getBaseIntent(); + final ActivityRecord root = inTask.getRootActivity(); + if (baseIntent == null) { + ActivityOptions.abort(options); + throw new IllegalArgumentException("Launching into task without base intent: " + + inTask); + } + + // If this task is empty, then we are adding the first activity -- it + // determines the root, and must be launching as a NEW_TASK. + if (launchSingleInstance || launchSingleTask) { + if (!baseIntent.getComponent().equals(r.intent.getComponent())) { + ActivityOptions.abort(options); + throw new IllegalArgumentException("Trying to launch singleInstance/Task " + + r + " into different task " + inTask); + } + if (root != null) { + ActivityOptions.abort(options); + throw new IllegalArgumentException("Caller with inTask " + inTask + + " has root " + root + " but target is singleInstance/Task"); + } + } + + // If task is empty, then adopt the interesting intent launch flags in to the + // activity being started. + if (root == null) { + final int flagsOfInterest = FLAG_ACTIVITY_NEW_TASK + | FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT + | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; + launchFlags = (launchFlags&~flagsOfInterest) + | (baseIntent.getFlags()&flagsOfInterest); + intent.setFlags(launchFlags); + inTask.setIntent(r); + addingToTask = true; + + // If the task is not empty and the caller is asking to start it as the root + // of a new task, then we don't actually want to start this on the task. We + // will bring the task to the front, and possibly give it a new intent. + } else if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { + addingToTask = false; + + } else { + addingToTask = true; + } + + reuseTask = inTask; + } else { + inTask = null; + // Launch ResolverActivity in the source task, so that it stays in the task + // bounds when in freeform workspace. + // Also put noDisplay activities in the source task. These by itself can + // be placed in any task/stack, however it could launch other activities + // like ResolverActivity, and we want those to stay in the original task. + if ((r.isResolverActivity() || r.noDisplay) && sourceRecord != null + && sourceRecord.isFreeform()) { + addingToTask = true; + } + } + + if (inTask == null) { + if (sourceRecord == null) { + // This activity is not being started from another... in this + // case we -always- start a new task. + if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) { + Slog.w(TAG, "startActivity called from non-Activity context; forcing " + + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); + launchFlags |= FLAG_ACTIVITY_NEW_TASK; + } + } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + // The original activity who is starting us is running as a single + // instance... this new activity it is starting must go on its + // own task. + launchFlags |= FLAG_ACTIVITY_NEW_TASK; + } else if (launchSingleInstance || launchSingleTask) { + // The activity being started is a single instance... it always + // gets launched into its own task. + launchFlags |= FLAG_ACTIVITY_NEW_TASK; + } + } + + ActivityInfo newTaskInfo = null; + Intent newTaskIntent = null; + final ActivityStack sourceStack; + if (sourceRecord != null) { + if (sourceRecord.finishing) { + // If the source is finishing, we can't further count it as our source. This + // is because the task it is associated with may now be empty and on its way out, + // so we don't want to blindly throw it in to that task. Instead we will take + // the NEW_TASK flow and try to find a task for it. But save the task information + // so it can be used when creating the new task. + if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0) { + Slog.w(TAG, "startActivity called from finishing " + sourceRecord + + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); + launchFlags |= FLAG_ACTIVITY_NEW_TASK; + newTaskInfo = sourceRecord.info; + newTaskIntent = sourceRecord.task.intent; + } + sourceRecord = null; + sourceStack = null; + } else { + sourceStack = sourceRecord.task.stack; + } + } else { + sourceStack = null; + } + + boolean movedHome = false; + ActivityStack targetStack; + + intent.setFlags(launchFlags); + final boolean noAnimation = (launchFlags & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0; + + ActivityRecord intentActivity = getReusableIntentActivity(r, inTask, intent, + launchSingleInstance, launchSingleTask, launchFlags); + if (intentActivity != null) { + // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused + // but still needs to be a lock task mode violation since the task gets + // cleared out and the device would otherwise leave the locked task. + if (mSupervisor.isLockTaskModeViolation(intentActivity.task, + (launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) + == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) { + mSupervisor.showLockTaskToast(); + Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode"); + return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; + } + if (r.task == null) { + r.task = intentActivity.task; + } + if (intentActivity.task.intent == null) { + // This task was started because of movement of the activity based on affinity... + // Now that we are actually launching it, we can assign the base intent. + intentActivity.task.setIntent(r); + } + + targetStack = intentActivity.task.stack; + targetStack.mLastPausedActivity = null; + // If the target task is not in the front, then we need + // to bring it to the front... except... well, with + // SINGLE_TASK_LAUNCH it's not entirely clear. We'd like + // to have the same behavior as if a new instance was + // being started, which means not bringing it to the front + // if the caller is not itself in the front. + final ActivityStack focusStack = mSupervisor.getFocusedStack(); + ActivityRecord curTop = (focusStack == null) + ? null : focusStack.topRunningNonDelayedActivityLocked(notTop); + boolean movedToFront = false; + if (curTop != null && (curTop.task != intentActivity.task || + curTop.task != focusStack.topTask())) { + r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); + if (sourceRecord == null || (sourceStack.topActivity() != null && + sourceStack.topActivity().task == sourceRecord.task)) { + // We really do want to push this one into the user's face, right now. + if (launchTaskBehind && sourceRecord != null) { + intentActivity.setTaskToAffiliateWith(sourceRecord.task); + } + movedHome = true; + final ActivityStack sideStack = getLaunchToSideStack(r, launchFlags, r.task); + if (sideStack == null || sideStack == targetStack) { + // We only want to move to the front, if we aren't going to launch on a + // different stack. If we launch on a different stack, we will put the + // task on top there. + targetStack.moveTaskToFrontLocked(intentActivity.task, noAnimation, + options, r.appTimeTracker, "bringingFoundTaskToFront"); + movedToFront = true; + } + if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) + == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { + // Caller wants to appear on home activity. + intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); + } + options = null; + } + } + if (!movedToFront && doResume) { + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + targetStack + + " from " + intentActivity); + targetStack.moveToFront("intentActivityFound"); + } + + // If the caller has requested that the target task be + // reset, then do so. + if ((launchFlags & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { + intentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r); + } + if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + // We don't need to start a new activity, and + // the client said not to do anything if that + // is the case, so this is it! And for paranoia, make + // sure we have correctly resumed the top activity. + if (doResume) { + mSupervisor.resumeTopActivitiesLocked(targetStack, null, options); + + // Make sure to notify Keyguard as well if we are not running an app + // transition later. + if (!movedToFront) { + mSupervisor.notifyActivityDrawnForKeyguard(); + } + } else { + ActivityOptions.abort(options); + } + mSupervisor.updateUserStackLocked(r.userId, targetStack); + return ActivityManager.START_RETURN_INTENT_TO_CALLER; + } + if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) + == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) { + // The caller has requested to completely replace any + // existing task with its new activity. Well that should + // not be too hard... + reuseTask = intentActivity.task; + reuseTask.performClearTaskLocked(); + reuseTask.setIntent(r); + } else if ((launchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0 + || launchSingleInstance || launchSingleTask) { + // In this situation we want to remove all activities + // from the task up to the one being started. In most + // cases this means we are resetting the task to its + // initial state. + ActivityRecord top = intentActivity.task.performClearTaskLocked(r, launchFlags); + if (top != null) { + if (top.frontOfTask) { + // Activity aliases may mean we use different + // intents for the top activity, so make sure + // the task now has the identity of the new + // intent. + top.task.setIntent(r); + } + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); + } else { + // A special case: we need to start the activity because it is not + // currently running, and the caller has asked to clear the current + // task to have this activity at the top. + addingToTask = true; + // Now pretend like this activity is being started by the top of its + // task, so it is put in the right place. + sourceRecord = intentActivity; + TaskRecord task = sourceRecord.task; + if (task != null && task.stack == null) { + // Target stack got cleared when we all activities were removed + // above. Go ahead and reset it. + targetStack = computeStackFocus( + sourceRecord, false /* newTask */, null /* bounds */, launchFlags); + targetStack.addTask(task, + !launchTaskBehind /* toTop */, "startActivityUnchecked"); + } + + } + } else if (r.realActivity.equals(intentActivity.task.realActivity)) { + // In this case the top activity on the task is the + // same as the one being launched, so we take that + // as a request to bring the task to the foreground. + // If the top activity in the task is the root + // activity, deliver this new intent to it if it + // desires. + if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 || launchSingleTop) + && intentActivity.realActivity.equals(r.realActivity)) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, + intentActivity.task); + if (intentActivity.frontOfTask) { + intentActivity.task.setIntent(r); + } + intentActivity.deliverNewIntentLocked(callingUid, r.intent, + r.launchedFromPackage); + } else if (!r.intent.filterEquals(intentActivity.task.intent)) { + // In this case we are launching the root activity + // of the task, but with a different intent. We + // should start a new instance on top. + addingToTask = true; + sourceRecord = intentActivity; + } + } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) { + // In this case an activity is being launched in to an + // existing task, without resetting that task. This + // is typically the situation of launching an activity + // from a notification or shortcut. We want to place + // the new activity on top of the current task. + addingToTask = true; + sourceRecord = intentActivity; + } else if (!intentActivity.task.rootWasReset) { + // In this case we are launching in to an existing task + // that has not yet been started from its front door. + // The current task has been brought to the front. + // Ideally, we'd probably like to place this new task + // at the bottom of its stack, but that's a little hard + // to do with the current organization of the code so + // for now we'll just drop it. + intentActivity.task.setIntent(r); + } + if (!addingToTask && reuseTask == null) { + // We didn't do anything... but it was needed (a.k.a., client + // don't use that intent!) And for paranoia, make + // sure we have correctly resumed the top activity. + if (doResume) { + targetStack.resumeTopActivityLocked(null, options); + if (!movedToFront) { + // Make sure to notify Keyguard as well if we are not running an app + // transition later. + mSupervisor.notifyActivityDrawnForKeyguard(); + } + } else { + ActivityOptions.abort(options); + } + mSupervisor.updateUserStackLocked(r.userId, targetStack); + return ActivityManager.START_TASK_TO_FRONT; + } + } + + //String uri = r.intent.toURI(); + //Intent intent2 = new Intent(uri); + //Slog.i(TAG, "Given intent: " + r.intent); + //Slog.i(TAG, "URI is: " + uri); + //Slog.i(TAG, "To intent: " + intent2); + + if (r.packageName != null) { + // If the activity being launched is the same as the one currently + // at the top, then we need to check if it should only be launched + // once. + ActivityStack topStack = mSupervisor.mFocusedStack; + ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop); + final boolean dontStart = top != null && r.resultTo == null + && top.realActivity.equals(r.realActivity) && top.userId == r.userId + && top.app != null && top.app.thread != null + && ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 + || launchSingleTop || launchSingleTask); + if (dontStart) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); + // For paranoia, make sure we have correctly resumed the top activity. + topStack.mLastPausedActivity = null; + if (doResume) { + mSupervisor.resumeTopActivitiesLocked(); + } + ActivityOptions.abort(options); + if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + // We don't need to start a new activity, and the client said not to do + // anything if that is the case, so this is it! + return ActivityManager.START_RETURN_INTENT_TO_CALLER; + } + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); + return ActivityManager.START_DELIVERED_TO_TOP; + } + } else { + if (r.resultTo != null && r.resultTo.task.stack != null) { + r.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho, + r.requestCode, Activity.RESULT_CANCELED, null); + } + ActivityOptions.abort(options); + return ActivityManager.START_CLASS_NOT_FOUND; + } + + boolean newTask = false; + boolean keepCurTransition = false; + + final TaskRecord taskToAffiliate = launchTaskBehind && sourceRecord != null ? + sourceRecord.task : null; + + // Should this be considered a new task? + if (r.resultTo == null && inTask == null && !addingToTask + && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { + newTask = true; + targetStack = computeStackFocus(r, newTask, newBounds, launchFlags); + if (doResume) { + targetStack.moveToFront("startingNewTask"); + } + + if (reuseTask == null) { + r.setTask(targetStack.createTaskRecord(mSupervisor.getNextTaskId(), + newTaskInfo != null ? newTaskInfo : r.info, + newTaskIntent != null ? newTaskIntent : intent, + voiceSession, voiceInteractor, !launchTaskBehind /* toTop */), + taskToAffiliate); + if (overrideBounds) { + r.task.updateOverrideConfiguration(newBounds); + } + if (DEBUG_TASKS) Slog.v(TAG_TASKS, + "Starting new activity " + r + " in new task " + r.task); + } else { + r.setTask(reuseTask, taskToAffiliate); + } + if (mSupervisor.isLockTaskModeViolation(r.task)) { + Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); + return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; + } + if (!movedHome) { + if ((launchFlags & + (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) + == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { + // Caller wants to appear on home activity, so before starting + // their own activity we will bring home to the front. + r.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); + } + } + } else if (sourceRecord != null) { + final TaskRecord sourceTask = sourceRecord.task; + if (mSupervisor.isLockTaskModeViolation(sourceTask)) { + Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); + return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; + } + targetStack = null; + if (sourceTask.stack.topTask() != sourceTask) { + // We only want to allow changing stack if the target task is not the top one, + // otherwise we would move the launching task to the other side, rather than show + // two side by side. + targetStack = getLaunchToSideStack(r, launchFlags, r.task); + } + if (targetStack == null) { + targetStack = sourceTask.stack; + } else if (targetStack != sourceTask.stack) { + mSupervisor.moveTaskToStackLocked(sourceTask.taskId, targetStack.mStackId, + ON_TOP, FORCE_FOCUS, "launchToSide", !ANIMATE); + } + if (doResume) { + targetStack.moveToFront("sourceStackToFront"); + } + final TaskRecord topTask = targetStack.topTask(); + if (topTask != sourceTask) { + targetStack.moveTaskToFrontLocked(sourceTask, noAnimation, options, + r.appTimeTracker, "sourceTaskToFront"); + } + if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { + // In this case, we are adding the activity to an existing + // task, but the caller has asked to clear that task if the + // activity is already running. + ActivityRecord top = sourceTask.performClearTaskLocked(r, launchFlags); + keepCurTransition = true; + if (top != null) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); + // For paranoia, make sure we have correctly + // resumed the top activity. + targetStack.mLastPausedActivity = null; + if (doResume) { + targetStack.resumeTopActivityLocked(null); + } + ActivityOptions.abort(options); + return ActivityManager.START_DELIVERED_TO_TOP; + } + } else if (!addingToTask && + (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) { + // In this case, we are launching an activity in our own task + // that may already be running somewhere in the history, and + // we want to shuffle it to the front of the stack if so. + final ActivityRecord top = sourceTask.findActivityInHistoryLocked(r); + if (top != null) { + final TaskRecord task = top.task; + task.moveActivityToFrontLocked(top); + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, task); + top.updateOptionsLocked(options); + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); + targetStack.mLastPausedActivity = null; + if (doResume) { + targetStack.resumeTopActivityLocked(null); + } + return ActivityManager.START_DELIVERED_TO_TOP; + } + } + // An existing activity is starting this new activity, so we want + // to keep the new one in the same task as the one that is starting + // it. + r.setTask(sourceTask, null); + if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r + + " in existing task " + r.task + " from source " + sourceRecord); + + } else if (inTask != null) { + // The caller is asking that the new activity be started in an explicit + // task it has provided to us. + if (mSupervisor.isLockTaskModeViolation(inTask)) { + Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); + return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; + } + if (overrideBounds) { + inTask.updateOverrideConfiguration(newBounds); + int stackId = inTask.getLaunchStackId(); + if (stackId != inTask.stack.mStackId) { + mSupervisor.moveTaskToStackUncheckedLocked( + inTask, stackId, ON_TOP, !FORCE_FOCUS, "inTaskToFront"); + } + } + targetStack = inTask.stack; + targetStack.moveTaskToFrontLocked(inTask, noAnimation, options, r.appTimeTracker, + "inTaskToFront"); + + // Check whether we should actually launch the new activity in to the task, + // or just reuse the current activity on top. + ActivityRecord top = inTask.getTopActivity(); + if (top != null && top.realActivity.equals(r.realActivity) && top.userId == r.userId) { + if ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 + || launchSingleTop || launchSingleTask) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + // We don't need to start a new activity, and + // the client said not to do anything if that + // is the case, so this is it! + return ActivityManager.START_RETURN_INTENT_TO_CALLER; + } + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); + return ActivityManager.START_DELIVERED_TO_TOP; + } + } + + if (!addingToTask) { + // We don't actually want to have this activity added to the task, so just + // stop here but still tell the caller that we consumed the intent. + ActivityOptions.abort(options); + return ActivityManager.START_TASK_TO_FRONT; + } + + r.setTask(inTask, null); + if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r + + " in explicit task " + r.task); + + } else { + // This not being started from an existing activity, and not part + // of a new task... just put it in the top task, though these days + // this case should never happen. + targetStack = computeStackFocus(r, newTask, null /* bounds */, launchFlags); + if (doResume) { + targetStack.moveToFront("addingToTopTask"); + } + ActivityRecord prev = targetStack.topActivity(); + r.setTask(prev != null ? prev.task : targetStack.createTaskRecord( + mSupervisor.getNextTaskId(), r.info, intent, null, null, true), null); + mWindowManager.moveTaskToTop(r.task.taskId); + if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r + + " in new guessed " + r.task); + } + + mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, + intent, r.getUriPermissionsLocked(), r.userId); + + if (sourceRecord != null && sourceRecord.isRecentsActivity()) { + r.task.setTaskToReturnTo(RECENTS_ACTIVITY_TYPE); + } + if (newTask) { + EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId); + } + ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); + targetStack.mLastPausedActivity = null; + targetStack.startActivityLocked(r, newTask, keepCurTransition, options); + if (doResume) { + if (!launchTaskBehind) { + mService.setFocusedActivityLocked(r, "startedActivity"); + } + mSupervisor.resumeTopActivitiesLocked(targetStack, r, options); + } else { + targetStack.addRecentActivityLocked(r); + } + mSupervisor.updateUserStackLocked(r.userId, targetStack); + + if (!r.task.mResizeable && mSupervisor.isStackDockedInEffect(targetStack.mStackId)) { + mSupervisor.showNonResizeableDockToast(r.task.taskId); + } + + return ActivityManager.START_SUCCESS; + } + + private int adjustLaunchFlagsToDocumentMode(ActivityRecord r, boolean launchSingleInstance, + boolean launchSingleTask, int launchFlags) { + if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && + (launchSingleInstance || launchSingleTask)) { + // We have a conflict between the Intent and the Activity manifest, manifest wins. + Slog.i(TAG, "Ignoring FLAG_ACTIVITY_NEW_DOCUMENT, launchMode is " + + "\"singleInstance\" or \"singleTask\""); + launchFlags &= + ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK); + } else { + switch (r.info.documentLaunchMode) { + case ActivityInfo.DOCUMENT_LAUNCH_NONE: + break; + case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING: + launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT; + break; + case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS: + launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT; + break; + case ActivityInfo.DOCUMENT_LAUNCH_NEVER: + launchFlags &= ~FLAG_ACTIVITY_MULTIPLE_TASK; + break; + } + } + return launchFlags; + } + + final int startActivities(IApplicationThread caller, int callingUid, String callingPackage, + Intent[] intents, String[] resolvedTypes, IBinder resultTo, + Bundle bOptions, int userId) { + if (intents == null) { + throw new NullPointerException("intents is null"); + } + if (resolvedTypes == null) { + throw new NullPointerException("resolvedTypes is null"); + } + if (intents.length != resolvedTypes.length) { + throw new IllegalArgumentException("intents are length different than resolvedTypes"); + } + + + int callingPid; + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { + callingPid = Binder.getCallingPid(); + callingUid = Binder.getCallingUid(); + } else { + callingPid = callingUid = -1; + } + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mService) { + ActivityRecord[] outActivity = new ActivityRecord[1]; + for (int i=0; i<intents.length; i++) { + Intent intent = intents[i]; + if (intent == null) { + continue; + } + + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i], 0, + null, userId); + // TODO: New, check if this is correct + aInfo = mService.getActivityInfoForUser(aInfo, userId); + + if (aInfo != null && + (aInfo.applicationInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { + throw new IllegalArgumentException( + "FLAG_CANT_SAVE_STATE not supported here"); + } + + ActivityOptions options = ActivityOptions.fromBundle( + i == intents.length - 1 ? bOptions : null); + int res = startActivityLocked(caller, intent, null /*ephemeralIntent*/, + resolvedTypes[i], aInfo, null /*rInfo*/, null, null, resultTo, null, -1, + callingPid, callingUid, callingPackage, callingPid, callingUid, 0, + options, false, componentSpecified, outActivity, null, null); + if (res < 0) { + return res; + } + + resultTo = outActivity[0] != null ? outActivity[0].appToken : null; + } + } + } finally { + Binder.restoreCallingIdentity(origId); + } + + return ActivityManager.START_SUCCESS; + } + + final void doPendingActivityLaunchesLocked(boolean doResume) { + while (!mPendingActivityLaunches.isEmpty()) { + PendingActivityLaunch pal = mPendingActivityLaunches.remove(0); + + try { + startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, + pal.startFlags, doResume && mPendingActivityLaunches.isEmpty(), + null, null); + } catch (Exception e) { + Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e); + pal.sendErrorResult(e.getMessage()); + } + } + } + + private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds, + int launchFlags) { + final TaskRecord task = r.task; + if (!(r.isApplicationActivity() || (task != null && task.isApplicationTask()))) { + return mSupervisor.mHomeStack; + } + + ActivityStack stack = getLaunchToSideStack(r, launchFlags, task); + if (stack != null) { + return stack; + } + + if (task != null && task.stack != null) { + stack = task.stack; + if (stack.isOnHomeDisplay()) { + if (mSupervisor.mFocusedStack != stack) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Setting " + "focused stack to r=" + r + + " task=" + task); + } else { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Focused stack already=" + + mSupervisor.mFocusedStack); + } + } + return stack; + } + + final ActivityStackSupervisor.ActivityContainer container = r.mInitialActivityContainer; + if (container != null) { + // The first time put it on the desired stack, after this put on task stack. + r.mInitialActivityContainer = null; + return container.mStack; + } + + // The fullscreen stack can contain any task regardless of if the task is resizeable + // or not. So, we let the task go in the fullscreen task if it is the focus stack. + // If the freeform or docked stack has focus, and the activity to be launched is resizeable, + // we can also put it in the focused stack. + final int focusedStackId = mSupervisor.mFocusedStack.mStackId; + final boolean canUseFocusedStack = + focusedStackId == FULLSCREEN_WORKSPACE_STACK_ID + || focusedStackId == DOCKED_STACK_ID + || (focusedStackId == FREEFORM_WORKSPACE_STACK_ID && r.info.resizeable); + if (canUseFocusedStack && (!newTask + || mSupervisor.mFocusedStack.mActivityContainer.isEligibleForNewTasks())) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Have a focused stack=" + mSupervisor.mFocusedStack); + return mSupervisor.mFocusedStack; + } + + // We first try to put the task in the first dynamic stack. + final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks; + for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) { + stack = homeDisplayStacks.get(stackNdx); + if (!ActivityManager.StackId.isStaticStack(stack.mStackId)) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Setting focused stack=" + stack); + return stack; + } + } + + // If there is no suitable dynamic stack then we figure out which static stack to use. + final int stackId = task != null ? task.getLaunchStackId() : + bounds != null ? FREEFORM_WORKSPACE_STACK_ID : + FULLSCREEN_WORKSPACE_STACK_ID; + stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP); + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r=" + + r + " stackId=" + stack.mStackId); + return stack; + } + + private ActivityStack getLaunchToSideStack(ActivityRecord r, int launchFlags, TaskRecord task) { + if ((launchFlags & FLAG_ACTIVITY_LAUNCH_TO_SIDE) == 0) { + return null; + } + // The parent activity doesn't want to launch the activity on top of itself, but + // instead tries to put it onto other side in side-by-side mode. + final ActivityStack parentStack = task != null ? task.stack + : r.mInitialActivityContainer != null ? r.mInitialActivityContainer.mStack + : mSupervisor.mFocusedStack; + if (parentStack != null && parentStack.mStackId == DOCKED_STACK_ID) { + // If parent was in docked stack, the natural place to launch another activity + // will be fullscreen, so it can appear alongside the docked window. + return mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED, ON_TOP); + } else { + // If the parent is not in the docked stack, we check if there is docked window + // and if yes, we will launch into that stack. If not, we just put the new + // activity into parent's stack, because we can't find a better place. + final ActivityStack stack = mSupervisor.getStack(DOCKED_STACK_ID); + if (stack != null && !stack.isStackVisibleLocked()) { + // There is a docked stack, but it isn't visible, so we can't launch into that. + return null; + } else { + return stack; + } + } + } + + /** + * Decide whether the new activity should be inserted into an existing task. Returns null if not + * or an ActivityRecord with the task into which the new activity should be added. + */ + private ActivityRecord getReusableIntentActivity(ActivityRecord r, TaskRecord inTask, + Intent intent, boolean launchSingleInstance, boolean launchSingleTask, + int launchFlags) { + // We may want to try to place the new activity in to an existing task. We always + // do this if the target activity is singleTask or singleInstance; we will also do + // this if NEW_TASK has been requested, and there is not an additional qualifier telling + // us to still place it in a new task: multi task, always doc mode, or being asked to + // launch this as a new task behind the current one. + boolean putIntoExistingTask = ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 && + (launchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0) + || launchSingleInstance || launchSingleTask; + // If bring to front is requested, and no result is requested and we have not + // been given an explicit task to launch in to, and + // we can find a task that was started with this same + // component, then instead of launching bring that one to the front. + putIntoExistingTask &= inTask == null && r.resultTo == null; + ActivityRecord intentActivity = null; + if (putIntoExistingTask) { + // See if there is a task to bring to the front. If this is + // a SINGLE_INSTANCE activity, there can be one and only one + // instance of it in the history, and it is always in its own + // unique task, so we do a special search. + intentActivity = launchSingleInstance ? mSupervisor.findActivityLocked(intent, r.info) + : mSupervisor.findTaskLocked(r); + } + return intentActivity; + } + + void setWindowManager(WindowManagerService wm) { + mWindowManager = wm; + } + + void removePendingActivityLaunchesLocked(ActivityStack stack) { + for (int palNdx = mPendingActivityLaunches.size() - 1; palNdx >= 0; --palNdx) { + PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx); + if (pal.stack == stack) { + mPendingActivityLaunches.remove(palNdx); + } + } + } +} diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 5ee9eea48e33..4647d77bef89 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -1394,6 +1394,12 @@ final class TaskRecord { return mLastNonFullscreenBounds; } + boolean canMatchRootAffinity() { + // We don't allow root affinity matching on the pinned stack as no other task should + // be launching in it based on affinity. + return rootAffinity != null && (stack == null || stack.mStackId != PINNED_STACK_ID); + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("userId="); pw.print(userId); pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid); diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 62e78a456c98..5426b72c2efb 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -215,7 +215,8 @@ final class UserController { Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null); intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); - intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT); + intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); mService.broadcastIntentLocked(null, null, intent, null, resultTo, 0, null, null, new String[] { android.Manifest.permission.RECEIVE_BOOT_COMPLETED }, AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId); @@ -245,6 +246,7 @@ final class UserController { mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0)); final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED); + unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); unlockedIntent.addFlags( Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); mService.broadcastIntentLocked(null, null, unlockedIntent, null, null, 0, null, diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java index d9f94d0714ae..472e8f6ff06c 100644 --- a/services/core/java/com/android/server/job/JobStore.java +++ b/services/core/java/com/android/server/job/JobStore.java @@ -46,6 +46,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Set; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -348,9 +349,27 @@ public class JobStore { private void writeBundleToXml(PersistableBundle extras, XmlSerializer out) throws IOException, XmlPullParserException { out.startTag(null, XML_TAG_EXTRAS); - extras.saveToXml(out); + PersistableBundle extrasCopy = deepCopyBundle(extras, 10); + extrasCopy.saveToXml(out); out.endTag(null, XML_TAG_EXTRAS); } + + private PersistableBundle deepCopyBundle(PersistableBundle bundle, int maxDepth) { + if (maxDepth <= 0) { + return null; + } + PersistableBundle copy = (PersistableBundle) bundle.clone(); + Set<String> keySet = bundle.keySet(); + for (String key: keySet) { + PersistableBundle b = copy.getPersistableBundle(key); + if (b != null) { + PersistableBundle bCopy = deepCopyBundle(b, maxDepth-1); + copy.putPersistableBundle(key, bCopy); + } + } + return copy; + } + /** * Write out a tag with data identifying this job's constraints. If the constraint isn't here * it doesn't apply. diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 3a8f041d7299..80fb15d66999 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1263,6 +1263,13 @@ public class NotificationManagerService extends SystemService { return mRankingHelper.getTopicImportance(pkg, uid, topic); } + @Override + public void setAppImportance(String pkg, int uid, int importance) { + enforceSystemOrSystemUI("Caller not system or systemui"); + mRankingHelper.setAppImportance(pkg, uid, importance); + savePolicyFile(); + } + /** * System-only API for getting a list of current (i.e. not cleared) notifications. * diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index acdd90ae9577..a6c9b0d946e2 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -35,4 +35,6 @@ public interface RankingConfig { void setTopicImportance(String packageName, int uid, Notification.Topic topic, int importance); int getTopicImportance(String packageName, int uid, Notification.Topic topic); + + void setAppImportance(String packageName, int uid, int importance); } diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 5a31c6a27a4f..32c0ce24dc20 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -251,6 +251,7 @@ public class RankingHelper implements RankingConfig { } out.startTag(null, TAG_PACKAGE); out.attribute(null, ATT_NAME, r.pkg); + out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance)); if (!forBackup) { out.attribute(null, ATT_UID, Integer.toString(r.uid)); @@ -426,6 +427,20 @@ public class RankingHelper implements RankingConfig { updateConfig(); } + /** + * Sets the default importance for all new topics that appear in the future, and resets + * the importance of all current topics. + */ + @Override + public void setAppImportance(String pkgName, int uid, int importance) { + final Record r = getOrCreateRecord(pkgName, uid); + r.importance = importance; + for (Topic t : r.topics.values()) { + t.importance = importance; + } + updateConfig(); + } + private Topic getOrCreateTopic(Record r, Notification.Topic topic) { if (topic == null) { topic = createDefaultTopic(); @@ -435,6 +450,7 @@ public class RankingHelper implements RankingConfig { return t; } else { t = new Topic(topic); + t.importance = r.importance; r.topics.put(topic.getId(), t); return t; } @@ -477,6 +493,8 @@ public class RankingHelper implements RankingConfig { pw.print(" ("); pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid)); pw.print(')'); + pw.print(" importance="); + pw.print(Ranking.importanceToString(r.importance)); pw.println(); for (Topic t : r.topics.values()) { pw.print(prefix); @@ -532,6 +550,7 @@ public class RankingHelper implements RankingConfig { String pkg; int uid = UNKNOWN_UID; + int importance = DEFAULT_IMPORTANCE; Map<String, Topic> topics = new ArrayMap<>(); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index fa0aa37bbd07..66d10b51dabc 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -482,7 +482,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR, "Failed to resolve stage location", e); } - final boolean quickInstall = (params.installFlags & PackageManager.INSTALL_QUICK) != 0; // Verify that stage looks sane with respect to existing application. // This currently only ensures packageName, versionCode, and certificate @@ -490,10 +489,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { validateInstallLocked(); Preconditions.checkNotNull(mPackageName); - // TODO: fix b/25118622; don't bypass signature check - if (!quickInstall) { - Preconditions.checkNotNull(mSignatures); - } + Preconditions.checkNotNull(mSignatures); Preconditions.checkNotNull(mResolvedBaseFile); if (!mPermissionsAccepted) { @@ -603,7 +599,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * {@link PackageManagerService}. */ private void validateInstallLocked() throws PackageManagerException { - final boolean quickInstall = (params.installFlags & PackageManager.INSTALL_QUICK) != 0; mPackageName = null; mVersionCode = -1; mSignatures = null; @@ -627,9 +622,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final ApkLite apk; try { - // TODO: fix b/25118622; always use PARSE_COLLECT_CERTIFICATES - final int parseFlags = quickInstall ? 0 : PackageParser.PARSE_COLLECT_CERTIFICATES; - apk = PackageParser.parseApkLite(file, parseFlags); + apk = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES); } catch (PackageParserException e) { throw PackageManagerException.from(e); } @@ -750,7 +743,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } private void assertApkConsistent(String tag, ApkLite apk) throws PackageManagerException { - final boolean quickInstall = (params.installFlags & PackageManager.INSTALL_QUICK) != 0; if (!mPackageName.equals(apk.packageName)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package " + apk.packageName + " inconsistent with " + mPackageName); @@ -760,8 +752,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { + " version code " + apk.versionCode + " inconsistent with " + mVersionCode); } - // TODO: fix b/25118622; don't bypass signature check - if (!quickInstall && !Signature.areExactMatch(mSignatures, apk.signatures)) { + if (!Signature.areExactMatch(mSignatures, apk.signatures)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " signatures are inconsistent"); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 42e8b018f004..dfb01eb1c99b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -10426,10 +10426,6 @@ public class PackageManagerService extends IPackageManager.Stub { if (!DEFAULT_VERIFY_ENABLE) { return false; } - // TODO: fix b/25118622; don't bypass verification - if (Build.IS_DEBUGGABLE && (installFlags & PackageManager.INSTALL_QUICK) != 0) { - return false; - } // Ephemeral apps don't get the full verification treatment if ((installFlags & PackageManager.INSTALL_EPHEMERAL) != 0) { if (DEBUG_EPHEMERAL) { @@ -12792,7 +12788,6 @@ public class PackageManagerService extends IPackageManager.Stub { final boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0); final boolean onExternal = (((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) || (args.volumeUuid != null)); - final boolean quickInstall = ((installFlags & PackageManager.INSTALL_QUICK) != 0); final boolean ephemeral = ((installFlags & PackageManager.INSTALL_EPHEMERAL) != 0); boolean replace = false; int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE; @@ -12818,7 +12813,6 @@ public class PackageManagerService extends IPackageManager.Stub { | PackageParser.PARSE_ENFORCE_CODE | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0) - | (quickInstall ? PackageParser.PARSE_SKIP_VERIFICATION : 0) | (ephemeral ? PackageParser.PARSE_IS_EPHEMERAL : 0); PackageParser pp = new PackageParser(); pp.setSeparateProcesses(mSeparateProcesses); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 7c42ae103e8a..901749ed874c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -141,6 +141,7 @@ class PackageManagerShellCommand extends ShellCommand { return 1; } abandonSession = false; + pw.println("Success"); return 0; } finally { if (abandonSession) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index ef2f29b67e20..da1e5468a649 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -65,7 +65,9 @@ import com.android.server.backup.PreferredActivityBackupHelper; import com.android.server.pm.PackageManagerService.DumpState; import com.android.server.pm.PermissionsState.PermissionState; +import java.io.BufferedInputStream; import java.io.FileNotFoundException; +import java.io.InputStream; import java.util.Collection; import org.xmlpull.v1.XmlPullParser; @@ -2799,9 +2801,9 @@ final class Settings { } if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Reading default preferred " + f); - FileInputStream str = null; + InputStream str = null; try { - str = new FileInputStream(f); + str = new BufferedInputStream(new FileInputStream(f)); XmlPullParser parser = Xml.newPullParser(); parser.setInput(str, null); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index f5da1032c718..13f48263b69e 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -526,6 +526,7 @@ public class UserManagerService extends IUserManager.Stub { if (parentHandle != null) { intent = new Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED); intent.putExtra(Intent.EXTRA_USER, profileHandle); + intent.putExtra(Intent.EXTRA_USER_HANDLE, profileHandle.getIdentifier()); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); intent.putExtras(extras); mContext.sendBroadcastAsUser(intent, parentHandle); @@ -2073,6 +2074,7 @@ public class UserManagerService extends IUserManager.Stub { managedProfileIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); managedProfileIntent.putExtra(Intent.EXTRA_USER, new UserHandle(removedUserId)); + managedProfileIntent.putExtra(Intent.EXTRA_USER_HANDLE, removedUserId); mContext.sendBroadcastAsUser(managedProfileIntent, new UserHandle(parentUserId), null); } diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index 7f4c42b5c686..9bf7ae4465ad 100644 --- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -17,6 +17,7 @@ package com.android.server.tv; import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED; +import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY; import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED; import android.content.BroadcastReceiver; @@ -102,7 +103,6 @@ class TvInputHardwareManager implements TvInputHal.Callback { private int mCurrentIndex = 0; private int mCurrentMaxIndex = 0; - // TODO: Should handle STANDBY case. private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray(); private final List<Message> mPendingHdmiDeviceEvents = new LinkedList<>(); @@ -206,7 +206,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { String inputId = mHardwareInputIdMap.get(deviceId); if (inputId != null) { mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, - convertConnectedToState(configs.length > 0), 0, inputId).sendToTarget(); + obtainStateFromConfigs(configs), 0, inputId).sendToTarget(); } ITvInputHardwareCallback callback = connection.getCallbackLocked(); if (callback != null) { @@ -256,12 +256,13 @@ class TvInputHardwareManager implements TvInputHal.Callback { || connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId; } - private int convertConnectedToState(boolean connected) { - if (connected) { - return INPUT_STATE_CONNECTED; - } else { - return INPUT_STATE_DISCONNECTED; + private int obtainStateFromConfigs(TvStreamConfig[] configs) { + for (TvStreamConfig config : configs) { + if ((config.getFlags() & TvStreamConfig.FLAG_MASK_SIGNAL_DETECTION) != 0) { + return INPUT_STATE_CONNECTED; + } } + return (configs.length > 0) ? INPUT_STATE_CONNECTED_STANDBY : INPUT_STATE_DISCONNECTED; } public void addHardwareTvInput(int deviceId, TvInputInfo info) { @@ -286,9 +287,14 @@ class TvInputHardwareManager implements TvInputHal.Callback { } String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); if (inputId != null && inputId.equals(info.getId())) { - mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, - convertConnectedToState(mHdmiStateMap.valueAt(i)), 0, - inputId).sendToTarget(); + // No HDMI hotplug does not necessarily mean disconnected, as old devices may + // not report hotplug state correctly. Using INPUT_STATE_CONNECTED_STANDBY to + // denote unknown state. + int state = mHdmiStateMap.valueAt(i) + ? INPUT_STATE_CONNECTED + : INPUT_STATE_CONNECTED_STANDBY; + mHandler.obtainMessage( + ListenerHandler.STATE_CHANGED, state, 0, inputId).sendToTarget(); return; } } @@ -296,7 +302,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { Connection connection = mConnections.get(deviceId); if (connection != null) { mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, - convertConnectedToState(connection.getConfigsLocked().length > 0), 0, + obtainStateFromConfigs(connection.getConfigsLocked()), 0, info.getId()).sendToTarget(); } } @@ -1110,8 +1116,14 @@ class TvInputHardwareManager implements TvInputHal.Callback { if (inputId == null) { return; } - mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, - convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget(); + // No HDMI hotplug does not necessarily mean disconnected, as old devices may + // not report hotplug state correctly. Using INPUT_STATE_CONNECTED_STANDBY to + // denote unknown state. + int state = event.isConnected() + ? INPUT_STATE_CONNECTED + : INPUT_STATE_CONNECTED_STANDBY; + mHandler.obtainMessage( + ListenerHandler.STATE_CHANGED, state, 0, inputId).sendToTarget(); } } } diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java index ab47f07262bd..82929976682c 100644 --- a/services/core/java/com/android/server/wm/AppWindowAnimator.java +++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java @@ -223,7 +223,7 @@ public class AppWindowAnimator { } if (DEBUG_LAYERS) Slog.v(TAG, "Updating layer " + w + ": " + winAnimator.mAnimLayer); if (w == mService.mInputMethodTarget && !mService.mInputMethodTargetWaitingAnim) { - mService.setInputMethodAnimLayerAdjustment(adj); + mService.mLayersController.setInputMethodAnimLayerAdjustment(adj); } wallpaperController.setAnimLayerAdjustment(w, adj); } diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index da894818519a..7b0a8d7c5d0c 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -35,7 +35,6 @@ import android.os.RemoteException; import android.util.Slog; import android.view.Display; import android.view.DragEvent; -import android.view.DropPermissionHolder; import android.view.InputChannel; import android.view.SurfaceControl; import android.view.View; @@ -54,6 +53,8 @@ import com.android.server.input.InputWindowHandle; import com.android.server.wm.WindowManagerService.DragInputEventReceiver; import com.android.server.wm.WindowManagerService.H; +import com.android.internal.view.IDropPermissions; + import java.util.ArrayList; /** @@ -428,7 +429,7 @@ class DragState { // Tell the drop target about the data. Returns 'true' if we can immediately // dispatch the global drag-ended message, 'false' if we need to wait for a // result from the recipient. - boolean notifyDropLw(WindowState touchedWin, DropPermissionHolder dropPermissionHolder, + boolean notifyDropLw(WindowState touchedWin, IDropPermissions dropPermissions, float x, float y) { if (mAnimation != null) { return false; @@ -449,7 +450,7 @@ class DragState { final int myPid = Process.myPid(); final IBinder token = touchedWin.mClient.asBinder(); DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y, - null, null, mData, dropPermissionHolder, false); + null, null, mData, dropPermissions, false); try { touchedWin.mClient.dispatchDragEvent(evt); @@ -516,7 +517,7 @@ class DragState { private static DragEvent obtainDragEvent(WindowState win, int action, float x, float y, Object localState, ClipDescription description, ClipData data, - DropPermissionHolder dropPermissionHolder, + IDropPermissions dropPermissions, boolean result) { float winX = x - win.mFrame.left; float winY = y - win.mFrame.top; @@ -525,7 +526,7 @@ class DragState { winY *= win.mGlobalScale; } return DragEvent.obtain(action, winX, winY, localState, description, data, - dropPermissionHolder, result); + dropPermissions, result); } boolean stepAnimationLocked(long currentTimeMs) { diff --git a/services/core/java/com/android/server/wm/DropPermissionsHandler.java b/services/core/java/com/android/server/wm/DropPermissionsHandler.java new file mode 100644 index 000000000000..2ac1ef4124b5 --- /dev/null +++ b/services/core/java/com/android/server/wm/DropPermissionsHandler.java @@ -0,0 +1,86 @@ +/* +** Copyright 2015, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package com.android.server.wm; + +import android.app.ActivityManagerNative; +import android.content.ClipData; +import android.net.Uri; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.internal.view.IDropPermissions; + +import java.util.ArrayList; + +class DropPermissionsHandler extends IDropPermissions.Stub { + + private final int mSourceUid; + private final String mTargetPackage; + private final int mMode; + private final int mSourceUserId; + private final int mTargetUserId; + + private final ArrayList<Uri> mUris = new ArrayList<Uri>(); + + private IBinder mPermissionOwner = null; + + DropPermissionsHandler(ClipData clipData, int sourceUid, String targetPackage, int mode, + int sourceUserId, int targetUserId) { + mSourceUid = sourceUid; + mTargetPackage = targetPackage; + mMode = mode; + mSourceUserId = sourceUserId; + mTargetUserId = targetUserId; + + clipData.collectUris(mUris); + } + + @Override + public void take() throws RemoteException { + if (mPermissionOwner != null) { + return; + } + + mPermissionOwner = ActivityManagerNative.getDefault().newUriPermissionOwner("drop"); + + long origId = Binder.clearCallingIdentity(); + try { + for (int i = 0; i < mUris.size(); i++) { + ActivityManagerNative.getDefault().grantUriPermissionFromOwner( + mPermissionOwner, mSourceUid, mTargetPackage, mUris.get(i), mMode, + mSourceUserId, mTargetUserId); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + @Override + public void release() throws RemoteException { + if (mPermissionOwner == null) { + return; + } + + for (int i = 0; i < mUris.size(); ++i) { + ActivityManagerNative.getDefault().revokeUriPermissionFromOwner( + mPermissionOwner, mUris.get(i), mMode, mSourceUserId); + } + + mPermissionOwner = null; + } +} diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 6a5183fa9c77..62d4f3611a74 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -782,7 +782,7 @@ public class WindowAnimator { SurfaceControl.openTransaction(); try { for (int i = mService.mDisplayContents.size() - 1; i >= 0; i--) { - DisplayContent display = mService.mDisplayContents.get(i); + DisplayContent display = mService.mDisplayContents.valueAt(i); final WindowList windows = mService.getWindowListLocked(display.getDisplayId()); for (int j = windows.size() - 1; j >= 0; j--) { windows.get(j).maybeRemoveReplacedWindow(); diff --git a/services/core/java/com/android/server/wm/WindowLayersController.java b/services/core/java/com/android/server/wm/WindowLayersController.java new file mode 100644 index 000000000000..4a77b2225cf8 --- /dev/null +++ b/services/core/java/com/android/server/wm/WindowLayersController.java @@ -0,0 +1,202 @@ +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowManagerService.WINDOW_LAYER_MULTIPLIER; + +import android.app.ActivityManager.StackId; +import android.util.Slog; +import android.view.Display; + +import java.io.PrintWriter; + +/** + * Controller for assigning layers to windows on the display. + * + * This class encapsulates general algorithm for assigning layers and special rules that we need to + * apply on top. The general algorithm goes through windows from bottom to the top and the higher + * the window is, the higher layer is assigned. The final layer is equal to base layer + + * adjustment from the order. This means that the window list is assumed to be ordered roughly by + * the base layer (there are exceptions, e.g. due to keyguard and wallpaper and they need to be + * handled with care, because they break the algorithm). + * + * On top of the general algorithm we add special rules, that govern such amazing things as: + * <li>IME (which has higher base layer, but will be positioned above application windows)</li> + * <li>docked/pinned windows (that need to be lifted above other application windows, including + * animations) + * <li>dock divider (which needs to live above applications, but below IME)</li> + * <li>replaced windows, which need to live above their normal level, because they anticipate + * an animation</li>. + */ +public class WindowLayersController { + private final WindowManagerService mService; + + private int mInputMethodAnimLayerAdjustment; + + public WindowLayersController(WindowManagerService service) { + mService = service; + } + + private int mHighestApplicationLayer = 0; + private WindowState mPinnedWindow = null; + private WindowState mDockedWindow = null; + private WindowState mDockDivider = null; + private WindowState mImeWindow = null; + private WindowState mReplacingWindow = null; + + final void assignLayersLocked(WindowList windows) { + if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based on windows=" + windows, + new RuntimeException("here").fillInStackTrace()); + + clear(); + int curBaseLayer = 0; + int curLayer = 0; + boolean anyLayerChanged = false; + for (int i = 0, windowCount = windows.size(); i < windowCount; i++) { + final WindowState w = windows.get(i); + boolean layerChanged = false; + + int oldLayer = w.mLayer; + if (w.mBaseLayer == curBaseLayer || w.mIsImWindow || (i > 0 && w.mIsWallpaper)) { + curLayer += WINDOW_LAYER_MULTIPLIER; + w.mLayer = curLayer; + } else { + curBaseLayer = curLayer = w.mBaseLayer; + w.mLayer = curLayer; + } + if (w.mLayer != oldLayer) { + layerChanged = true; + anyLayerChanged = true; + } + + final WindowStateAnimator winAnimator = w.mWinAnimator; + oldLayer = winAnimator.mAnimLayer; + winAnimator.mAnimLayer = w.mLayer + w.getAnimLayerAdjustment() + + getSpecialWindowAnimLayerAdjustment(w); + if (winAnimator.mAnimLayer != oldLayer) { + layerChanged = true; + anyLayerChanged = true; + } + + if (w.mAppToken != null) { + mHighestApplicationLayer = Math.max(mHighestApplicationLayer, + winAnimator.mAnimLayer); + } + collectSpecialWindows(w); + + if (layerChanged) { + w.scheduleAnimationIfDimming(); + } + } + + adjustSpecialWindows(); + + //TODO (multidisplay): Magnification is supported only for the default display. + if (mService.mAccessibilityController != null && anyLayerChanged + && windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) { + mService.mAccessibilityController.onWindowLayersChangedLocked(); + } + + if (DEBUG_LAYERS) logDebugLayers(windows); + } + + void setInputMethodAnimLayerAdjustment(int adj) { + if (DEBUG_LAYERS) Slog.v(TAG_WM, "Setting im layer adj to " + adj); + mInputMethodAnimLayerAdjustment = adj; + final WindowState imw = mService.mInputMethodWindow; + if (imw != null) { + imw.mWinAnimator.mAnimLayer = imw.mLayer + adj; + if (DEBUG_LAYERS) Slog.v(TAG_WM, "IM win " + imw + + " anim layer: " + imw.mWinAnimator.mAnimLayer); + for (int i = imw.mChildWindows.size() - 1; i >= 0; i--) { + final WindowState childWindow = imw.mChildWindows.get(i); + childWindow.mWinAnimator.mAnimLayer = childWindow.mLayer + adj; + if (DEBUG_LAYERS) Slog.v(TAG_WM, "IM win " + childWindow + + " anim layer: " + childWindow.mWinAnimator.mAnimLayer); + } + } + for (int i = mService.mInputMethodDialogs.size() - 1; i >= 0; i--) { + final WindowState dialog = mService.mInputMethodDialogs.get(i); + dialog.mWinAnimator.mAnimLayer = dialog.mLayer + adj; + if (DEBUG_LAYERS) Slog.v(TAG_WM, "IM win " + imw + + " anim layer: " + dialog.mWinAnimator.mAnimLayer); + } + } + + int getSpecialWindowAnimLayerAdjustment(WindowState win) { + if (win.mIsImWindow) { + return mInputMethodAnimLayerAdjustment; + } else if (win.mIsWallpaper) { + return mService.mWallpaperControllerLocked.getAnimLayerAdjustment(); + } + return 0; + } + + private void logDebugLayers(WindowList windows) { + for (int i = 0, n = windows.size(); i < n; i++) { + final WindowState w = windows.get(i); + final WindowStateAnimator winAnimator = w.mWinAnimator; + Slog.v(TAG_WM, "Assign layer " + w + ": " + "mBase=" + w.mBaseLayer + + " mLayer=" + w.mLayer + (w.mAppToken == null + ? "" : " mAppLayer=" + w.mAppToken.mAppAnimator.animLayerAdjustment) + + " =mAnimLayer=" + winAnimator.mAnimLayer); + } + } + + private void clear() { + mHighestApplicationLayer = 0; + mImeWindow = null; + mPinnedWindow = null; + mDockedWindow = null; + mDockDivider = null; + } + + private void collectSpecialWindows(WindowState w) { + if (w.mIsImWindow) { + mImeWindow = w; + } else if (w.mAttrs.type == TYPE_DOCK_DIVIDER) { + mDockDivider = w; + } else { + final TaskStack stack = w.getStack(); + if (stack.mStackId == StackId.PINNED_STACK_ID) { + mPinnedWindow = w; + } else if (stack.mStackId == StackId.DOCKED_STACK_ID) { + mDockedWindow = w; + } + } + } + + private void adjustSpecialWindows() { + int layer = mHighestApplicationLayer + 1; + // For pinned and docked stack window, we want to make them above other windows + // also when these windows are animating. + layer = assignAndIncreaseLayerIfNeeded(mDockedWindow, layer); + layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer); + // We know that we will be animating a relaunching window in the near future, + // which will receive a z-order increase. We want the replaced window to + // immediately receive the same treatment, e.g. to be above the dock divider. + layer = assignAndIncreaseLayerIfNeeded(mReplacingWindow, layer); + layer = assignAndIncreaseLayerIfNeeded(mPinnedWindow, layer); + layer = assignAndIncreaseLayerIfNeeded(mImeWindow, layer); + } + + private int assignAndIncreaseLayerIfNeeded(WindowState win, int layer) { + if (win != null) { + win.mLayer = layer; + win.mWinAnimator.mAnimLayer = layer; + layer++; + } + return layer; + } + + void dump(PrintWriter pw, String s) { + if (mInputMethodAnimLayerAdjustment != 0 || + mService.mWallpaperControllerLocked.getAnimLayerAdjustment() != 0) { + pw.print(" mInputMethodAnimLayerAdjustment="); + pw.print(mInputMethodAnimLayerAdjustment); + pw.print(" mWallpaperAnimLayerAdjustment="); + pw.println(mService.mWallpaperControllerLocked.getAnimLayerAdjustment()); + } + } +} diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 456c416be361..6385caac3fa9 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -66,7 +66,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEYGUARD; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RESIZE; @@ -92,7 +91,6 @@ import android.Manifest; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityManager.StackId; import android.app.ActivityManagerNative; import android.app.AppOpsManager; import android.app.IActivityManager; @@ -153,7 +151,6 @@ import android.view.AppTransitionAnimationSpec; import android.view.Choreographer; import android.view.Display; import android.view.DisplayInfo; -import android.view.DropPermissionHolder; import android.view.Gravity; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IApplicationToken; @@ -567,7 +564,6 @@ public class WindowManagerService extends IWindowManager.Stub /** If true hold off on modifying the animation layer of mInputMethodTarget */ boolean mInputMethodTargetWaitingAnim; - int mInputMethodAnimLayerAdjustment; WindowState mInputMethodWindow = null; final ArrayList<WindowState> mInputMethodDialogs = new ArrayList<>(); @@ -609,6 +605,8 @@ public class WindowManagerService extends IWindowManager.Stub WallpaperController mWallpaperControllerLocked; + final WindowLayersController mLayersController; + boolean mAnimateWallpaperWithTarget; AppWindowToken mFocusedApp = null; @@ -758,13 +756,12 @@ public class WindowManagerService extends IWindowManager.Stub private boolean completeDropLw(float x, float y) { WindowState dropTargetWin = mDragState.getDropTargetWinLw(x, y); - DropPermissionHolder dropPermissionHolder = null; + DropPermissionsHandler dropPermissions = null; if (dropTargetWin != null && (mDragState.mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mDragState.mFlags & DRAG_FLAGS_URI_ACCESS) != 0) { - dropPermissionHolder = new DropPermissionHolder( + dropPermissions = new DropPermissionsHandler( mDragState.mData, - mActivityManager, mDragState.mUid, dropTargetWin.getOwningPackage(), mDragState.mFlags & DRAG_FLAGS_URI_PERMISSIONS, @@ -772,7 +769,7 @@ public class WindowManagerService extends IWindowManager.Stub UserHandle.getUserId(dropTargetWin.getOwningUid())); } - return mDragState.notifyDropLw(dropTargetWin, dropPermissionHolder, x, y); + return mDragState.notifyDropLw(dropTargetWin, dropPermissions, x, y); } /** @@ -882,6 +879,7 @@ public class WindowManagerService extends IWindowManager.Stub mWallpaperControllerLocked = new WallpaperController(this); mWindowPlacerLocked = new WindowSurfacePlacer(this); + mLayersController = new WindowLayersController(this); LocalServices.addService(WindowManagerPolicy.class, mPolicy); @@ -1511,9 +1509,10 @@ public class WindowManagerService extends IWindowManager.Stub mInputMethodTarget = w; mInputMethodTargetWaitingAnim = false; if (w.mAppToken != null) { - setInputMethodAnimLayerAdjustment(w.mAppToken.mAppAnimator.animLayerAdjustment); + mLayersController.setInputMethodAnimLayerAdjustment( + w.mAppToken.mAppAnimator.animLayerAdjustment); } else { - setInputMethodAnimLayerAdjustment(0); + mLayersController.setInputMethodAnimLayerAdjustment(0); } } return i+1; @@ -1522,7 +1521,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to null." + (HIDE_STACK_CRAWLS ? "" : " Callers=" + Debug.getCallers(4))); mInputMethodTarget = null; - setInputMethodAnimLayerAdjustment(0); + mLayersController.setInputMethodAnimLayerAdjustment(0); } return -1; } @@ -1544,33 +1543,6 @@ public class WindowManagerService extends IWindowManager.Stub moveInputMethodDialogsLocked(pos); } - void setInputMethodAnimLayerAdjustment(int adj) { - if (DEBUG_LAYERS) Slog.v(TAG_WM, "Setting im layer adj to " + adj); - mInputMethodAnimLayerAdjustment = adj; - WindowState imw = mInputMethodWindow; - if (imw != null) { - imw.mWinAnimator.mAnimLayer = imw.mLayer + adj; - if (DEBUG_LAYERS) Slog.v(TAG_WM, "IM win " + imw - + " anim layer: " + imw.mWinAnimator.mAnimLayer); - int wi = imw.mChildWindows.size(); - while (wi > 0) { - wi--; - WindowState cw = imw.mChildWindows.get(wi); - cw.mWinAnimator.mAnimLayer = cw.mLayer + adj; - if (DEBUG_LAYERS) Slog.v(TAG_WM, "IM win " + cw - + " anim layer: " + cw.mWinAnimator.mAnimLayer); - } - } - int di = mInputMethodDialogs.size(); - while (di > 0) { - di --; - imw = mInputMethodDialogs.get(di); - imw.mWinAnimator.mAnimLayer = imw.mLayer + adj; - if (DEBUG_LAYERS) Slog.v(TAG_WM, "IM win " + imw - + " anim layer: " + imw.mWinAnimator.mAnimLayer); - } - } - private int tmpRemoveWindowLocked(int interestingPos, WindowState win) { WindowList windows = win.getWindowList(); int wpos = windows.indexOf(win); @@ -1769,7 +1741,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (needAssignLayers) { - assignLayersLocked(windows); + mLayersController.assignLayersLocked(windows); } return true; @@ -2061,7 +2033,7 @@ public class WindowManagerService extends IWindowManager.Stub moveInputMethodWindowsIfNeededLocked(false); } - assignLayersLocked(displayContent.getWindowList()); + mLayersController.assignLayersLocked(displayContent.getWindowList()); // Don't do layout here, the window must call // relayout to be displayed, so we'll do it there. @@ -2388,7 +2360,7 @@ public class WindowManagerService extends IWindowManager.Stub if (windows != null) { windows.remove(win); if (!mWindowPlacerLocked.isInLayout()) { - assignLayersLocked(windows); + mLayersController.assignLayersLocked(windows); win.setDisplayLayoutNeeded(); mWindowPlacerLocked.performSurfacePlacement(); if (win.mAppToken != null) { @@ -2743,7 +2715,7 @@ public class WindowManagerService extends IWindowManager.Stub // its layer recomputed. However, if the IME was hidden // and isn't actually moved in the list, its layer may be // out of data so we make sure to recompute it. - assignLayersLocked(win.getWindowList()); + mLayersController.assignLayersLocked(win.getWindowList()); } if (wallpaperMayMove) { @@ -4586,7 +4558,7 @@ public class WindowManagerService extends IWindowManager.Stub if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/)) { - assignLayersLocked(displayContent.getWindowList()); + mLayersController.assignLayersLocked(displayContent.getWindowList()); } mInputMonitor.setUpdateInputWindowsNeededLw(); @@ -8662,102 +8634,6 @@ public class WindowManagerService extends IWindowManager.Stub Arrays.fill(mRebuildTmp, null); } - final void assignLayersLocked(WindowList windows) { - int N = windows.size(); - int curBaseLayer = 0; - int curLayer = 0; - int i; - - if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based on windows=" + windows, - new RuntimeException("here").fillInStackTrace()); - - boolean anyLayerChanged = false; - - for (i=0; i<N; i++) { - final WindowState w = windows.get(i); - final WindowStateAnimator winAnimator = w.mWinAnimator; - boolean layerChanged = false; - int oldLayer = w.mLayer; - if (w.mBaseLayer == curBaseLayer || w.mIsImWindow || (i > 0 && w.mIsWallpaper)) { - curLayer += WINDOW_LAYER_MULTIPLIER; - w.mLayer = curLayer; - } else { - curBaseLayer = curLayer = w.mBaseLayer; - w.mLayer = curLayer; - } - if (w.mLayer != oldLayer) { - layerChanged = true; - anyLayerChanged = true; - } - final AppWindowToken wtoken = w.mAppToken; - oldLayer = winAnimator.mAnimLayer; - if (w.mTargetAppToken != null) { - winAnimator.mAnimLayer = - w.mLayer + w.mTargetAppToken.mAppAnimator.animLayerAdjustment; - } else if (wtoken != null) { - winAnimator.mAnimLayer = - w.mLayer + wtoken.mAppAnimator.animLayerAdjustment; - forceHigherLayerIfNeeded(w, winAnimator, wtoken); - } else { - winAnimator.mAnimLayer = w.mLayer; - } - if (w.mIsImWindow) { - winAnimator.mAnimLayer += mInputMethodAnimLayerAdjustment; - } else if (w.mIsWallpaper) { - winAnimator.mAnimLayer += mWallpaperControllerLocked.getAnimLayerAdjustment(); - } - if (winAnimator.mAnimLayer != oldLayer) { - layerChanged = true; - anyLayerChanged = true; - } - final DimLayer.DimLayerUser dimLayerUser = w.getDimLayerUser(); - final DisplayContent displayContent = w.getDisplayContent(); - if (layerChanged && dimLayerUser != null && displayContent != null && - displayContent.mDimLayerController.isDimming(dimLayerUser, winAnimator)) { - // Force an animation pass just to update the mDimLayer layer. - scheduleAnimationLocked(); - } - if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assign layer " + w + ": " - + "mBase=" + w.mBaseLayer - + " mLayer=" + w.mLayer - + (wtoken == null ? - "" : " mAppLayer=" + wtoken.mAppAnimator.animLayerAdjustment) - + " =mAnimLayer=" + winAnimator.mAnimLayer); - //System.out.println( - // "Assigned layer " + curLayer + " to " + w.mClient.asBinder()); - } - - //TODO (multidisplay): Magnification is supported only for the default display. - if (mAccessibilityController != null && anyLayerChanged - && windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) { - mAccessibilityController.onWindowLayersChangedLocked(); - } - } - - private void forceHigherLayerIfNeeded(WindowState w, WindowStateAnimator winAnimator, - AppWindowToken wtoken) { - boolean force = false; - - if (w.mWillReplaceWindow) { - // We know that we will be animating a relaunching window in the near future, - // which will receive a z-order increase. We want the replaced window to - // immediately receive the same treatment, e.g. to be above the dock divider. - force = true; - } - if (!force) { - final TaskStack stack = w.getStack(); - if (stack != null && (StackId.shouldIncreaseApplicationWindowLayer(stack.mStackId))) { - // For pinned and docked stack window, we want to make them above other windows - // also when these windows are animating. - force = true; - } - } - if (force) { - w.mLayer += TYPE_LAYER_OFFSET; - winAnimator.mAnimLayer += TYPE_LAYER_OFFSET; - } - } - void makeWindowFreezingScreenIfNeededLocked(WindowState w) { // If the screen is currently frozen or off, then keep // it frozen/off until this window draws at its new @@ -9128,7 +9004,7 @@ public class WindowManagerService extends IWindowManager.Stub } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) { // Client will do the layout, but we need to assign layers // for handleNewWindowLocked() below. - assignLayersLocked(displayContent.getWindowList()); + mLayersController.assignLayersLocked(displayContent.getWindowList()); } } @@ -9816,13 +9692,7 @@ public class WindowManagerService extends IWindowManager.Stub } mWindowPlacerLocked.dump(pw, " "); mWallpaperControllerLocked.dump(pw, " "); - if (mInputMethodAnimLayerAdjustment != 0 || - mWallpaperControllerLocked.getAnimLayerAdjustment() != 0) { - pw.print(" mInputMethodAnimLayerAdjustment="); - pw.print(mInputMethodAnimLayerAdjustment); - pw.print(" mWallpaperAnimLayerAdjustment="); - pw.println(mWallpaperControllerLocked.getAnimLayerAdjustment()); - } + mLayersController.dump(pw, " "); pw.print(" mSystemBooted="); pw.print(mSystemBooted); pw.print(" mDisplayEnabled="); pw.println(mDisplayEnabled); if (needsLayout()) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index e4a6806621ff..c63618527910 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -74,6 +74,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; @@ -603,9 +604,15 @@ final class WindowState implements WindowManagerPolicy.WindowState { mHaveFrame = true; final Task task = getTask(); - final boolean nonFullscreenTask = task != null && !task.isFullscreen(); + final boolean fullscreenTask = task == null || task.isFullscreen(); final boolean freeformWorkspace = task != null && task.inFreeformWorkspace(); - if (nonFullscreenTask) { + + if (fullscreenTask || (isChildWindow() + && (mAttrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) != 0)) { + // We use the parent frame as the containing frame for fullscreen and child windows + mContainingFrame.set(pf); + mDisplayFrame.set(df); + } else { task.getBounds(mContainingFrame); final WindowState imeWin = mService.mInputMethodWindow; if (imeWin != null && imeWin.isVisibleNow() && mService.mInputMethodTarget == this @@ -623,9 +630,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } mDisplayFrame.set(mContainingFrame); - } else { - mContainingFrame.set(pf); - mDisplayFrame.set(df); } final int pw = mContainingFrame.width(); @@ -1273,6 +1277,29 @@ final class WindowState implements WindowManagerPolicy.WindowState { mHasSurface = hasSurface; } + int getAnimLayerAdjustment() { + if (mTargetAppToken != null) { + return mTargetAppToken.mAppAnimator.animLayerAdjustment; + } else if (mAppToken != null) { + return mAppToken.mAppAnimator.animLayerAdjustment; + } else { + // Nothing is animating, so there is no animation adjustment. + return 0; + } + } + + void scheduleAnimationIfDimming() { + if (mDisplayContent == null) { + return; + } + final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser(); + if (dimLayerUser != null && mDisplayContent.mDimLayerController.isDimming( + dimLayerUser, mWinAnimator)) { + // Force an animation pass just to update the mDimLayer layer. + mService.scheduleAnimationLocked(); + } + } + private final class DeadWindowEventReceiver extends InputEventReceiver { DeadWindowEventReceiver(InputChannel inputChannel) { super(inputChannel, mService.mH.getLooper()); @@ -2253,7 +2280,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { } if (nonFullscreenTask) { - // Make sure window fits in containing frame since it is in a non-fullscreen stack as + // Make sure window fits in containing frame since it is in a non-fullscreen task as // required by {@link Gravity#apply} call. w = Math.min(w, pw); h = Math.min(h, ph); diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index d4001cdf4651..7605af0bf811 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -384,12 +384,8 @@ class WindowStateAnimator { if (mAnimator.mWindowDetachedWallpaper == mWin) { mAnimator.mWindowDetachedWallpaper = null; } - mAnimLayer = mWin.mLayer; - if (mWin.mIsImWindow) { - mAnimLayer += mService.mInputMethodAnimLayerAdjustment; - } else if (mIsWallpaper) { - mAnimLayer += mWallpaperControllerLocked.getAnimLayerAdjustment(); - } + mAnimLayer = mWin.mLayer + + mService.mLayersController.getSpecialWindowAnimLayerAdjustment(mWin); if (DEBUG_LAYERS) Slog.v(TAG, "Stepping win " + this + " anim layer: " + mAnimLayer); mHasTransformation = false; mHasLocalTransformation = false; diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index 160c97f1e62b..cbfb201a529c 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -524,7 +524,7 @@ class WindowSurfacePlacer { } for (DisplayContent displayContent : displayList) { - mService.assignLayersLocked(displayContent.getWindowList()); + mService.mLayersController.assignLayersLocked(displayContent.getWindowList()); displayContent.layoutNeeded = true; } } @@ -599,7 +599,7 @@ class WindowSurfacePlacer { if ((displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0 && mWallpaperControllerLocked.adjustWallpaperWindows()) { - mService.assignLayersLocked(windows); + mService.mLayersController.assignLayersLocked(windows); displayContent.layoutNeeded = true; } @@ -1134,7 +1134,7 @@ class WindowSurfacePlacer { // TODO(multidisplay): IMEs are only supported on the default display. if (windows == mService.getDefaultWindowListLocked() && !mService.moveInputMethodWindowsIfNeededLocked(true)) { - mService.assignLayersLocked(windows); + mService.mLayersController.assignLayersLocked(windows); } mService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, true /*updateInputWindows*/); diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp index 01acdeffb853..6c640ba55042 100644 --- a/services/core/jni/com_android_server_tv_TvInputHal.cpp +++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp @@ -54,6 +54,7 @@ static struct { jmethodID maxWidth; jmethodID maxHeight; jmethodID generation; + jmethodID flags; jmethodID build; } gTvStreamConfigBuilderClassInfo; @@ -239,7 +240,7 @@ public: int addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface); int removeStream(int deviceId, int streamId); - const tv_stream_config_t* getStreamConfigs(int deviceId, int* numConfigs); + const tv_stream_config_ext_t* getStreamConfigs(int deviceId, int* numConfigs); void onDeviceAvailable(const tv_input_device_info_t& info); void onDeviceUnavailable(int deviceId); @@ -288,10 +289,15 @@ private: sp<Looper> mLooper; KeyedVector<int, KeyedVector<int, Connection> > mConnections; + + tv_stream_config_ext_t* mConfigBuffer; + int mConfigBufferSize; }; JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* device, - const sp<Looper>& looper) { + const sp<Looper>& looper) + : mConfigBuffer(NULL), + mConfigBufferSize(0) { mThiz = env->NewWeakGlobalRef(thiz); mDevice = device; mCallback.notify = &JTvInputHal::notify; @@ -306,6 +312,10 @@ JTvInputHal::~JTvInputHal() { JNIEnv* env = AndroidRuntime::getJNIEnv(); env->DeleteWeakGlobalRef(mThiz); mThiz = NULL; + + if (mConfigBuffer != NULL) { + delete[] mConfigBuffer; + } } JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper) { @@ -354,15 +364,14 @@ int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface> if (connection.mSourceHandle == NULL && connection.mThread == NULL) { // Need to configure stream int numConfigs = 0; - const tv_stream_config_t* configs = NULL; - if (mDevice->get_stream_configurations( - mDevice, deviceId, &numConfigs, &configs) != 0) { + const tv_stream_config_ext_t* configs = getStreamConfigs(deviceId, &numConfigs); + if (configs == NULL) { ALOGE("Couldn't get stream configs"); return UNKNOWN_ERROR; } int configIndex = -1; for (int i = 0; i < numConfigs; ++i) { - if (configs[i].stream_id == streamId) { + if (configs[i].config.stream_id == streamId) { configIndex = i; break; } @@ -371,13 +380,13 @@ int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface> ALOGE("Cannot find a config with given stream ID: %d", streamId); return BAD_VALUE; } - connection.mStreamType = configs[configIndex].type; + connection.mStreamType = configs[configIndex].config.type; tv_stream_t stream; - stream.stream_id = configs[configIndex].stream_id; + stream.stream_id = configs[configIndex].config.stream_id; if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) { - stream.buffer_producer.width = configs[configIndex].max_video_width; - stream.buffer_producer.height = configs[configIndex].max_video_height; + stream.buffer_producer.width = configs[configIndex].config.max_video_width; + stream.buffer_producer.height = configs[configIndex].config.max_video_height; } if (mDevice->open_stream(mDevice, deviceId, &stream) != 0) { ALOGE("Couldn't add stream"); @@ -431,12 +440,33 @@ int JTvInputHal::removeStream(int deviceId, int streamId) { return NO_ERROR; } -const tv_stream_config_t* JTvInputHal::getStreamConfigs(int deviceId, int* numConfigs) { - const tv_stream_config_t* configs = NULL; - if (mDevice->get_stream_configurations( - mDevice, deviceId, numConfigs, &configs) != 0) { - ALOGE("Couldn't get stream configs"); - return NULL; +const tv_stream_config_ext_t* JTvInputHal::getStreamConfigs(int deviceId, int* numConfigs) { + const tv_stream_config_ext_t* configs = NULL; + if (mDevice->common.version >= TV_INPUT_DEVICE_API_VERSION_0_2) { + if (mDevice->get_stream_configurations_ext( + mDevice, deviceId, numConfigs, &configs) != 0) { + ALOGE("Couldn't get stream configs"); + return NULL; + } + } else { + const tv_stream_config_t* oldConfigs; + if (mDevice->get_stream_configurations( + mDevice, deviceId, numConfigs, &oldConfigs) != 0) { + ALOGE("Couldn't get stream configs"); + return NULL; + } + if (mConfigBufferSize < *numConfigs) { + mConfigBufferSize = (*numConfigs / 16 + 1) * 16; + if (mConfigBuffer != NULL) { + delete[] mConfigBuffer; + } + mConfigBuffer = new tv_stream_config_ext_t[mConfigBufferSize]; + } + for (int i = 0; i < *numConfigs; ++i) { + mConfigBuffer[i].config = oldConfigs[i]; + mConfigBuffer[i].flags = 0; + } + configs = mConfigBuffer; } return configs; } @@ -629,7 +659,7 @@ static jobjectArray nativeGetStreamConfigs(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId, jint generation) { JTvInputHal* tvInputHal = (JTvInputHal*)ptr; int numConfigs = 0; - const tv_stream_config_t* configs = tvInputHal->getStreamConfigs(deviceId, &numConfigs); + const tv_stream_config_ext_t* configs = tvInputHal->getStreamConfigs(deviceId, &numConfigs); jobjectArray result = env->NewObjectArray(numConfigs, gTvStreamConfigClassInfo.clazz, NULL); for (int i = 0; i < numConfigs; ++i) { @@ -637,15 +667,20 @@ static jobjectArray nativeGetStreamConfigs(JNIEnv* env, jclass clazz, gTvStreamConfigBuilderClassInfo.clazz, gTvStreamConfigBuilderClassInfo.constructor); env->CallObjectMethod( - builder, gTvStreamConfigBuilderClassInfo.streamId, configs[i].stream_id); + builder, gTvStreamConfigBuilderClassInfo.streamId, configs[i].config.stream_id); env->CallObjectMethod( - builder, gTvStreamConfigBuilderClassInfo.type, configs[i].type); + builder, gTvStreamConfigBuilderClassInfo.type, configs[i].config.type); env->CallObjectMethod( - builder, gTvStreamConfigBuilderClassInfo.maxWidth, configs[i].max_video_width); + builder, gTvStreamConfigBuilderClassInfo.maxWidth, + configs[i].config.max_video_width); env->CallObjectMethod( - builder, gTvStreamConfigBuilderClassInfo.maxHeight, configs[i].max_video_height); + builder, gTvStreamConfigBuilderClassInfo.maxHeight, + configs[i].config.max_video_height); env->CallObjectMethod( builder, gTvStreamConfigBuilderClassInfo.generation, generation); + env->CallObjectMethod( + builder, gTvStreamConfigBuilderClassInfo.flags, + configs[i].flags); jobject config = env->CallObjectMethod(builder, gTvStreamConfigBuilderClassInfo.build); @@ -737,6 +772,10 @@ int register_android_server_tv_TvInputHal(JNIEnv* env) { gTvStreamConfigBuilderClassInfo.clazz, "generation", "(I)Landroid/media/tv/TvStreamConfig$Builder;"); GET_METHOD_ID( + gTvStreamConfigBuilderClassInfo.flags, + gTvStreamConfigBuilderClassInfo.clazz, + "flags", "(I)Landroid/media/tv/TvStreamConfig$Builder;"); + GET_METHOD_ID( gTvStreamConfigBuilderClassInfo.build, gTvStreamConfigBuilderClassInfo.clazz, "build", "()Landroid/media/tv/TvStreamConfig;"); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 810ee6bcd598..2082911b58ac 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -16,8 +16,6 @@ package com.android.server.devicepolicy; -import com.google.android.collect.Sets; - import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; @@ -28,6 +26,8 @@ import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.END_TAG; import static org.xmlpull.v1.XmlPullParser.TEXT; +import com.google.android.collect.Sets; + import android.Manifest.permission; import android.accessibilityservice.AccessibilityServiceInfo; import android.accounts.AccountManager; @@ -193,6 +193,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String ATTR_PERMISSION_POLICY = "permission-policy"; private static final String ATTR_DELEGATED_CERT_INSTALLER = "delegated-cert-installer"; + private static final String ATTR_APPLICATION_RESTRICTIONS_MANAGER + = "application-restrictions-manager"; private static final int STATUS_BAR_DISABLE_MASK = StatusBarManager.DISABLE_EXPAND | @@ -322,6 +324,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean doNotAskCredentialsOnBoot = false; + String mApplicationRestrictionsManagingPackage; + public DevicePolicyData(int userHandle) { mUserHandle = userHandle; } @@ -1035,19 +1039,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { saveSettingsLocked(policy.mUserHandle); } - if (policy.mDelegatedCertInstallerPackage != null && - (packageName == null - || packageName.equals(policy.mDelegatedCertInstallerPackage))) { - try { - // Check if delegated cert installer package is removed. - if (mIPackageManager.getPackageInfo( - policy.mDelegatedCertInstallerPackage, 0, userHandle) == null) { - policy.mDelegatedCertInstallerPackage = null; - saveSettingsLocked(policy.mUserHandle); - } - } catch (RemoteException e) { - // Shouldn't happen - } + // Check if delegated cert installer or app restrictions managing packages are removed. + if (isRemovedPackage(packageName, policy.mDelegatedCertInstallerPackage, userHandle)) { + policy.mDelegatedCertInstallerPackage = null; + saveSettingsLocked(policy.mUserHandle); + } + if (isRemovedPackage( + packageName, policy.mApplicationRestrictionsManagingPackage, userHandle)) { + policy.mApplicationRestrictionsManagingPackage = null; + saveSettingsLocked(policy.mUserHandle); } } if (removed) { @@ -1056,6 +1056,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private boolean isRemovedPackage(String changedPackage, String targetPackage, int userHandle) { + try { + return targetPackage != null + && (changedPackage == null || changedPackage.equals(targetPackage)) + && mIPackageManager.getPackageInfo(targetPackage, 0, userHandle) == null; + } catch (RemoteException e) { + // Shouldn't happen + } + + return false; + } + /** * Unit test will subclass it to inject mocks. */ @@ -1162,6 +1174,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mContext.getSystemService(PowerManager.class).goToSleep(time, reason, flags); } + void powerManagerReboot(String reason) { + mContext.getSystemService(PowerManager.class).reboot(reason); + } + boolean systemPropertiesGetBoolean(String key, boolean def) { return SystemProperties.getBoolean(key, def); } @@ -1795,6 +1811,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.attribute(null, ATTR_DELEGATED_CERT_INSTALLER, policy.mDelegatedCertInstallerPackage); } + if (policy.mApplicationRestrictionsManagingPackage != null) { + out.attribute(null, ATTR_APPLICATION_RESTRICTIONS_MANAGER, + policy.mApplicationRestrictionsManagingPackage); + } final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { @@ -1920,6 +1940,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } policy.mDelegatedCertInstallerPackage = parser.getAttributeValue(null, ATTR_DELEGATED_CERT_INSTALLER); + policy.mApplicationRestrictionsManagingPackage = parser.getAttributeValue(null, + ATTR_APPLICATION_RESTRICTIONS_MANAGER); type = parser.next(); int outerDepth = parser.getDepth(); @@ -4815,6 +4837,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { DevicePolicyData policy = getUserData(userId); policy.mPermissionPolicy = DevicePolicyManager.PERMISSION_POLICY_PROMPT; policy.mDelegatedCertInstallerPackage = null; + policy.mApplicationRestrictionsManagingPackage = null; policy.mStatusBarDisabled = false; saveSettingsLocked(userId); @@ -5187,18 +5210,68 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public void setApplicationRestrictions(ComponentName who, String packageName, Bundle settings) { - Preconditions.checkNotNull(who, "ComponentName is null"); - final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId()); + public void setApplicationRestrictionsManagingPackage(ComponentName admin, String packageName) { + final int userHandle = mInjector.userHandleGetCallingUserId(); synchronized (this) { - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + DevicePolicyData policy = getUserData(userHandle); + policy.mApplicationRestrictionsManagingPackage = packageName; + saveSettingsLocked(userHandle); + } + } + + @Override + public String getApplicationRestrictionsManagingPackage(ComponentName admin) { + final int userHandle = mInjector.userHandleGetCallingUserId(); + synchronized (this) { + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + DevicePolicyData policy = getUserData(userHandle); + return policy.mApplicationRestrictionsManagingPackage; + } + } + + @Override + public boolean isCallerApplicationRestrictionsManagingPackage() { + final int callingUid = mInjector.binderGetCallingUid(); + final int userHandle = UserHandle.getUserId(callingUid); + synchronized (this) { + final DevicePolicyData policy = getUserData(userHandle); + if (policy.mApplicationRestrictionsManagingPackage == null) { + return false; + } - long id = mInjector.binderClearCallingIdentity(); try { - mUserManager.setApplicationRestrictions(packageName, settings, userHandle); - } finally { - mInjector.binderRestoreCallingIdentity(id); + int uid = mContext.getPackageManager().getPackageUid( + policy.mApplicationRestrictionsManagingPackage, userHandle); + return uid == callingUid; + } catch (NameNotFoundException e) { + return false; + } + } + } + + private void enforceCanManageApplicationRestrictions(ComponentName who) { + if (who != null) { + synchronized (this) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); } + } else if (!isCallerApplicationRestrictionsManagingPackage()) { + throw new SecurityException( + "No admin component given, and caller cannot manage application restrictions " + + "for other apps."); + } + } + + @Override + public void setApplicationRestrictions(ComponentName who, String packageName, Bundle settings) { + enforceCanManageApplicationRestrictions(who); + + final UserHandle userHandle = mInjector.binderGetCallingUserHandle(); + final long id = mInjector.binderClearCallingIdentity(); + try { + mUserManager.setApplicationRestrictions(packageName, settings, userHandle); + } finally { + mInjector.binderRestoreCallingIdentity(id); } } @@ -5764,21 +5837,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public Bundle getApplicationRestrictions(ComponentName who, String packageName) { - Preconditions.checkNotNull(who, "ComponentName is null"); - final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId()); + enforceCanManageApplicationRestrictions(who); - synchronized (this) { - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - - long id = mInjector.binderClearCallingIdentity(); - try { - Bundle bundle = mUserManager.getApplicationRestrictions(packageName, userHandle); - // if no restrictions were saved, mUserManager.getApplicationRestrictions - // returns null, but DPM method should return an empty Bundle as per JavaDoc - return bundle != null ? bundle : Bundle.EMPTY; - } finally { - mInjector.binderRestoreCallingIdentity(id); - } + final UserHandle userHandle = mInjector.binderGetCallingUserHandle(); + final long id = mInjector.binderClearCallingIdentity(); + try { + Bundle bundle = mUserManager.getApplicationRestrictions(packageName, userHandle); + // if no restrictions were saved, mUserManager.getApplicationRestrictions + // returns null, but DPM method should return an empty Bundle as per JavaDoc + return bundle != null ? bundle : Bundle.EMPTY; + } finally { + mInjector.binderRestoreCallingIdentity(id); } } @@ -7018,4 +7087,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return UserManager.isSplitSystemUser() && callingUserId == UserHandle.USER_SYSTEM; } + @Override + public void reboot(ComponentName admin) { + Preconditions.checkNotNull(admin); + // Make sure caller has DO. + synchronized (this) { + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } + long ident = mInjector.binderClearCallingIdentity(); + try { + mInjector.powerManagerReboot(PowerManager.REBOOT_REQUESTED_BY_DEVICE_OWNER); + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } + } + } diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java index 0a8c014c4d37..f18617ef4748 100644 --- a/services/print/java/com/android/server/print/PrintManagerService.java +++ b/services/print/java/com/android/server/print/PrintManagerService.java @@ -77,8 +77,8 @@ public final class PrintManagerService extends SystemService { } @Override - public void onStartUser(int userHandle) { - mPrintManagerImpl.handleUserStarted(userHandle); + public void onUnlockUser(int userHandle) { + mPrintManagerImpl.handleUserUnlocked(userHandle); } @Override @@ -473,14 +473,11 @@ public final class PrintManagerService extends SystemService { public void onChange(boolean selfChange, Uri uri, int userId) { if (enabledPrintServicesUri.equals(uri)) { synchronized (mLock) { - if (userId != UserHandle.USER_ALL) { - UserState userState = getOrCreateUserStateLocked(userId); - userState.updateIfNeededLocked(); - } else { - final int userCount = mUserStates.size(); - for (int i = 0; i < userCount; i++) { - UserState userState = mUserStates.valueAt(i); - userState.updateIfNeededLocked(); + final int userCount = mUserStates.size(); + for (int i = 0; i < userCount; i++) { + if (userId == UserHandle.USER_ALL + || userId == mUserStates.keyAt(i)) { + mUserStates.valueAt(i).updateIfNeededLocked(); } } } @@ -496,6 +493,7 @@ public final class PrintManagerService extends SystemService { PackageMonitor monitor = new PackageMonitor() { @Override public void onPackageModified(String packageName) { + if (!mUserManager.isUserUnlocked(getChangingUserId())) return; synchronized (mLock) { // A background user/profile's print jobs are running but there is // no UI shown. Hence, if the packages of such a user change we need @@ -517,6 +515,7 @@ public final class PrintManagerService extends SystemService { @Override public void onPackageRemoved(String packageName, int uid) { + if (!mUserManager.isUserUnlocked(getChangingUserId())) return; synchronized (mLock) { // A background user/profile's print jobs are running but there is // no UI shown. Hence, if the packages of such a user change we need @@ -544,6 +543,7 @@ public final class PrintManagerService extends SystemService { @Override public boolean onHandleForceStop(Intent intent, String[] stoppedPackages, int uid, boolean doit) { + if (!mUserManager.isUserUnlocked(getChangingUserId())) return false; synchronized (mLock) { // A background user/profile's print jobs are running but there is // no UI shown. Hence, if the packages of such a user change we need @@ -574,6 +574,8 @@ public final class PrintManagerService extends SystemService { @Override public void onPackageAdded(String packageName, int uid) { + if (!mUserManager.isUserUnlocked(getChangingUserId())) return; + // A background user/profile's print jobs are running but there is // no UI shown. Hence, if the packages of such a user change we need // to handle it as the change may affect ongoing print jobs. @@ -634,6 +636,11 @@ public final class PrintManagerService extends SystemService { } private UserState getOrCreateUserStateLocked(int userId) { + if (!mUserManager.isUserUnlocked(userId)) { + throw new IllegalStateException( + "User " + userId + " must be unlocked for printing to be available"); + } + UserState userState = mUserStates.get(userId); if (userState == null) { userState = new UserState(mContext, userId, mLock); @@ -642,7 +649,7 @@ public final class PrintManagerService extends SystemService { return userState; } - private void handleUserStarted(final int userId) { + private void handleUserUnlocked(final int userId) { // This code will touch the remote print spooler which // must be called off the main thread, so post the work. BackgroundThread.getHandler().post(new Runnable() { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 565ef4b72058..7747fd9a1cb5 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -944,6 +944,88 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg2").size()); } + public void testApplicationRestrictionsManagingApp() throws Exception { + setAsProfileOwner(admin1); + + final String appRestrictionsManagerPackage = "com.google.app.restrictions.manager"; + final int appRestrictionsManagerAppId = 20987; + final int appRestrictionsManagerUid = UserHandle.getUid( + DpmMockContext.CALLER_USER_HANDLE, appRestrictionsManagerAppId); + doReturn(appRestrictionsManagerUid).when(mContext.packageManager).getPackageUid( + eq(appRestrictionsManagerPackage), + eq(DpmMockContext.CALLER_USER_HANDLE)); + mContext.binder.callingUid = appRestrictionsManagerUid; + + // appRestrictionsManager package shouldn't be able to manage restrictions as the PO hasn't + // delegated that permission yet. + assertFalse(dpm.isCallerApplicationRestrictionsManagingPackage()); + Bundle rest = new Bundle(); + rest.putString("KEY_STRING", "Foo1"); + try { + dpm.setApplicationRestrictions(null, "pkg1", rest); + fail("Didn't throw expected SecurityException"); + } catch (SecurityException expected) { + MoreAsserts.assertContainsRegex( + "caller cannot manage application restrictions", expected.getMessage()); + } + try { + dpm.getApplicationRestrictions(null, "pkg1"); + fail("Didn't throw expected SecurityException"); + } catch (SecurityException expected) { + MoreAsserts.assertContainsRegex( + "caller cannot manage application restrictions", expected.getMessage()); + } + + // Check via the profile owner that no restrictions were set. + mContext.binder.callingUid = DpmMockContext.CALLER_UID; + assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg1").size()); + + // Let appRestrictionsManagerPackage manage app restrictions + dpm.setApplicationRestrictionsManagingPackage(admin1, appRestrictionsManagerPackage); + assertEquals(appRestrictionsManagerPackage, + dpm.getApplicationRestrictionsManagingPackage(admin1)); + + // Now that package should be able to set and retrieve app restrictions. + mContext.binder.callingUid = appRestrictionsManagerUid; + assertTrue(dpm.isCallerApplicationRestrictionsManagingPackage()); + dpm.setApplicationRestrictions(null, "pkg1", rest); + Bundle returned = dpm.getApplicationRestrictions(null, "pkg1"); + assertEquals(1, returned.size(), 1); + assertEquals("Foo1", returned.get("KEY_STRING")); + + // The same app running on a separate user shouldn't be able to manage app restrictions. + mContext.binder.callingUid = UserHandle.getUid( + UserHandle.USER_SYSTEM, appRestrictionsManagerAppId); + assertFalse(dpm.isCallerApplicationRestrictionsManagingPackage()); + try { + dpm.setApplicationRestrictions(null, "pkg1", rest); + fail("Didn't throw expected SecurityException"); + } catch (SecurityException expected) { + MoreAsserts.assertContainsRegex( + "caller cannot manage application restrictions", expected.getMessage()); + } + + // The DPM is still able to manage app restrictions, even if it allowed another app to do it + // too. + mContext.binder.callingUid = DpmMockContext.CALLER_UID; + assertEquals(returned, dpm.getApplicationRestrictions(admin1, "pkg1")); + dpm.setApplicationRestrictions(admin1, "pkg1", null); + assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg1").size()); + + // Removing the ability for the package to manage app restrictions. + dpm.setApplicationRestrictionsManagingPackage(admin1, null); + assertNull(dpm.getApplicationRestrictionsManagingPackage(admin1)); + mContext.binder.callingUid = appRestrictionsManagerUid; + assertFalse(dpm.isCallerApplicationRestrictionsManagingPackage()); + try { + dpm.setApplicationRestrictions(null, "pkg1", null); + fail("Didn't throw expected SecurityException"); + } catch (SecurityException expected) { + MoreAsserts.assertContainsRegex( + "caller cannot manage application restrictions", expected.getMessage()); + } + } + public void testSetUserRestriction_asDo() throws Exception { mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); mContext.callerPermissions.add(permission.MANAGE_USERS); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 3b281e222bd8..0b5aaa3b273b 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -766,7 +766,7 @@ public class TelephonyManager { IPhoneSubInfo info = getSubscriberInfo(); if (info == null) return null; - return info.getDeviceIdForPhone(slotId); + return info.getDeviceIdForPhone(slotId, mContext.getOpPackageName()); } catch (RemoteException ex) { return null; } catch (NullPointerException ex) { @@ -4865,4 +4865,4 @@ public class TelephonyManager { } return null; } -}
\ No newline at end of file +} diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl index ed85392dd4d7..dc2b297f6dbe 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl @@ -36,7 +36,7 @@ interface IPhoneSubInfo { * Retrieves the unique device ID of a phone for the device, e.g., IMEI * for GSM phones. */ - String getDeviceIdForPhone(int phoneId); + String getDeviceIdForPhone(int phoneId, String callingPackage); /** * Retrieves the IMEI. diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java index cf1a4aa97c6d..fecfdf98d0e7 100644 --- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java +++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.ContentResolver; import android.content.Intent; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; import android.os.Vibrator; @@ -180,6 +181,9 @@ public class NotificationTestList extends TestActivity new Test("with topic GoodBye") { public void run() { + Notification.BigPictureStyle picture = new Notification.BigPictureStyle(); + picture.bigPicture(BitmapFactory.decodeResource(getResources(), + R.id.large_icon_pineapple2)); Notification n = new Notification.Builder(NotificationTestList.this) .setSmallIcon(R.drawable.icon1) .setWhen(mActivityCreateTime) @@ -187,11 +191,29 @@ public class NotificationTestList extends TestActivity .setContentText("This is a notification!!!") .setContentIntent(makeIntent2()) .setTopic(new Notification.Topic("bye", "Goodbye")) + .setStyle(picture) .build(); mNM.notify(9999, n); } }, + new Test("with topic Bananas") { + public void run() { + Notification.BigTextStyle bigText = new Notification.BigTextStyle(); + bigText.bigText("bananas are great\nso tasty\nyum\nyum\nyum\n"); + Notification n = new Notification.Builder(NotificationTestList.this) + .setSmallIcon(R.drawable.icon1) + .setStyle(bigText) + .setWhen(mActivityCreateTime) + .setContentTitle("bananananana") + .setContentText("This is a banana!!!") + .setContentIntent(makeIntent2()) + .setTopic(new Notification.Topic("bananas", "Bananas")) + .build(); + + mNM.notify(999, n); + } + }, new Test("Whens") { public void run() |