diff options
172 files changed, 5339 insertions, 1612 deletions
diff --git a/api/current.txt b/api/current.txt index 9d0f2988fa81..2638bb62424a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5762,6 +5762,7 @@ package android.app.admin { method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName); method public long getMaximumTimeToLock(android.content.ComponentName); method public boolean getPackageSuspended(android.content.ComponentName, java.lang.String); + method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName); method public long getPasswordExpiration(android.content.ComponentName); method public long getPasswordExpirationTimeout(android.content.ComponentName); method public int getPasswordHistoryLength(android.content.ComponentName); @@ -9446,8 +9447,10 @@ package android.content.pm { method public abstract java.lang.String getNameForUid(int); method public android.content.pm.PackageInfo getPackageArchiveInfo(java.lang.String, int); method public abstract int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; + method public abstract int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.PackageInstaller getPackageInstaller(); + method public abstract int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract java.lang.String[] getPackagesForUid(int); method public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int); method public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; @@ -22452,6 +22455,7 @@ package android.mtp { method public int[] getObjectHandles(int, int, int); method public android.mtp.MtpObjectInfo getObjectInfo(int); method public long getParent(int); + method public int getPartialObject(int, int, int, byte[]) throws java.io.IOException; method public long getStorageId(int); method public int[] getStorageIds(); method public android.mtp.MtpStorageInfo getStorageInfo(int); @@ -30778,7 +30782,7 @@ package android.provider { field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type"; field public static final java.lang.String COLUMN_SIZE = "_size"; field public static final java.lang.String COLUMN_SUMMARY = "summary"; - field public static final int FLAG_ARCHIVE = 2048; // 0x800 + field public static final int FLAG_ARCHIVE = 1024; // 0x400 field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10 field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20 field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8 @@ -30787,9 +30791,8 @@ package android.provider { field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100 field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40 field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 - field public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 512; // 0x200 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 - field public static final int FLAG_VIRTUAL_DOCUMENT = 1024; // 0x400 + field public static final int FLAG_VIRTUAL_DOCUMENT = 512; // 0x200 field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory"; } @@ -31233,6 +31236,7 @@ package android.provider { field public static final java.lang.String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS"; field public static final java.lang.String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS"; field public static final java.lang.String ACTION_SETTINGS = "android.settings.SETTINGS"; + field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS"; field public static final java.lang.String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO"; field public static final java.lang.String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS"; field public static final java.lang.String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS"; @@ -36751,8 +36755,10 @@ package android.test.mock { method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String); method public java.lang.String getNameForUid(int); method public int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; + method public int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public android.content.pm.PackageInstaller getPackageInstaller(); + method public int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public java.lang.String[] getPackagesForUid(int); method public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int); method public android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; @@ -39180,7 +39186,7 @@ package android.util { method public static final java.lang.String digitsAndPlusOnly(java.util.regex.Matcher); field public static final java.util.regex.Pattern DOMAIN_NAME; field public static final java.util.regex.Pattern EMAIL_ADDRESS; - field public static final java.lang.String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef"; + field public static final deprecated java.lang.String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef"; field public static final java.util.regex.Pattern IP_ADDRESS; field public static final java.util.regex.Pattern PHONE; field public static final deprecated java.util.regex.Pattern TOP_LEVEL_DOMAIN; @@ -40559,7 +40565,6 @@ package android.view { field public static final int AXIS_RX = 12; // 0xc field public static final int AXIS_RY = 13; // 0xd field public static final int AXIS_RZ = 14; // 0xe - field public static final int AXIS_SCROLL = 26; // 0x1a field public static final int AXIS_SIZE = 3; // 0x3 field public static final int AXIS_THROTTLE = 19; // 0x13 field public static final int AXIS_TILT = 25; // 0x19 @@ -44046,7 +44051,7 @@ package android.webkit { method public abstract deprecated void setEnableSmoothTransition(boolean); method public abstract void setFantasyFontFamily(java.lang.String); method public abstract void setFixedFontFamily(java.lang.String); - method public abstract void setGeolocationDatabasePath(java.lang.String); + method public abstract deprecated void setGeolocationDatabasePath(java.lang.String); method public abstract void setGeolocationEnabled(boolean); method public abstract void setJavaScriptCanOpenWindowsAutomatically(boolean); method public abstract void setJavaScriptEnabled(boolean); diff --git a/api/system-current.txt b/api/system-current.txt index 5cbf06c0e96b..795d631f8909 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5890,6 +5890,7 @@ package android.app.admin { method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName); method public long getMaximumTimeToLock(android.content.ComponentName); method public boolean getPackageSuspended(android.content.ComponentName, java.lang.String); + method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName); method public long getPasswordExpiration(android.content.ComponentName); method public long getPasswordExpirationTimeout(android.content.ComponentName); method public int getPasswordHistoryLength(android.content.ComponentName); @@ -9747,8 +9748,10 @@ package android.content.pm { method public abstract java.lang.String getNameForUid(int); method public android.content.pm.PackageInfo getPackageArchiveInfo(java.lang.String, int); method public abstract int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; + method public abstract int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.PackageInstaller getPackageInstaller(); + method public abstract int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract java.lang.String[] getPackagesForUid(int); method public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int); method public abstract int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle); @@ -23998,6 +24001,7 @@ package android.mtp { method public int[] getObjectHandles(int, int, int); method public android.mtp.MtpObjectInfo getObjectInfo(int); method public long getParent(int); + method public int getPartialObject(int, int, int, byte[]) throws java.io.IOException; method public long getStorageId(int); method public int[] getStorageIds(); method public android.mtp.MtpStorageInfo getStorageInfo(int); @@ -32811,7 +32815,7 @@ package android.provider { field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type"; field public static final java.lang.String COLUMN_SIZE = "_size"; field public static final java.lang.String COLUMN_SUMMARY = "summary"; - field public static final int FLAG_ARCHIVE = 2048; // 0x800 + field public static final int FLAG_ARCHIVE = 1024; // 0x400 field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10 field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20 field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8 @@ -32820,9 +32824,8 @@ package android.provider { field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100 field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40 field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 - field public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 512; // 0x200 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 - field public static final int FLAG_VIRTUAL_DOCUMENT = 1024; // 0x400 + field public static final int FLAG_VIRTUAL_DOCUMENT = 512; // 0x200 field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory"; } @@ -33368,6 +33371,7 @@ package android.provider { field public static final java.lang.String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS"; field public static final java.lang.String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS"; field public static final java.lang.String ACTION_SETTINGS = "android.settings.SETTINGS"; + field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS"; field public static final java.lang.String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO"; field public static final java.lang.String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS"; field public static final java.lang.String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS"; @@ -39092,8 +39096,10 @@ package android.test.mock { method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String); method public java.lang.String getNameForUid(int); method public int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; + method public int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public android.content.pm.PackageInstaller getPackageInstaller(); + method public int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public java.lang.String[] getPackagesForUid(int); method public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int); method public int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle); @@ -41527,7 +41533,7 @@ package android.util { method public static final java.lang.String digitsAndPlusOnly(java.util.regex.Matcher); field public static final java.util.regex.Pattern DOMAIN_NAME; field public static final java.util.regex.Pattern EMAIL_ADDRESS; - field public static final java.lang.String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef"; + field public static final deprecated java.lang.String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef"; field public static final java.util.regex.Pattern IP_ADDRESS; field public static final java.util.regex.Pattern PHONE; field public static final deprecated java.util.regex.Pattern TOP_LEVEL_DOMAIN; @@ -42906,7 +42912,6 @@ package android.view { field public static final int AXIS_RX = 12; // 0xc field public static final int AXIS_RY = 13; // 0xd field public static final int AXIS_RZ = 14; // 0xe - field public static final int AXIS_SCROLL = 26; // 0x1a field public static final int AXIS_SIZE = 3; // 0x3 field public static final int AXIS_THROTTLE = 19; // 0x13 field public static final int AXIS_TILT = 25; // 0x19 @@ -46464,7 +46469,7 @@ package android.webkit { method public abstract deprecated void setEnableSmoothTransition(boolean); method public abstract void setFantasyFontFamily(java.lang.String); method public abstract void setFixedFontFamily(java.lang.String); - method public abstract void setGeolocationDatabasePath(java.lang.String); + method public abstract deprecated void setGeolocationDatabasePath(java.lang.String); method public abstract void setGeolocationEnabled(boolean); method public abstract void setJavaScriptCanOpenWindowsAutomatically(boolean); method public abstract void setJavaScriptEnabled(boolean); diff --git a/api/test-current.txt b/api/test-current.txt index 49779e8d6823..8199c0948b08 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5501,8 +5501,10 @@ package android.app { method public android.view.WindowAnimationFrameStats getWindowAnimationFrameStats(); method public android.view.WindowContentFrameStats getWindowContentFrameStats(int); method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows(); + method public boolean grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public boolean injectInputEvent(android.view.InputEvent, boolean); method public final boolean performGlobalAction(int); + method public boolean revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public void setOnAccessibilityEventListener(android.app.UiAutomation.OnAccessibilityEventListener); method public boolean setRotation(int); method public void setRunAsMonkey(boolean); @@ -5762,6 +5764,7 @@ package android.app.admin { method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName); method public long getMaximumTimeToLock(android.content.ComponentName); method public boolean getPackageSuspended(android.content.ComponentName, java.lang.String); + method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName); method public long getPasswordExpiration(android.content.ComponentName); method public long getPasswordExpirationTimeout(android.content.ComponentName); method public int getPasswordHistoryLength(android.content.ComponentName); @@ -7673,6 +7676,7 @@ package android.content { method public static java.util.List<android.content.PeriodicSync> getPeriodicSyncs(android.accounts.Account, java.lang.String); method public java.util.List<android.content.UriPermission> getPersistedUriPermissions(); method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String); + method public static java.lang.String[] getSyncAdapterPackagesForAuthorityAsUser(java.lang.String, int); method public static android.content.SyncAdapterType[] getSyncAdapterTypes(); method public static boolean getSyncAutomatically(android.accounts.Account, java.lang.String); method public final java.lang.String getType(android.net.Uri); @@ -7839,6 +7843,7 @@ package android.content { method public abstract java.lang.String getSystemServiceName(java.lang.Class<?>); method public final java.lang.CharSequence getText(int); method public abstract android.content.res.Resources.Theme getTheme(); + method public abstract int getUserId(); method public abstract deprecated android.graphics.drawable.Drawable getWallpaper(); method public abstract deprecated int getWallpaperDesiredMinimumHeight(); method public abstract deprecated int getWallpaperDesiredMinimumWidth(); @@ -8021,6 +8026,7 @@ package android.content { method public java.lang.Object getSystemService(java.lang.String); method public java.lang.String getSystemServiceName(java.lang.Class<?>); method public android.content.res.Resources.Theme getTheme(); + method public int getUserId(); method public deprecated android.graphics.drawable.Drawable getWallpaper(); method public deprecated int getWallpaperDesiredMinimumHeight(); method public deprecated int getWallpaperDesiredMinimumWidth(); @@ -9073,6 +9079,8 @@ package android.content.pm { ctor public ApplicationInfo(android.content.pm.ApplicationInfo); method public int describeContents(); method public void dump(android.util.Printer, java.lang.String); + method public boolean isPrivilegedApp(); + method public boolean isSystemApp(); method public java.lang.CharSequence loadDescription(android.content.pm.PackageManager); field public static final android.os.Parcelable.Creator<android.content.pm.ApplicationInfo> CREATOR; field public static final int FLAG_ALLOW_BACKUP = 32768; // 0x8000 @@ -9434,6 +9442,7 @@ package android.content.pm { method public abstract android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract int getComponentEnabledSetting(android.content.ComponentName); method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon(); + method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int); method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo); method public abstract byte[] getEphemeralCookie(); method public abstract int getEphemeralCookieMaxSizeBytes(); @@ -9446,8 +9455,10 @@ package android.content.pm { method public abstract java.lang.String getNameForUid(int); method public android.content.pm.PackageInfo getPackageArchiveInfo(java.lang.String, int); method public abstract int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; + method public abstract int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract android.content.pm.PackageInstaller getPackageInstaller(); + method public abstract int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract java.lang.String[] getPackagesForUid(int); method public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int); method public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; @@ -22452,6 +22463,7 @@ package android.mtp { method public int[] getObjectHandles(int, int, int); method public android.mtp.MtpObjectInfo getObjectInfo(int); method public long getParent(int); + method public int getPartialObject(int, int, int, byte[]) throws java.io.IOException; method public long getStorageId(int); method public int[] getStorageIds(); method public android.mtp.MtpStorageInfo getStorageInfo(int); @@ -28366,6 +28378,7 @@ package android.os { public final class UserHandle implements android.os.Parcelable { ctor public UserHandle(android.os.Parcel); method public int describeContents(); + method public static int getAppId(int); method public static android.os.UserHandle readFromParcel(android.os.Parcel); method public void writeToParcel(android.os.Parcel, int); method public static void writeToParcel(android.os.UserHandle, android.os.Parcel); @@ -30781,7 +30794,7 @@ package android.provider { field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type"; field public static final java.lang.String COLUMN_SIZE = "_size"; field public static final java.lang.String COLUMN_SUMMARY = "summary"; - field public static final int FLAG_ARCHIVE = 2048; // 0x800 + field public static final int FLAG_ARCHIVE = 1024; // 0x400 field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10 field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20 field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8 @@ -30790,9 +30803,8 @@ package android.provider { field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100 field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40 field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 - field public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 512; // 0x200 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 - field public static final int FLAG_VIRTUAL_DOCUMENT = 1024; // 0x400 + field public static final int FLAG_VIRTUAL_DOCUMENT = 512; // 0x200 field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory"; } @@ -31236,6 +31248,7 @@ package android.provider { field public static final java.lang.String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS"; field public static final java.lang.String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS"; field public static final java.lang.String ACTION_SETTINGS = "android.settings.SETTINGS"; + field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS"; field public static final java.lang.String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO"; field public static final java.lang.String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS"; field public static final java.lang.String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS"; @@ -31390,6 +31403,7 @@ package android.provider { field public static final deprecated java.lang.String TTS_USE_DEFAULTS = "tts_use_defaults"; field public static final deprecated java.lang.String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled"; field public static final deprecated java.lang.String USE_GOOGLE_MAIL = "use_google_mail"; + field public static final java.lang.String VOICE_INTERACTION_SERVICE = "voice_interaction_service"; field public static final deprecated java.lang.String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count"; field public static final deprecated java.lang.String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS = "wifi_mobile_data_transition_wakelock_timeout_ms"; field public static final deprecated java.lang.String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = "wifi_networks_available_notification_on"; @@ -31773,6 +31787,7 @@ package android.provider { field public static final int RESULT_SMS_OUT_OF_MEMORY = 3; // 0x3 field public static final int RESULT_SMS_UNSUPPORTED = 4; // 0x4 field public static final java.lang.String SIM_FULL_ACTION = "android.provider.Telephony.SIM_FULL"; + field public static final java.lang.String SMS_CARRIER_PROVISION_ACTION = "android.provider.Telephony.SMS_CARRIER_PROVISION"; field public static final java.lang.String SMS_CB_RECEIVED_ACTION = "android.provider.Telephony.SMS_CB_RECEIVED"; field public static final java.lang.String SMS_DELIVER_ACTION = "android.provider.Telephony.SMS_DELIVER"; field public static final java.lang.String SMS_EMERGENCY_CB_RECEIVED_ACTION = "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED"; @@ -36617,6 +36632,7 @@ package android.test.mock { method public java.lang.Object getSystemService(java.lang.String); method public java.lang.String getSystemServiceName(java.lang.Class<?>); method public android.content.res.Resources.Theme getTheme(); + method public int getUserId(); method public android.graphics.drawable.Drawable getWallpaper(); method public int getWallpaperDesiredMinimumHeight(); method public int getWallpaperDesiredMinimumWidth(); @@ -36743,6 +36759,7 @@ package android.test.mock { method public android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; method public int getComponentEnabledSetting(android.content.ComponentName); method public android.graphics.drawable.Drawable getDefaultActivityIcon(); + method public java.lang.String getDefaultBrowserPackageNameAsUser(int); method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo); method public byte[] getEphemeralCookie(); method public int getEphemeralCookieMaxSizeBytes(); @@ -36754,8 +36771,10 @@ package android.test.mock { method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String); method public java.lang.String getNameForUid(int); method public int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; + method public int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public android.content.pm.PackageInstaller getPackageInstaller(); + method public int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public java.lang.String[] getPackagesForUid(int); method public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int); method public android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; @@ -39183,7 +39202,7 @@ package android.util { method public static final java.lang.String digitsAndPlusOnly(java.util.regex.Matcher); field public static final java.util.regex.Pattern DOMAIN_NAME; field public static final java.util.regex.Pattern EMAIL_ADDRESS; - field public static final java.lang.String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef"; + field public static final deprecated java.lang.String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef"; field public static final java.util.regex.Pattern IP_ADDRESS; field public static final java.util.regex.Pattern PHONE; field public static final deprecated java.util.regex.Pattern TOP_LEVEL_DOMAIN; @@ -40562,7 +40581,6 @@ package android.view { field public static final int AXIS_RX = 12; // 0xc field public static final int AXIS_RY = 13; // 0xd field public static final int AXIS_RZ = 14; // 0xe - field public static final int AXIS_SCROLL = 26; // 0x1a field public static final int AXIS_SIZE = 3; // 0x3 field public static final int AXIS_THROTTLE = 19; // 0x13 field public static final int AXIS_TILT = 25; // 0x19 @@ -44049,7 +44067,7 @@ package android.webkit { method public abstract deprecated void setEnableSmoothTransition(boolean); method public abstract void setFantasyFontFamily(java.lang.String); method public abstract void setFixedFontFamily(java.lang.String); - method public abstract void setGeolocationDatabasePath(java.lang.String); + method public abstract deprecated void setGeolocationDatabasePath(java.lang.String); method public abstract void setGeolocationEnabled(boolean); method public abstract void setJavaScriptCanOpenWindowsAutomatically(boolean); method public abstract void setJavaScriptEnabled(boolean); diff --git a/core/java/android/annotation/AppIdInt.java b/core/java/android/annotation/AppIdInt.java new file mode 100644 index 000000000000..29838dd5bd7c --- /dev/null +++ b/core/java/android/annotation/AppIdInt.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Denotes that the annotated element is a multi-user application ID. This is + * <em>not</em> the same as a UID. + * + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface AppIdInt { +} diff --git a/core/java/android/annotation/UserIdInt.java b/core/java/android/annotation/UserIdInt.java new file mode 100644 index 000000000000..7b9ce25e3f1a --- /dev/null +++ b/core/java/android/annotation/UserIdInt.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Denotes that the annotated element is a multi-user user ID. This is + * <em>not</em> the same as a UID. + * + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD}) +public @interface UserIdInt { +} diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 177fabe2d25c..4531a746edf4 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -101,6 +101,9 @@ import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.renderscript.RenderScriptCacheDir; import android.security.keystore.AndroidKeyStoreProvider; +import android.system.Os; +import android.system.OsConstants; +import android.system.ErrnoException; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IVoiceInteractor; @@ -820,48 +823,6 @@ public final class ActivityThread { setCoreSettings(coreSettings); - /* - * Two possible indications that this package could be - * sharing its runtime with other packages: - * - * 1.) the sharedUserId attribute is set in the manifest, - * indicating a request to share a VM with other - * packages with the same sharedUserId. - * - * 2.) the application element of the manifest has an - * attribute specifying a non-default process name, - * indicating the desire to run in another packages VM. - * - * If sharing is enabled we do not have a unique application - * in a process and therefore cannot rely on the package - * name inside the runtime. - */ - IPackageManager pm = getPackageManager(); - android.content.pm.PackageInfo pi = null; - try { - pi = pm.getPackageInfo(appInfo.packageName, 0, UserHandle.myUserId()); - } catch (RemoteException e) { - } - if (pi != null) { - boolean sharedUserIdSet = (pi.sharedUserId != null); - boolean processNameNotDefault = - (pi.applicationInfo != null && - !appInfo.packageName.equals(pi.applicationInfo.processName)); - boolean sharable = (sharedUserIdSet || processNameNotDefault); - - // Tell the VMRuntime about the application, unless it is shared - // inside a process. - if (!sharable) { - final List<String> codePaths = new ArrayList<>(); - codePaths.add(appInfo.sourceDir); - if (appInfo.splitSourceDirs != null) { - Collections.addAll(codePaths, appInfo.splitSourceDirs); - } - VMRuntime.registerAppInfo(appInfo.packageName, appInfo.dataDir, - codePaths.toArray(new String[codePaths.size()])); - } - } - AppBindData data = new AppBindData(); data.processName = processName; data.appInfo = appInfo; @@ -4697,6 +4658,87 @@ public final class ActivityThread { } } + private static void setupJitProfileSupport(LoadedApk loadedApk, File cacheDir) { + final ApplicationInfo appInfo = loadedApk.getApplicationInfo(); + if (isSharingRuntime(appInfo)) { + // If sharing is enabled we do not have a unique application + // in a process and therefore cannot rely on the package + // name inside the runtime. + return; + } + final List<String> codePaths = new ArrayList<>(); + if ((appInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) { + codePaths.add(appInfo.sourceDir); + } + if (appInfo.splitSourceDirs != null) { + Collections.addAll(codePaths, appInfo.splitSourceDirs); + } + + if (codePaths.isEmpty()) { + // If there are no code paths there's no need to setup a profile file and register with + // the runtime, + return; + } + + // Add an extension to the file name to better reveal its intended use. + // Keep in sync with BackgroundDexOptService. + final String profileExtension = ".prof"; + final File profileFile = new File(cacheDir, loadedApk.mPackageName + profileExtension); + if (!profileFile.exists()) { + FileDescriptor fd = null; + try { + final int permissions = 0600; // read-write for user. + fd = Os.open(profileFile.getAbsolutePath(), OsConstants.O_CREAT, permissions); + Os.fchmod(fd, permissions); + Os.fchown(fd, appInfo.uid, appInfo.uid); + } catch (ErrnoException e) { + Log.w(TAG, "Unable to create jit profile file " + profileFile, e); + try { + Os.unlink(profileFile.getAbsolutePath()); + } catch (ErrnoException unlinkErr) { + Log.v(TAG, "Unable to unlink jit profile file " + profileFile, unlinkErr); + } + return; + } finally { + IoUtils.closeQuietly(fd); + } + } + + VMRuntime.registerAppInfo(profileFile.getAbsolutePath(), appInfo.dataDir, + codePaths.toArray(new String[codePaths.size()])); + } + + /* + * Two possible indications that this package could be + * sharing its runtime with other packages: + * + * 1) the sharedUserId attribute is set in the manifest, + * indicating a request to share a VM with other + * packages with the same sharedUserId. + * + * 2) the application element of the manifest has an + * attribute specifying a non-default process name, + * indicating the desire to run in another packages VM. + */ + private static boolean isSharingRuntime(ApplicationInfo appInfo) { + IPackageManager pm = getPackageManager(); + android.content.pm.PackageInfo pi = null; + try { + pi = pm.getPackageInfo(appInfo.packageName, 0, UserHandle.myUserId()); + } catch (RemoteException e) { + } + if (pi != null) { + boolean sharedUserIdSet = (pi.sharedUserId != null); + boolean processNameNotDefault = (pi.applicationInfo != null) && + !appInfo.packageName.equals(pi.applicationInfo.processName); + boolean sharable = sharedUserIdSet || processNameNotDefault; + return sharable; + } + // We couldn't get information for the package. Be pessimistic and assume + // it's sharing the runtime. + return true; + } + private void updateDefaultDensity() { if (mCurDefaultDisplayDpi != Configuration.DENSITY_DPI_UNDEFINED && mCurDefaultDisplayDpi != DisplayMetrics.DENSITY_DEVICE @@ -4900,12 +4942,14 @@ public final class ActivityThread { + "due to missing cache directory"); } - // Use codeCacheDir to store generated/compiled graphics code + // Use codeCacheDir to store generated/compiled graphics code and jit profiling data. final File codeCacheDir = appContext.getCodeCacheDir(); if (codeCacheDir != null) { setupGraphicsSupport(data.info, codeCacheDir); + setupJitProfileSupport(data.info, codeCacheDir); } else { - Log.e(TAG, "Unable to setupGraphicsSupport due to missing code-cache directory"); + Log.e(TAG, "Unable to setupGraphicsSupport and setupJitProfileSupport " + + "due to missing code-cache directory"); } } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index e34b1ac713be..42b18384c588 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -213,10 +213,15 @@ public class ApplicationPackageManager extends PackageManager { } @Override - public int[] getPackageGids(String packageName) + public int[] getPackageGids(String packageName) throws NameNotFoundException { + return getPackageGids(packageName, 0); + } + + @Override + public int[] getPackageGids(String packageName, int flags) throws NameNotFoundException { try { - int[] gids = mPM.getPackageGids(packageName, mContext.getUserId()); + int[] gids = mPM.getPackageGidsEtc(packageName, flags, mContext.getUserId()); if (gids != null) { return gids; } @@ -228,10 +233,20 @@ public class ApplicationPackageManager extends PackageManager { } @Override - public int getPackageUidAsUser(String packageName, int userHandle) + public int getPackageUid(String packageName, int flags) throws NameNotFoundException { + return getPackageUidAsUser(packageName, flags, mContext.getUserId()); + } + + @Override + public int getPackageUidAsUser(String packageName, int userId) throws NameNotFoundException { + return getPackageUidAsUser(packageName, 0, userId); + } + + @Override + public int getPackageUidAsUser(String packageName, int flags, int userId) throws NameNotFoundException { try { - int uid = mPM.getPackageUid(packageName, userHandle); + int uid = mPM.getPackageUidEtc(packageName, flags, userId); if (uid >= 0) { return uid; } diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index a9516d03ba0d..ed4bb28427ae 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -920,9 +920,9 @@ public class DownloadManager { private final ContentResolver mResolver; private final String mPackageName; - private final int mTargetSdkVersion; private Uri mBaseUri = Downloads.Impl.CONTENT_URI; + private boolean mAccessFilename; /** * @hide @@ -930,7 +930,10 @@ public class DownloadManager { public DownloadManager(Context context) { mResolver = context.getContentResolver(); mPackageName = context.getPackageName(); - mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion; + + // Callers can access filename columns when targeting old platform + // versions; otherwise we throw telling them it's deprecated. + mAccessFilename = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N; } /** @@ -946,6 +949,11 @@ public class DownloadManager { } } + /** {@hide} */ + public void setAccessFilename(boolean accessFilename) { + mAccessFilename = accessFilename; + } + /** * Enqueue a new download. The download will start automatically once the download manager is * ready to execute it and connectivity is available. @@ -1010,7 +1018,7 @@ public class DownloadManager { if (underlyingCursor == null) { return null; } - return new CursorTranslator(underlyingCursor, mBaseUri, mTargetSdkVersion); + return new CursorTranslator(underlyingCursor, mBaseUri, mAccessFilename); } /** @@ -1279,12 +1287,12 @@ public class DownloadManager { */ private static class CursorTranslator extends CursorWrapper { private final Uri mBaseUri; - private final int mTargetSdkVersion; + private final boolean mAccessFilename; - public CursorTranslator(Cursor cursor, Uri baseUri, int targetSdkVersion) { + public CursorTranslator(Cursor cursor, Uri baseUri, boolean accessFilename) { super(cursor); mBaseUri = baseUri; - mTargetSdkVersion = targetSdkVersion; + mAccessFilename = accessFilename; } @Override @@ -1310,7 +1318,7 @@ public class DownloadManager { case COLUMN_LOCAL_URI: return getLocalUri(); case COLUMN_LOCAL_FILENAME: - if (mTargetSdkVersion >= Build.VERSION_CODES.N) { + if (!mAccessFilename) { throw new IllegalArgumentException( "COLUMN_LOCAL_FILENAME is deprecated;" + " use ContentResolver.openFileDescriptor() instead"); diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index f7848f98b006..8475840bbae2 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -22,6 +22,7 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.NonNull; +import android.annotation.TestApi; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Point; @@ -856,6 +857,7 @@ public final class UiAutomation { * * @hide */ + @TestApi public boolean grantRuntimePermission(String packageName, String permission, UserHandle userHandle) { synchronized (mLock) { @@ -884,6 +886,7 @@ public final class UiAutomation { * * @hide */ + @TestApi public boolean revokeRuntimePermission(String packageName, String permission, UserHandle userHandle) { synchronized (mLock) { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index f940bd6e8fed..08e9b1e897af 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -88,13 +88,15 @@ public class DevicePolicyManager { private final Context mContext; private final IDevicePolicyManager mService; + private boolean mParentInstance; private static final String REMOTE_EXCEPTION_MESSAGE = "Failed to talk with device policy manager service"; - private DevicePolicyManager(Context context) { + private DevicePolicyManager(Context context, boolean parentInstance) { this(context, IDevicePolicyManager.Stub.asInterface( ServiceManager.getService(Context.DEVICE_POLICY_SERVICE))); + mParentInstance = parentInstance; } /** @hide */ @@ -106,7 +108,7 @@ public class DevicePolicyManager { /** @hide */ public static DevicePolicyManager create(Context context) { - DevicePolicyManager me = new DevicePolicyManager(context); + DevicePolicyManager me = new DevicePolicyManager(context, false); return me.mService != null ? me : null; } @@ -1031,7 +1033,7 @@ public class DevicePolicyManager { public void setPasswordQuality(@NonNull ComponentName admin, int quality) { if (mService != null) { try { - mService.setPasswordQuality(admin, quality); + mService.setPasswordQuality(admin, quality, mParentInstance); } catch (RemoteException e) { Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e); } @@ -1052,7 +1054,7 @@ public class DevicePolicyManager { public int getPasswordQuality(@Nullable ComponentName admin, int userHandle) { if (mService != null) { try { - return mService.getPasswordQuality(admin, userHandle); + return mService.getPasswordQuality(admin, userHandle, mParentInstance); } catch (RemoteException e) { Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e); } @@ -1622,7 +1624,7 @@ public class DevicePolicyManager { public boolean isActivePasswordSufficient() { if (mService != null) { try { - return mService.isActivePasswordSufficient(myUserId()); + return mService.isActivePasswordSufficient(myUserId(), mParentInstance); } catch (RemoteException e) { Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e); } @@ -1791,6 +1793,9 @@ public class DevicePolicyManager { * not acceptable for the current constraints or if the user has not been decrypted yet. */ public boolean resetPassword(String password, int flags) { + if (mParentInstance) { + throw new SecurityException("Reset password does not work across profiles."); + } if (mService != null) { try { return mService.resetPassword(password, flags); @@ -3060,6 +3065,8 @@ public class DevicePolicyManager { * * <p>If the device owner information is {@code null} or empty then the device owner info is * cleared and the user owner info is shown on the lock screen if it is set. + * <p>If the device owner information contains only whitespaces then the message on the lock + * screen will be blank and the user will not be allowed to change it. * * @param admin The name of the admin component to check. * @param info Device owner information which will be displayed instead of the user @@ -4927,4 +4934,23 @@ public class DevicePolicyManager { } return null; } + + /** + * Obtains a {@link DevicePolicyManager} whose calls act on the parent profile. + * + * <p> Note only some methods will work on the parent Manager. + * + * @return a new instance of {@link DevicePolicyManager} that acts on the parent profile. + */ + public DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) { + try { + if (!mService.isManagedProfile(admin)) { + throw new SecurityException("The current user does not have a parent profile."); + } + return new DevicePolicyManager(mContext, true); + } catch (RemoteException re) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re); + return null; + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index f480a02b21a0..754cb432bf7c 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -35,8 +35,8 @@ import java.util.List; * {@hide} */ interface IDevicePolicyManager { - void setPasswordQuality(in ComponentName who, int quality); - int getPasswordQuality(in ComponentName who, int userHandle); + void setPasswordQuality(in ComponentName who, int quality, boolean parent); + int getPasswordQuality(in ComponentName who, int userHandle, boolean parent); void setPasswordMinimumLength(in ComponentName who, int length); int getPasswordMinimumLength(in ComponentName who, int userHandle); @@ -67,7 +67,7 @@ interface IDevicePolicyManager { long getPasswordExpiration(in ComponentName who, int userHandle); - boolean isActivePasswordSufficient(int userHandle); + boolean isActivePasswordSufficient(int userHandle, boolean parent); int getCurrentFailedPasswordAttempts(int userHandle); int getProfileWithMinimumFailedPasswordsForWipe(int userHandle); diff --git a/core/java/android/app/job/JobScheduler.java b/core/java/android/app/job/JobScheduler.java index a1c11f335091..6e96da5b4667 100644 --- a/core/java/android/app/job/JobScheduler.java +++ b/core/java/android/app/job/JobScheduler.java @@ -50,18 +50,15 @@ public abstract class JobScheduler { */ public static final int RESULT_FAILURE = 0; /** - * Returned from {@link #schedule(JobInfo)} if this application has made too many requests for - * work over too short a time. + * Returned from {@link #schedule(JobInfo)} if this job has been successfully scheduled. */ - // TODO: Determine if this is necessary. public static final int RESULT_SUCCESS = 1; /** * @param job The job you wish scheduled. See * {@link android.app.job.JobInfo.Builder JobInfo.Builder} for more detail on the sorts of jobs * you can schedule. - * @return If >0, this int returns the jobId of the successfully scheduled job. - * Otherwise you have to compare the return value to the error codes defined in this class. + * @return An int representing ({@link #RESULT_SUCCESS} or {@link #RESULT_FAILURE}). */ public abstract int schedule(JobInfo job); diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index dec0d9107d3a..16b5a4bc0404 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -144,7 +144,12 @@ public class ContentProviderClient implements AutoCloseable { } final Cursor cursor = mContentProvider.query(mPackageName, url, projection, selection, selectionArgs, sortOrder, remoteCancellationSignal); - return new CursorWrapperInner(cursor); + if ("com.google.android.gms".equals(mPackageName)) { + // They're casting to a concrete subclass, sigh + return cursor; + } else { + return new CursorWrapperInner(cursor); + } } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index ce5d3b1f5405..684a85e52674 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -20,6 +20,8 @@ import android.accounts.Account; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.TestApi; +import android.annotation.UserIdInt; import android.app.ActivityManagerNative; import android.app.ActivityThread; import android.app.AppGlobals; @@ -1610,7 +1612,7 @@ public abstract class ContentResolver { /** @hide - designated user version */ public final void registerContentObserver(Uri uri, boolean notifyForDescendents, - ContentObserver observer, int userHandle) { + ContentObserver observer, @UserIdInt int userHandle) { try { getContentService().registerContentObserver(uri, notifyForDescendents, observer.getContentObserver(), userHandle); @@ -1684,7 +1686,7 @@ public abstract class ContentResolver { * @hide */ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork, - int userHandle) { + @UserIdInt int userHandle) { try { getContentService().notifyChange( uri, observer == null ? null : observer.getContentObserver(), @@ -1825,7 +1827,7 @@ public abstract class ContentResolver { * @see #requestSync(Account, String, Bundle) * @hide */ - public static void requestSyncAsUser(Account account, String authority, int userId, + public static void requestSyncAsUser(Account account, String authority, @UserIdInt int userId, Bundle extras) { if (extras == null) { throw new IllegalArgumentException("Must specify extras."); @@ -1922,7 +1924,7 @@ public abstract class ContentResolver { * @see #cancelSync(Account, String) * @hide */ - public static void cancelSyncAsUser(Account account, String authority, int userId) { + public static void cancelSyncAsUser(Account account, String authority, @UserIdInt int userId) { try { getContentService().cancelSyncAsUser(account, authority, null, userId); } catch (RemoteException e) { @@ -1945,7 +1947,7 @@ public abstract class ContentResolver { * @see #getSyncAdapterTypes() * @hide */ - public static SyncAdapterType[] getSyncAdapterTypesAsUser(int userId) { + public static SyncAdapterType[] getSyncAdapterTypesAsUser(@UserIdInt int userId) { try { return getContentService().getSyncAdapterTypesAsUser(userId); } catch (RemoteException e) { @@ -1957,8 +1959,9 @@ public abstract class ContentResolver { * @hide * Returns the package names of syncadapters that match a given user and authority. */ + @TestApi public static String[] getSyncAdapterPackagesForAuthorityAsUser(String authority, - int userId) { + @UserIdInt int userId) { try { return getContentService().getSyncAdapterPackagesForAuthorityAsUser(authority, userId); } catch (RemoteException e) { @@ -1988,7 +1991,7 @@ public abstract class ContentResolver { * @hide */ public static boolean getSyncAutomaticallyAsUser(Account account, String authority, - int userId) { + @UserIdInt int userId) { try { return getContentService().getSyncAutomaticallyAsUser(account, authority, userId); } catch (RemoteException e) { @@ -2014,7 +2017,7 @@ public abstract class ContentResolver { * @hide */ public static void setSyncAutomaticallyAsUser(Account account, String authority, boolean sync, - int userId) { + @UserIdInt int userId) { try { getContentService().setSyncAutomaticallyAsUser(account, authority, sync, userId); } catch (RemoteException e) { @@ -2173,7 +2176,8 @@ public abstract class ContentResolver { * @see #getIsSyncable(Account, String) * @hide */ - public static int getIsSyncableAsUser(Account account, String authority, int userId) { + public static int getIsSyncableAsUser(Account account, String authority, + @UserIdInt int userId) { try { return getContentService().getIsSyncableAsUser(account, authority, userId); } catch (RemoteException e) { @@ -2216,7 +2220,7 @@ public abstract class ContentResolver { * @see #getMasterSyncAutomatically() * @hide */ - public static boolean getMasterSyncAutomaticallyAsUser(int userId) { + public static boolean getMasterSyncAutomaticallyAsUser(@UserIdInt int userId) { try { return getContentService().getMasterSyncAutomaticallyAsUser(userId); } catch (RemoteException e) { @@ -2240,7 +2244,7 @@ public abstract class ContentResolver { * @see #setMasterSyncAutomatically(boolean) * @hide */ - public static void setMasterSyncAutomaticallyAsUser(boolean sync, int userId) { + public static void setMasterSyncAutomaticallyAsUser(boolean sync, @UserIdInt int userId) { try { getContentService().setMasterSyncAutomaticallyAsUser(sync, userId); } catch (RemoteException e) { @@ -2320,7 +2324,7 @@ public abstract class ContentResolver { * @see #getCurrentSyncs() * @hide */ - public static List<SyncInfo> getCurrentSyncsAsUser(int userId) { + public static List<SyncInfo> getCurrentSyncsAsUser(@UserIdInt int userId) { try { return getContentService().getCurrentSyncsAsUser(userId); } catch (RemoteException e) { @@ -2348,7 +2352,7 @@ public abstract class ContentResolver { * @hide */ public static SyncStatusInfo getSyncStatusAsUser(Account account, String authority, - int userId) { + @UserIdInt int userId) { try { return getContentService().getSyncStatusAsUser(account, authority, null, userId); } catch (RemoteException e) { @@ -2372,7 +2376,8 @@ public abstract class ContentResolver { * @see #requestSync(Account, String, Bundle) * @hide */ - public static boolean isSyncPendingAsUser(Account account, String authority, int userId) { + public static boolean isSyncPendingAsUser(Account account, String authority, + @UserIdInt int userId) { try { return getContentService().isSyncPendingAsUser(account, authority, null, userId); } catch (RemoteException e) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 67bdad576af3..a6036bb87861 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -30,6 +30,8 @@ import android.annotation.StringRes; import android.annotation.StyleRes; import android.annotation.StyleableRes; import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.annotation.UserIdInt; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; @@ -3967,7 +3969,8 @@ public abstract class Context { * * @hide */ - public abstract int getUserId(); + @TestApi + public abstract @UserIdInt int getUserId(); /** * Return a new Context object for the current Context but whose resources diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index fe279d1d9b6f..01689087043f 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -16,6 +16,7 @@ package android.content.pm; +import android.annotation.TestApi; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -803,7 +804,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public boolean hasRtlSupport() { return (flags & FLAG_SUPPORTS_RTL) == FLAG_SUPPORTS_RTL; } - + + /** {@hide} */ + public boolean hasCode() { + return (flags & FLAG_HAS_CODE) != 0; + } + public static class DisplayNameComparator implements Comparator<ApplicationInfo> { public DisplayNameComparator(PackageManager pm) { @@ -1069,6 +1075,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** * @hide */ + @TestApi public boolean isSystemApp() { return (flags & ApplicationInfo.FLAG_SYSTEM) != 0; } @@ -1076,6 +1083,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** * @hide */ + @TestApi public boolean isPrivilegedApp() { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 719bfceb7cbb..9d94b74350dd 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -27,6 +27,8 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.StringRes; import android.annotation.SystemApi; +import android.annotation.UserIdInt; +import android.annotation.TestApi; import android.annotation.XmlRes; import android.app.PackageDeleteObserver; import android.app.PackageInstallObserver; @@ -2227,9 +2229,6 @@ public abstract class PackageManager { /** * Retrieve overall information about an application package that is * installed on the system. - * <p> - * Throws {@link NameNotFoundException} if a package with the given name can - * not be found on the system. * * @param packageName The full name (i.e. com.google.apps.contacts) of the * desired package. @@ -2247,6 +2246,8 @@ public abstract class PackageManager { * applications (which includes installed applications as well as * applications with data directory i.e. applications which had been * deleted with {@code DONT_DELETE_DATA} flag set). + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. * @see #GET_ACTIVITIES * @see #GET_GIDS * @see #GET_CONFIGURATIONS @@ -2265,9 +2266,6 @@ public abstract class PackageManager { * @hide * Retrieve overall information about an application package that is * installed on the system. - * <p> - * Throws {@link NameNotFoundException} if a package with the given name can - * not be found on the system. * * @param packageName The full name (i.e. com.google.apps.contacts) of the * desired package. @@ -2286,6 +2284,8 @@ public abstract class PackageManager { * applications (which includes installed applications as well as * applications with data directory i.e. applications which had been * deleted with {@code DONT_DELETE_DATA} flag set). + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. * @see #GET_ACTIVITIES * @see #GET_GIDS * @see #GET_CONFIGURATIONS @@ -2299,7 +2299,7 @@ public abstract class PackageManager { */ @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) public abstract PackageInfo getPackageInfoAsUser(String packageName, - @PackageInfoFlags int flags, int userId) throws NameNotFoundException; + @PackageInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException; /** * Map from the current package names in use on the device to whatever @@ -2341,9 +2341,6 @@ public abstract class PackageManager { * through packages. The current implementation will look for a main * activity in the category {@link Intent#CATEGORY_LEANBACK_LAUNCHER}, or * return null if no main leanback activities are found. - * <p> - * Throws {@link NameNotFoundException} if a package with the given name - * cannot be found on the system. * * @param packageName The name of the package to inspect. * @return Returns either a fully-qualified Intent that can be used to launch @@ -2355,39 +2352,73 @@ public abstract class PackageManager { /** * Return an array of all of the secondary group-ids that have been assigned * to a package. - * <p> - * Throws {@link NameNotFoundException} if a package with the given name - * cannot be found on the system. * * @param packageName The full name (i.e. com.google.apps.contacts) of the * desired package. * @return Returns an int array of the assigned gids, or null if there are * none. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. */ public abstract int[] getPackageGids(String packageName) throws NameNotFoundException; /** - * @hide Return the uid associated with the given package name for the - * given user. + * Return an array of all of the secondary group-ids that have been assigned + * to a package. * - * <p>Throws {@link NameNotFoundException} if a package with the given - * name can not be found on the system. + * @param packageName The full name (i.e. com.google.apps.contacts) of the + * desired package. + * @return Returns an int array of the assigned gids, or null if there are + * none. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. + */ + public abstract int[] getPackageGids(String packageName, @PackageInfoFlags int flags) + throws NameNotFoundException; + + /** + * Return the UID associated with the given package name. * * @param packageName The full name (i.e. com.google.apps.contacts) of the - * desired package. - * @param userHandle The user handle identifier to look up the package under. + * desired package. + * @return Returns an integer UID who owns the given package name. + * @throws NameNotFoundException if a package with the given name can not be + * found on the system. + */ + public abstract int getPackageUid(String packageName, @PackageInfoFlags int flags) + throws NameNotFoundException; + + /** + * Return the UID associated with the given package name. * - * @return Returns an integer uid who owns the given package name. + * @param packageName The full name (i.e. com.google.apps.contacts) of the + * desired package. + * @param userId The user handle identifier to look up the package under. + * @return Returns an integer UID who owns the given package name. + * @throws NameNotFoundException if a package with the given name can not be + * found on the system. + * @hide */ - public abstract int getPackageUidAsUser(String packageName, int userId) + public abstract int getPackageUidAsUser(String packageName, @UserIdInt int userId) throws NameNotFoundException; /** - * Retrieve all of the information we know about a particular permission. + * Return the UID associated with the given package name. * - * <p>Throws {@link NameNotFoundException} if a permission with the given - * name cannot be found on the system. + * @param packageName The full name (i.e. com.google.apps.contacts) of the + * desired package. + * @param userId The user handle identifier to look up the package under. + * @return Returns an integer UID who owns the given package name. + * @throws NameNotFoundException if a package with the given name can not be + * found on the system. + * @hide + */ + public abstract int getPackageUidAsUser(String packageName, @PackageInfoFlags int flags, + @UserIdInt int userId) throws NameNotFoundException; + + /** + * Retrieve all of the information we know about a particular permission. * * @param name The fully qualified name (i.e. com.google.permission.LOGIN) * of the permission you are interested in. @@ -2396,6 +2427,8 @@ public abstract class PackageManager { * * @return Returns a {@link PermissionInfo} containing information about the * permission. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. */ public abstract PermissionInfo getPermissionInfo(String name, @PermissionInfoFlags int flags) throws NameNotFoundException; @@ -2403,9 +2436,6 @@ public abstract class PackageManager { /** * Query for all of the permissions associated with a particular group. * - * <p>Throws {@link NameNotFoundException} if the given group does not - * exist. - * * @param group The fully qualified name (i.e. com.google.permission.LOGIN) * of the permission group you are interested in. Use null to * find all of the permissions not associated with a group. @@ -2414,6 +2444,8 @@ public abstract class PackageManager { * * @return Returns a list of {@link PermissionInfo} containing information * about all of the permissions in the given group. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. */ public abstract List<PermissionInfo> queryPermissionsByGroup(String group, @PermissionInfoFlags int flags) throws NameNotFoundException; @@ -2422,9 +2454,6 @@ public abstract class PackageManager { * Retrieve all of the information we know about a particular group of * permissions. * - * <p>Throws {@link NameNotFoundException} if a permission group with the given - * name cannot be found on the system. - * * @param name The fully qualified name (i.e. com.google.permission_group.APPS) * of the permission you are interested in. * @param flags Additional option flags. Use {@link #GET_META_DATA} to @@ -2432,6 +2461,8 @@ public abstract class PackageManager { * * @return Returns a {@link PermissionGroupInfo} containing information * about the permission. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. */ public abstract PermissionGroupInfo getPermissionGroupInfo(String name, @PermissionGroupInfoFlags int flags) throws NameNotFoundException; @@ -2452,9 +2483,6 @@ public abstract class PackageManager { * Retrieve all of the information we know about a particular * package/application. * - * <p>Throws {@link NameNotFoundException} if an application with the given - * package name cannot be found on the system. - * * @param packageName The full name (i.e. com.google.apps.contacts) of an * application. * @param flags Additional option flags. Use any combination of @@ -2470,6 +2498,8 @@ public abstract class PackageManager { * installed applications as well as applications * with data directory ie applications which had been * deleted with {@code DONT_DELETE_DATA} flag set). + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. * * @see #GET_META_DATA * @see #GET_SHARED_LIBRARY_FILES @@ -2482,9 +2512,6 @@ public abstract class PackageManager { * Retrieve all of the information we know about a particular activity * class. * - * <p>Throws {@link NameNotFoundException} if an activity with the given - * class name cannot be found on the system. - * * @param component The full component name (i.e. * com.google.apps.contacts/com.google.apps.contacts.ContactsList) of an Activity * class. @@ -2493,6 +2520,8 @@ public abstract class PackageManager { * to modify the data (in ApplicationInfo) returned. * * @return {@link ActivityInfo} containing information about the activity. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. * * @see #GET_INTENT_FILTERS * @see #GET_META_DATA @@ -2505,9 +2534,6 @@ public abstract class PackageManager { * Retrieve all of the information we know about a particular receiver * class. * - * <p>Throws {@link NameNotFoundException} if a receiver with the given - * class name cannot be found on the system. - * * @param component The full component name (i.e. * com.google.apps.calendar/com.google.apps.calendar.CalendarAlarm) of a Receiver * class. @@ -2516,6 +2542,8 @@ public abstract class PackageManager { * to modify the data returned. * * @return {@link ActivityInfo} containing information about the receiver. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. * * @see #GET_INTENT_FILTERS * @see #GET_META_DATA @@ -2528,9 +2556,6 @@ public abstract class PackageManager { * Retrieve all of the information we know about a particular service * class. * - * <p>Throws {@link NameNotFoundException} if a service with the given - * class name cannot be found on the system. - * * @param component The full component name (i.e. * com.google.apps.media/com.google.apps.media.BackgroundPlayback) of a Service * class. @@ -2539,6 +2564,8 @@ public abstract class PackageManager { * to modify the data returned. * * @return ServiceInfo containing information about the service. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. * * @see #GET_META_DATA * @see #GET_SHARED_LIBRARY_FILES @@ -2550,9 +2577,6 @@ public abstract class PackageManager { * Retrieve all of the information we know about a particular content * provider class. * - * <p>Throws {@link NameNotFoundException} if a provider with the given - * class name cannot be found on the system. - * * @param component The full component name (i.e. * com.google.providers.media/com.google.providers.media.MediaProvider) of a * ContentProvider class. @@ -2561,6 +2585,8 @@ public abstract class PackageManager { * to modify the data returned. * * @return ProviderInfo containing information about the service. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. * * @see #GET_META_DATA * @see #GET_SHARED_LIBRARY_FILES @@ -2675,7 +2701,7 @@ public abstract class PackageManager { * @hide */ public abstract List<PackageInfo> getInstalledPackagesAsUser(@PackageInfoFlags int flags, - int userId); + @UserIdInt int userId); /** * Check whether a particular package has been granted a particular @@ -2987,8 +3013,9 @@ public abstract class PackageManager { * shared user. * * @param sharedUserName The shared user name whose uid is to be retrieved. - * @return Returns the uid associated with the shared user, or NameNotFoundException - * if the shared user name is not being used by any installed packages + * @return Returns the UID associated with the shared user. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. * @hide */ public abstract int getUidForSharedUser(String sharedUserName) @@ -3184,7 +3211,7 @@ public abstract class PackageManager { * @hide */ public abstract ResolveInfo resolveActivityAsUser(Intent intent, @ResolveInfoFlags int flags, - int userId); + @UserIdInt int userId); /** * Retrieve all activities that can be performed for the given intent. @@ -3231,7 +3258,7 @@ public abstract class PackageManager { * @hide */ public abstract List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, - @ResolveInfoFlags int flags, int userId); + @ResolveInfoFlags int flags, @UserIdInt int userId); /** * Retrieve a set of activities that should be presented to the user as @@ -3300,7 +3327,7 @@ public abstract class PackageManager { * @hide */ public abstract List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent, - @ResolveInfoFlags int flags, int userId); + @ResolveInfoFlags int flags, @UserIdInt int userId); /** * Determine the best service to handle for a given Intent. @@ -3355,11 +3382,11 @@ public abstract class PackageManager { * @hide */ public abstract List<ResolveInfo> queryIntentServicesAsUser(Intent intent, - @ResolveInfoFlags int flags, int userId); + @ResolveInfoFlags int flags, @UserIdInt int userId); /** {@hide} */ public abstract List<ResolveInfo> queryIntentContentProvidersAsUser( - Intent intent, @ResolveInfoFlags int flags, int userId); + Intent intent, @ResolveInfoFlags int flags, @UserIdInt int userId); /** * Retrieve all providers that can match the given intent. @@ -3400,7 +3427,7 @@ public abstract class PackageManager { * @hide */ public abstract ProviderInfo resolveContentProviderAsUser(String name, - @ComponentInfoFlags int flags, int userId); + @ComponentInfoFlags int flags, @UserIdInt int userId); /** * Retrieve content provider information. @@ -3427,9 +3454,6 @@ public abstract class PackageManager { * Retrieve all of the information we know about a particular * instrumentation class. * - * <p>Throws {@link NameNotFoundException} if instrumentation with the - * given class name cannot be found on the system. - * * @param className The full name (i.e. * com.google.apps.contacts.InstrumentList) of an * Instrumentation class. @@ -3437,6 +3461,8 @@ public abstract class PackageManager { * * @return InstrumentationInfo containing information about the * instrumentation. + * @throws NameNotFoundException if a package with the given name cannot be + * found on the system. */ public abstract InstrumentationInfo getInstrumentationInfo(ComponentName className, @InstrumentationInfoFlags int flags) throws NameNotFoundException; @@ -3859,8 +3885,8 @@ public abstract class PackageManager { throws NameNotFoundException; /** @hide */ - public abstract Resources getResourcesForApplicationAsUser(String appPackageName, int userId) - throws NameNotFoundException; + public abstract Resources getResourcesForApplicationAsUser(String appPackageName, + @UserIdInt int userId) throws NameNotFoundException; /** * Retrieve overall information about an application package defined @@ -4060,7 +4086,7 @@ public abstract class PackageManager { Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public abstract void installPackageAsUser( Uri packageURI, PackageInstallObserver observer, int flags, - String installerPackageName, int userId); + String installerPackageName, @UserIdInt int userId); /** * Similar to @@ -4133,7 +4159,7 @@ public abstract class PackageManager { @RequiresPermission(anyOf = { Manifest.permission.INSTALL_PACKAGES, Manifest.permission.INTERACT_ACROSS_USERS_FULL}) - public abstract int installExistingPackageAsUser(String packageName, int userId) + public abstract int installExistingPackageAsUser(String packageName, @UserIdInt int userId) throws NameNotFoundException; /** @@ -4231,7 +4257,7 @@ public abstract class PackageManager { * * @hide */ - public abstract int getIntentVerificationStatusAsUser(String packageName, int userId); + public abstract int getIntentVerificationStatusAsUser(String packageName, @UserIdInt int userId); /** * Allow to change the status of a Intent Verification status for all IntentFilter of an App. @@ -4254,7 +4280,7 @@ public abstract class PackageManager { * @hide */ public abstract boolean updateIntentVerificationStatusAsUser(String packageName, int status, - int userId); + @UserIdInt int userId); /** * Get the list of IntentFilterVerificationInfo for a specific package and User. @@ -4294,7 +4320,8 @@ public abstract class PackageManager { * * @hide */ - public abstract String getDefaultBrowserPackageNameAsUser(int userId); + @TestApi + public abstract String getDefaultBrowserPackageNameAsUser(@UserIdInt int userId); /** * Set the default Browser package name for a specific user. @@ -4308,7 +4335,8 @@ public abstract class PackageManager { * * @hide */ - public abstract boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId); + public abstract boolean setDefaultBrowserPackageNameAsUser(String packageName, + @UserIdInt int userId); /** * Change the installer associated with a given package. There are limitations @@ -4367,7 +4395,7 @@ public abstract class PackageManager { Manifest.permission.DELETE_PACKAGES, Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public abstract void deletePackageAsUser( - String packageName, IPackageDeleteObserver observer, int flags, int userId); + String packageName, IPackageDeleteObserver observer, int flags, @UserIdInt int userId); /** * Retrieve the package name of the application that installed a package. This identifies @@ -4491,7 +4519,7 @@ public abstract class PackageManager { * * @hide */ - public abstract void getPackageSizeInfoAsUser(String packageName, int userId, + public abstract void getPackageSizeInfoAsUser(String packageName, @UserIdInt int userId, IPackageStatsObserver observer); /** @@ -4581,7 +4609,7 @@ public abstract class PackageManager { * @hide */ public void addPreferredActivityAsUser(IntentFilter filter, int match, - ComponentName[] set, ComponentName activity, int userId) { + ComponentName[] set, ComponentName activity, @UserIdInt int userId) { throw new RuntimeException("Not implemented. Must override in a subclass."); } @@ -4615,7 +4643,7 @@ public abstract class PackageManager { */ @Deprecated public void replacePreferredActivityAsUser(IntentFilter filter, int match, - ComponentName[] set, ComponentName activity, int userId) { + ComponentName[] set, ComponentName activity, @UserIdInt int userId) { throw new RuntimeException("Not implemented. Must override in a subclass."); } @@ -4843,7 +4871,7 @@ public abstract class PackageManager { * @hide */ public abstract boolean setPackageSuspendedAsUser( - String packageName, boolean suspended, int userId); + String packageName, boolean suspended, @UserIdInt int userId); /** {@hide} */ public static boolean isMoveStatusFinished(int status) { diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 8ab8991c7a42..f642f08df76e 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -19,16 +19,22 @@ package android.inputmethodservice; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import android.annotation.CallSuper; import android.annotation.DrawableRes; +import android.annotation.IntDef; +import android.annotation.MainThread; import android.app.ActivityManager; import android.app.Dialog; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; +import android.database.ContentObserver; import android.graphics.Rect; import android.graphics.Region; +import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.os.ResultReceiver; import android.os.SystemClock; @@ -68,6 +74,8 @@ import android.widget.LinearLayout; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * InputMethodService provides a standard implementation of an InputMethod, @@ -634,6 +642,97 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * A {@link ContentObserver} to monitor {@link Settings.Secure#SHOW_IME_WITH_HARD_KEYBOARD}. + * + * <p>Note that {@link Settings.Secure#SHOW_IME_WITH_HARD_KEYBOARD} is not a public API. + * Basically this functionality still needs to be considered as implementation details.</p> + */ + @MainThread + private static final class SettingsObserver extends ContentObserver { + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + ShowImeWithHardKeyboardType.UNKNOWN, + ShowImeWithHardKeyboardType.FALSE, + ShowImeWithHardKeyboardType.TRUE, + }) + private @interface ShowImeWithHardKeyboardType { + int UNKNOWN = 0; + int FALSE = 1; + int TRUE = 2; + } + @ShowImeWithHardKeyboardType + private int mShowImeWithHardKeyboard = ShowImeWithHardKeyboardType.UNKNOWN; + + private final InputMethodService mService; + + private SettingsObserver(InputMethodService service) { + super(new Handler(service.getMainLooper())); + mService = service; + } + + /** + * A factory method that internally enforces two-phase initialization to make sure that the + * object reference will not be escaped until the object is properly constructed. + * + * <p>NOTE: Currently {@link SettingsObserver} is accessed only from main thread. Hence + * this enforcement of two-phase initialization may be unnecessary at the moment.</p> + * + * @param service {@link InputMethodService} that needs to receive the callback. + * @return {@link SettingsObserver} that is already registered to + * {@link android.content.ContentResolver}. The caller must call + * {@link SettingsObserver#unregister()}. + */ + public static SettingsObserver createAndRegister(InputMethodService service) { + final SettingsObserver observer = new SettingsObserver(service); + // The observer is properly constructed. Let's start accepting the event. + service.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), + false, observer); + return observer; + } + + void unregister() { + mService.getContentResolver().unregisterContentObserver(this); + } + + private boolean shouldShowImeWithHardKeyboard() { + // Lazily initialize as needed. + if (mShowImeWithHardKeyboard == ShowImeWithHardKeyboardType.UNKNOWN) { + mShowImeWithHardKeyboard = Settings.Secure.getInt(mService.getContentResolver(), + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0 ? + ShowImeWithHardKeyboardType.TRUE : ShowImeWithHardKeyboardType.FALSE; + } + switch (mShowImeWithHardKeyboard) { + case ShowImeWithHardKeyboardType.TRUE: + return true; + case ShowImeWithHardKeyboardType.FALSE: + return false; + default: + Log.e(TAG, "Unexpected mShowImeWithHardKeyboard=" + mShowImeWithHardKeyboard); + return false; + } + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + final Uri showImeWithHardKeyboardUri = + Settings.Secure.getUriFor(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD); + if (showImeWithHardKeyboardUri.equals(uri)) { + mShowImeWithHardKeyboard = Settings.Secure.getInt(mService.getContentResolver(), + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0 ? + ShowImeWithHardKeyboardType.TRUE : ShowImeWithHardKeyboardType.FALSE; + mService.updateInputViewShown(); + } + } + + @Override + public String toString() { + return "SettingsObserver{mShowImeWithHardKeyboard=" + mShowImeWithHardKeyboard + "}"; + } + } + private SettingsObserver mSettingsObserver; + + /** * You can call this to customize the theme used by your IME's window. * This theme should typically be one that derives from * {@link android.R.style#Theme_InputMethod}, which is the default theme @@ -682,6 +781,7 @@ public class InputMethodService extends AbstractInputMethodService { super.setTheme(mTheme); super.onCreate(); mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); + mSettingsObserver = SettingsObserver.createAndRegister(this); // If the previous IME has occupied non-empty inset in the screen, we need to decide whether // we continue to use the same size of the inset or update it mShouldClearInsetOfPreviousIme = (mImm.getInputMethodWindowVisibleHeight() > 0); @@ -764,6 +864,10 @@ public class InputMethodService extends AbstractInputMethodService { mWindow.getWindow().setWindowAnimations(0); mWindow.dismiss(); } + if (mSettingsObserver != null) { + mSettingsObserver.unregister(); + mSettingsObserver = null; + } } /** @@ -1140,21 +1244,28 @@ public class InputMethodService extends AbstractInputMethodService { public boolean isInputViewShown() { return mIsInputViewShown && mWindowVisible; } - + /** - * Override this to control when the soft input area should be shown to - * the user. The default implementation only shows the input view when - * there is no hard keyboard or the keyboard is hidden. If you change what - * this returns, you will need to call {@link #updateInputViewShown()} - * yourself whenever the returned value may have changed to have it - * re-evaluated and applied. + * Override this to control when the soft input area should be shown to the user. The default + * implementation returns {@code false} when there is no hard keyboard or the keyboard is hidden + * unless the user shows an intention to use software keyboard. If you change what this + * returns, you will need to call {@link #updateInputViewShown()} yourself whenever the returned + * value may have changed to have it re-evaluated and applied. + * + * <p>When you override this method, it is recommended to call + * {@code super.onEvaluateInputViewShown()} and return {@code true} when {@code true} is + * returned.</p> */ + @CallSuper public boolean onEvaluateInputViewShown() { + if (mSettingsObserver.shouldShowImeWithHardKeyboard()) { + return true; + } Configuration config = getResources().getConfiguration(); return config.keyboard == Configuration.KEYBOARD_NOKEYS || config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES; } - + /** * Controls the visibility of the candidates display area. By default * it is hidden. @@ -2483,5 +2594,6 @@ public class InputMethodService extends AbstractInputMethodService { + " touchableInsets=" + mTmpInsets.touchableInsets + " touchableRegion=" + mTmpInsets.touchableRegion); p.println(" mShouldClearInsetOfPreviousIme=" + mShouldClearInsetOfPreviousIme); + p.println(" mSettingsObserver=" + mSettingsObserver); } } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 126824f2c46a..4159d89d1be1 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -980,6 +980,14 @@ public final class PowerManager { = "android.os.action.POWER_SAVE_MODE_CHANGED"; /** + * Intent that is broadcast when the state of {@link #isPowerSaveMode()} changes. + * @hide + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL + = "android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL"; + + /** * Intent that is broadcast when the state of {@link #isDeviceIdleMode()} changes. * This broadcast is only sent to registered receivers. */ diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java index 13b8b66306e1..2ba4aa433713 100644 --- a/core/java/android/os/ServiceManager.java +++ b/core/java/android/os/ServiceManager.java @@ -112,8 +112,10 @@ public final class ServiceManager { /** * Return a list of all currently running services. + * @return an array of all currently running services, or <code>null</code> in + * case of an exception */ - public static String[] listServices() throws RemoteException { + public static String[] listServices() { try { return getIServiceManager().listServices(); } catch (RemoteException e) { diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index f946ca7ed415..867d0b963cda 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -16,7 +16,10 @@ package android.os; +import android.annotation.AppIdInt; import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.annotation.UserIdInt; import java.io.PrintWriter; @@ -127,7 +130,7 @@ public final class UserHandle implements Parcelable { * Returns the user id for a given uid. * @hide */ - public static int getUserId(int uid) { + public static @UserIdInt int getUserId(int uid) { if (MU_ENABLED) { return uid / PER_USER_RANGE; } else { @@ -136,12 +139,12 @@ public final class UserHandle implements Parcelable { } /** @hide */ - public static int getCallingUserId() { + public static @UserIdInt int getCallingUserId() { return getUserId(Binder.getCallingUid()); } /** @hide */ - public static UserHandle of(int userId) { + public static UserHandle of(@UserIdInt int userId) { return userId == USER_SYSTEM ? SYSTEM : new UserHandle(userId); } @@ -149,7 +152,7 @@ public final class UserHandle implements Parcelable { * Returns the uid that is composed from the userId and the appId. * @hide */ - public static int getUid(int userId, int appId) { + public static int getUid(@UserIdInt int userId, @AppIdInt int appId) { if (MU_ENABLED) { return userId * PER_USER_RANGE + (appId % PER_USER_RANGE); } else { @@ -161,7 +164,8 @@ public final class UserHandle implements Parcelable { * Returns the app id (or base uid) for a given uid, stripping out the user id from it. * @hide */ - public static int getAppId(int uid) { + @TestApi + public static @AppIdInt int getAppId(int uid) { return uid % PER_USER_RANGE; } @@ -169,7 +173,7 @@ public final class UserHandle implements Parcelable { * Returns the gid shared between all apps with this userId. * @hide */ - public static int getUserGid(int userId) { + public static int getUserGid(@UserIdInt int userId) { return getUid(userId, Process.SHARED_USER_GID); } @@ -186,7 +190,7 @@ public final class UserHandle implements Parcelable { * Returns the app id for a given shared app gid. Returns -1 if the ID is invalid. * @hide */ - public static int getAppIdFromSharedAppGid(int gid) { + public static @AppIdInt int getAppIdFromSharedAppGid(int gid) { final int appId = getAppId(gid) + Process.FIRST_APPLICATION_UID - Process.FIRST_SHARED_APPLICATION_GID; if (appId < 0 || appId >= Process.FIRST_SHARED_APPLICATION_GID) { @@ -257,7 +261,7 @@ public final class UserHandle implements Parcelable { } /** @hide */ - public static int parseUserArg(String arg) { + public static @UserIdInt int parseUserArg(String arg) { int userId; if ("all".equals(arg)) { userId = UserHandle.USER_ALL; @@ -279,7 +283,7 @@ public final class UserHandle implements Parcelable { * @hide */ @SystemApi - public static int myUserId() { + public static @UserIdInt int myUserId() { return getUserId(Process.myUid()); } @@ -315,7 +319,7 @@ public final class UserHandle implements Parcelable { * @hide */ @SystemApi - public int getIdentifier() { + public @UserIdInt int getIdentifier() { return mHandle; } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 037916a94e5f..887dd17ebae9 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -20,6 +20,7 @@ import android.accounts.AccountManager; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.admin.DevicePolicyManager; @@ -601,7 +602,7 @@ public class UserManager { * @return the user handle of this process. * @hide */ - public int getUserHandle() { + public @UserIdInt int getUserHandle() { return UserHandle.myUserId(); } @@ -671,7 +672,7 @@ public class UserManager { * Returns whether the provided user is an admin user. There can be more than one admin * user. */ - public boolean isUserAdmin(int userId) { + public boolean isUserAdmin(@UserIdInt int userId) { UserInfo user = getUserInfo(userId); return user != null && user.isAdmin(); } @@ -695,7 +696,7 @@ public class UserManager { * Checks if specified user can have restricted profile. * @hide */ - public boolean canHaveRestrictedProfile(int userId) { + public boolean canHaveRestrictedProfile(@UserIdInt int userId) { try { return mService.canHaveRestrictedProfile(userId); } catch (RemoteException re) { @@ -741,7 +742,7 @@ public class UserManager { * Returns whether the specified user is ephemeral. * @hide */ - public boolean isUserEphemeral(int userId) { + public boolean isUserEphemeral(@UserIdInt int userId) { final UserInfo user = getUserInfo(userId); return user != null && user.isEphemeral(); } @@ -861,7 +862,7 @@ public class UserManager { } /** {@hide} */ - public boolean isUserUnlocked(int userId) { + public boolean isUserUnlocked(@UserIdInt int userId) { // TODO: eventually pivot this back to look at ActivityManager state, // but there is race where we can start a non-encryption-aware launcher // before that lifecycle has entered the running unlocked state. @@ -875,7 +876,7 @@ public class UserManager { * @return the UserInfo object for a specific user. * @hide */ - public UserInfo getUserInfo(int userHandle) { + public UserInfo getUserInfo(@UserIdInt int userHandle) { try { return mService.getUserInfo(userHandle); } catch (RemoteException re) { @@ -1093,7 +1094,7 @@ public class UserManager { * @return the UserInfo object for the created user, or null if the user could not be created. * @hide */ - public UserInfo createProfileForUser(String name, int flags, int userHandle) { + public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userHandle) { try { return mService.createProfileForUser(name, flags, userHandle); } catch (RemoteException re) { @@ -1133,7 +1134,7 @@ public class UserManager { * @param userHandle * @return */ - public boolean markGuestForDeletion(int userHandle) { + public boolean markGuestForDeletion(@UserIdInt int userHandle) { try { return mService.markGuestForDeletion(userHandle); } catch (RemoteException re) { @@ -1150,7 +1151,7 @@ public class UserManager { * @param userHandle the id of the profile to enable * @hide */ - public void setUserEnabled(int userHandle) { + public void setUserEnabled(@UserIdInt int userHandle) { try { mService.setUserEnabled(userHandle); } catch (RemoteException e) { @@ -1189,7 +1190,7 @@ public class UserManager { Manifest.permission.INTERACT_ACROSS_USERS_FULL, Manifest.permission.MANAGE_USERS }) - public @Nullable String getUserAccount(int userHandle) { + public @Nullable String getUserAccount(@UserIdInt int userHandle) { try { return mService.getUserAccount(userHandle); } catch (RemoteException re) { @@ -1206,7 +1207,7 @@ public class UserManager { Manifest.permission.INTERACT_ACROSS_USERS_FULL, Manifest.permission.MANAGE_USERS }) - public void setUserAccount(int userHandle, @Nullable String accountName) { + public void setUserAccount(@UserIdInt int userHandle, @Nullable String accountName) { try { mService.setUserAccount(userHandle, accountName); } catch (RemoteException re) { @@ -1259,7 +1260,7 @@ public class UserManager { * @return true if more managed profiles can be added, false if limit has been reached. * @hide */ - public boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne) { + public boolean canAddMoreManagedProfiles(@UserIdInt int userId, boolean allowedToRemoveOne) { try { return mService.canAddMoreManagedProfiles(userId, allowedToRemoveOne); } catch (RemoteException re) { @@ -1279,7 +1280,7 @@ public class UserManager { * @return the list of profiles. * @hide */ - public List<UserInfo> getProfiles(int userHandle) { + public List<UserInfo> getProfiles(@UserIdInt int userHandle) { try { return mService.getProfiles(userHandle, false /* enabledOnly */); } catch (RemoteException re) { @@ -1295,7 +1296,7 @@ public class UserManager { * @return true if the two user ids are in the same profile group. * @hide */ - public boolean isSameProfileGroup(int userId, int otherUserId) { + public boolean isSameProfileGroup(@UserIdInt int userId, int otherUserId) { try { return mService.isSameProfileGroup(userId, otherUserId); } catch (RemoteException re) { @@ -1314,7 +1315,7 @@ public class UserManager { * @return the list of profiles. * @hide */ - public List<UserInfo> getEnabledProfiles(int userHandle) { + public List<UserInfo> getEnabledProfiles(@UserIdInt int userHandle) { try { return mService.getProfiles(userHandle, true /* enabledOnly */); } catch (RemoteException re) { @@ -1352,7 +1353,7 @@ public class UserManager { * * @hide */ - public int getCredentialOwnerProfile(int userHandle) { + public int getCredentialOwnerProfile(@UserIdInt int userHandle) { try { return mService.getCredentialOwnerProfile(userHandle); } catch (RemoteException re) { @@ -1367,7 +1368,7 @@ public class UserManager { * * @hide */ - public UserInfo getProfileParent(int userHandle) { + public UserInfo getProfileParent(@UserIdInt int userHandle) { try { return mService.getProfileParent(userHandle); } catch (RemoteException re) { @@ -1383,7 +1384,7 @@ public class UserManager { * @param enableQuietMode Whether quiet mode should be enabled or disabled. * @hide */ - public void setQuietModeEnabled(int userHandle, boolean enableQuietMode) { + public void setQuietModeEnabled(@UserIdInt int userHandle, boolean enableQuietMode) { try { mService.setQuietModeEnabled(userHandle, enableQuietMode); } catch (RemoteException e) { @@ -1499,7 +1500,7 @@ public class UserManager { * @param userHandle the integer handle of the user, where 0 is the primary user. * @hide */ - public boolean removeUser(int userHandle) { + public boolean removeUser(@UserIdInt int userHandle) { try { return mService.removeUser(userHandle); } catch (RemoteException re) { @@ -1516,7 +1517,7 @@ public class UserManager { * @param name the new name for the user * @hide */ - public void setUserName(int userHandle, String name) { + public void setUserName(@UserIdInt int userHandle, String name) { try { mService.setUserName(userHandle, name); } catch (RemoteException re) { @@ -1530,7 +1531,7 @@ public class UserManager { * @param icon the bitmap to set as the photo. * @hide */ - public void setUserIcon(int userHandle, Bitmap icon) { + public void setUserIcon(@UserIdInt int userHandle, Bitmap icon) { try { mService.setUserIcon(userHandle, icon); } catch (RemoteException re) { @@ -1545,7 +1546,7 @@ public class UserManager { * @see com.android.internal.util.UserIcons#getDefaultUserIcon for a default. * @hide */ - public Bitmap getUserIcon(int userHandle) { + public Bitmap getUserIcon(@UserIdInt int userHandle) { try { ParcelFileDescriptor fd = mService.getUserIcon(userHandle); if (fd != null) { @@ -1611,7 +1612,7 @@ public class UserManager { * @return a serial number associated with that user, or -1 if the userHandle is not valid. * @hide */ - public int getUserSerialNumber(int userHandle) { + public int getUserSerialNumber(@UserIdInt int userHandle) { try { return mService.getUserSerialNumber(userHandle); } catch (RemoteException re) { @@ -1629,7 +1630,7 @@ public class UserManager { * is not valid. * @hide */ - public int getUserHandle(int userSerialNumber) { + public @UserIdInt int getUserHandle(int userSerialNumber) { try { return mService.getUserHandle(userSerialNumber); } catch (RemoteException re) { diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl index c3625b8fb029..469a4ea812b5 100644 --- a/core/java/android/print/IPrintSpooler.aidl +++ b/core/java/android/print/IPrintSpooler.aidl @@ -98,5 +98,11 @@ oneway interface IPrintSpooler { void writePrintJobData(in ParcelFileDescriptor fd, in PrintJobId printJobId); void setClient(IPrintSpoolerClient client); void setPrintJobCancelling(in PrintJobId printJobId, boolean cancelling); - void removeApprovedPrintService(in ComponentName serviceToRemove); + + /** + * Remove all approved print services that are not in the given set. + * + * @param servicesToKeep The names of the services to keep + */ + void pruneApprovedPrintServices(in List<ComponentName> servicesToKeep); } diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java index b33ef8304556..91e01f2dfd8b 100644 --- a/core/java/android/printservice/PrintServiceInfo.java +++ b/core/java/android/printservice/PrintServiceInfo.java @@ -16,6 +16,7 @@ package android.printservice; +import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; @@ -94,6 +95,16 @@ public final class PrintServiceInfo implements Parcelable { } /** + * Return the component name for this print service. + * + * @return The component name for this print service. + */ + public @NonNull ComponentName getComponentName() { + return new ComponentName(mResolveInfo.serviceInfo.packageName, + mResolveInfo.serviceInfo.name); + } + + /** * Creates a new instance. * * @param resolveInfo The service resolve info. diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 084ff7728ca4..a401ac2ff864 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -230,7 +230,6 @@ public final class DocumentsContract { * @see #FLAG_SUPPORTS_WRITE * @see #FLAG_SUPPORTS_DELETE * @see #FLAG_SUPPORTS_THUMBNAIL - * @see #FLAG_SUPPORTS_TYPED_DOCUMENT * @see #FLAG_DIR_PREFERS_GRID * @see #FLAG_DIR_PREFERS_LAST_MODIFIED * @see #FLAG_VIRTUAL_DOCUMENT @@ -349,15 +348,6 @@ public final class DocumentsContract { public static final int FLAG_SUPPORTS_MOVE = 1 << 8; /** - * Flag indicating that a document can be converted to alternative types. - * - * @see #COLUMN_FLAGS - * @see DocumentsProvider#openTypedDocument(String, String, Bundle, - * android.os.CancellationSignal) - */ - public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 1 << 9; - - /** * Flag indicating that a document is virtual, and doesn't have byte * representation in the MIME type specified as {@link #COLUMN_MIME_TYPE}. * @@ -366,7 +356,7 @@ public final class DocumentsContract { * @see DocumentsProvider#openTypedDocument(String, String, Bundle, * android.os.CancellationSignal) */ - public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 10; + public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 9; /** * Flag indicating that a document is an archive, and it's contents can be @@ -378,7 +368,7 @@ public final class DocumentsContract { * @see #COLUMN_FLAGS * @see DocumentsProvider#queryChildDocuments(String, String[], String) */ - public static final int FLAG_ARCHIVE = 1 << 11; + public static final int FLAG_ARCHIVE = 1 << 10; /** * Flag indicating that document titles should be hidden when viewing diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index e25ba35c8bb6..94b41575d0a3 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -517,13 +517,12 @@ public abstract class DocumentsProvider extends ContentProvider { * provider. * @param signal used by the caller to signal if the request should be * cancelled. May be null. - * @see Document#FLAG_SUPPORTS_TYPED_DOCUMENT */ @SuppressWarnings("unused") public AssetFileDescriptor openTypedDocument( String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal) throws FileNotFoundException { - throw new UnsupportedOperationException("Typed documents not supported"); + throw new FileNotFoundException("The requested MIME type is not supported."); } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 4eaee0b44fc5..e1391da080aa 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -19,6 +19,7 @@ package android.provider; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.ActivityThread; import android.app.AppOpsManager; import android.app.Application; @@ -1138,6 +1139,19 @@ public final class Settings { /** @hide */ public static final String EXTRA_APP_UID = "app_uid"; /** @hide */ public static final String EXTRA_APP_PACKAGE = "app_package"; + /** + * Activity Action: Show a dialog with disabled by policy message. + * <p> If an user action is disabled by policy, this dialog can be triggered to let + * the user know about this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS + = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS"; + // End of Intent actions for Settings /** @@ -4349,6 +4363,7 @@ public final class Settings { * The currently selected voice interaction service flattened ComponentName. * @hide */ + @TestApi public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service"; /** @@ -4972,19 +4987,22 @@ public final class Settings { /** * List of the enabled print services. + * + * N and beyond uses {@link #DISABLED_PRINT_SERVICES}. But this might be used in an upgrade + * from pre-N. + * * @hide */ public static final String ENABLED_PRINT_SERVICES = "enabled_print_services"; /** - * List of the system print services we enabled on first boot. On - * first boot we enable all system, i.e. bundled print services, - * once, so they work out-of-the-box. + * List of the disabled print services. + * * @hide */ - public static final String ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES = - "enabled_on_first_boot_system_print_services"; + public static final String DISABLED_PRINT_SERVICES = + "disabled_print_services"; /** * Setting to always use the default text-to-speech settings regardless diff --git a/core/java/android/text/Editable.java b/core/java/android/text/Editable.java index a284a0005918..b3f2c2a5c447 100644 --- a/core/java/android/text/Editable.java +++ b/core/java/android/text/Editable.java @@ -40,10 +40,14 @@ extends CharSequence, GetChars, Spannable, Appendable * is Spanned, the spans from it are preserved into the Editable. * Existing spans within the Editable that entirely cover the replaced * range are retained, but any that were strictly within the range - * that was replaced are removed. As a special case, the cursor - * position is preserved even when the entire range where it is - * located is replaced. + * that was replaced are removed. If the <code>source</code> contains a span + * with {@link Spanned#SPAN_PARAGRAPH} flag, and it does not satisfy the + * paragraph boundary constraint, it is not retained. As a special case, the + * cursor position is preserved even when the entire range where it is located + * is replaced. * @return a reference to this object. + * + * @see Spanned#SPAN_PARAGRAPH */ public Editable replace(int st, int en, CharSequence source, int start, int end); diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java index 40315add603d..42672384f316 100644 --- a/core/java/android/text/SpannableStringBuilder.java +++ b/core/java/android/text/SpannableStringBuilder.java @@ -421,8 +421,17 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable // Add span only if this object is not yet used as a span in this string if (getSpanStart(spans[i]) < 0) { - setSpan(false, spans[i], st - csStart + start, en - csStart + start, - sp.getSpanFlags(spans[i]) | SPAN_ADDED); + int copySpanStart = st - csStart + start; + int copySpanEnd = en - csStart + start; + int copySpanFlags = sp.getSpanFlags(spans[i]) | SPAN_ADDED; + + int flagsStart = (copySpanFlags & START_MASK) >> START_SHIFT; + int flagsEnd = copySpanFlags & END_MASK; + + if(!isInvalidParagraphStart(copySpanStart, flagsStart) && + !isInvalidParagraphEnd(copySpanEnd, flagsEnd)) { + setSpan(false, spans[i], copySpanStart, copySpanEnd, copySpanFlags); + } } } restoreInvariants(); @@ -666,23 +675,13 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable checkRange("setSpan", start, end); int flagsStart = (flags & START_MASK) >> START_SHIFT; - if (flagsStart == PARAGRAPH) { - if (start != 0 && start != length()) { - char c = charAt(start - 1); - - if (c != '\n') - throw new RuntimeException("PARAGRAPH span must start at paragraph boundary"); - } + if(isInvalidParagraphStart(start, flagsStart)) { + throw new RuntimeException("PARAGRAPH span must start at paragraph boundary"); } int flagsEnd = flags & END_MASK; - if (flagsEnd == PARAGRAPH) { - if (end != 0 && end != length()) { - char c = charAt(end - 1); - - if (c != '\n') - throw new RuntimeException("PARAGRAPH span must end at paragraph boundary"); - } + if(isInvalidParagraphEnd(end, flagsEnd)) { + throw new RuntimeException("PARAGRAPH span must end at paragraph boundary"); } // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE @@ -761,6 +760,28 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable } } + private final boolean isInvalidParagraphStart(int start, int flagsStart) { + if (flagsStart == PARAGRAPH) { + if (start != 0 && start != length()) { + char c = charAt(start - 1); + + if (c != '\n') return true; + } + } + return false; + } + + private final boolean isInvalidParagraphEnd(int end, int flagsEnd) { + if (flagsEnd == PARAGRAPH) { + if (end != 0 && end != length()) { + char c = charAt(end - 1); + + if (c != '\n') return true; + } + } + return false; + } + /** * Remove the specified markup object from the buffer. */ diff --git a/core/java/android/text/Spanned.java b/core/java/android/text/Spanned.java index a785d1b7d2c7..a0d54c26c6d9 100644 --- a/core/java/android/text/Spanned.java +++ b/core/java/android/text/Spanned.java @@ -81,7 +81,9 @@ extends CharSequence * immediately after a \n character, and if the \n * that anchors it is deleted, the endpoint is pulled to the * next \n that follows in the buffer (or to the end of - * the buffer). + * the buffer). If a span with SPAN_PARAGRAPH flag is pasted + * into another text and the paragraph boundary constraint + * is not satisfied, the span is discarded. */ public static final int SPAN_PARAGRAPH = 0x33; diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java index d9068dc74a2c..44811cb3ef8b 100644 --- a/core/java/android/text/method/Touch.java +++ b/core/java/android/text/method/Touch.java @@ -150,6 +150,7 @@ public class Touch { ds[0].mX = event.getX(); ds[0].mY = event.getY(); + int nx = widget.getScrollX() + (int) dx; int ny = widget.getScrollY() + (int) dy; int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom(); @@ -161,6 +162,8 @@ public class Touch { int oldX = widget.getScrollX(); int oldY = widget.getScrollY(); + scrollTo(widget, layout, nx, ny); + // If we actually scrolled, then cancel the up action. if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) { widget.cancelLongPress(); diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java index c1192776ed4f..fbd992466f5f 100644 --- a/core/java/android/text/util/Linkify.java +++ b/core/java/android/text/util/Linkify.java @@ -218,7 +218,7 @@ public class Linkify { ArrayList<LinkSpec> links = new ArrayList<LinkSpec>(); if ((mask & WEB_URLS) != 0) { - gatherLinks(links, text, Patterns.WEB_URL, + gatherLinks(links, text, Patterns.AUTOLINK_WEB_URL, new String[] { "http://", "https://", "rtsp://" }, sUrlMatchFilter, null); } diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java index 2cc91b9dfe97..9f2bcfd3a136 100644 --- a/core/java/android/util/Patterns.java +++ b/core/java/android/util/Patterns.java @@ -109,11 +109,137 @@ public class Patterns { + "|z[amw]))"; /** - * Good characters for Internationalized Resource Identifiers (IRI). - * This comprises most common used Unicode characters allowed in IRI - * as detailed in RFC 3987. - * Specifically, those two byte Unicode characters are not included. + * Regular expression to match all IANA top-level domains. + * + * List accurate as of 2015/11/24. List taken from: + * http://data.iana.org/TLD/tlds-alpha-by-domain.txt + * This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py + * + * @hide */ + static final String IANA_TOP_LEVEL_DOMAINS = + "(?:" + + "(?:aaa|aarp|abb|abbott|abogado|academy|accenture|accountant|accountants|aco|active" + + "|actor|ads|adult|aeg|aero|afl|agency|aig|airforce|airtel|allfinanz|alsace|amica|amsterdam" + + "|android|apartments|app|apple|aquarelle|aramco|archi|army|arpa|arte|asia|associates" + + "|attorney|auction|audio|auto|autos|axa|azure|a[cdefgilmoqrstuwxz])" + + "|(?:band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bbc|bbva" + + "|bcn|beats|beer|bentley|berlin|best|bet|bharti|bible|bid|bike|bing|bingo|bio|biz|black" + + "|blackfriday|bloomberg|blue|bms|bmw|bnl|bnpparibas|boats|bom|bond|boo|boots|boutique" + + "|bradesco|bridgestone|broadway|broker|brother|brussels|budapest|build|builders|business" + + "|buzz|bzh|b[abdefghijmnorstvwyz])" + + "|(?:cab|cafe|cal|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards" + + "|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|ceb|center|ceo" + + "|cern|cfa|cfd|chanel|channel|chat|cheap|chloe|christmas|chrome|church|cipriani|cisco" + + "|citic|city|cityeats|claims|cleaning|click|clinic|clothing|cloud|club|clubmed|coach" + + "|codes|coffee|college|cologne|com|commbank|community|company|computer|comsec|condos" + + "|construction|consulting|contractors|cooking|cool|coop|corsica|country|coupons|courses" + + "|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc|cuisinella|cymru|cyou|c[acdfghiklmnoruvwxyz])" + + "|(?:dabur|dad|dance|date|dating|datsun|day|dclk|deals|degree|delivery|dell|delta" + + "|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory|discount" + + "|dnp|docs|dog|doha|domains|doosan|download|drive|durban|dvag|d[ejkmoz])" + + "|(?:earth|eat|edu|education|email|emerck|energy|engineer|engineering|enterprises" + + "|epson|equipment|erni|esq|estate|eurovision|eus|events|everbank|exchange|expert|exposed" + + "|express|e[cegrstu])" + + "|(?:fage|fail|fairwinds|faith|family|fan|fans|farm|fashion|feedback|ferrero|film" + + "|final|finance|financial|firmdale|fish|fishing|fit|fitness|flights|florist|flowers|flsmidth" + + "|fly|foo|football|forex|forsale|forum|foundation|frl|frogans|fund|furniture|futbol|fyi" + + "|f[ijkmor])" + + "|(?:gal|gallery|game|garden|gbiz|gdn|gea|gent|genting|ggee|gift|gifts|gives|giving" + + "|glass|gle|global|globo|gmail|gmo|gmx|gold|goldpoint|golf|goo|goog|google|gop|gov|grainger" + + "|graphics|gratis|green|gripe|group|gucci|guge|guide|guitars|guru|g[abdefghilmnpqrstuwy])" + + "|(?:hamburg|hangout|haus|healthcare|help|here|hermes|hiphop|hitachi|hiv|hockey|holdings" + + "|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house|how|hsbc|hyundai" + + "|h[kmnrtu])" + + "|(?:ibm|icbc|ice|icu|ifm|iinet|immo|immobilien|industries|infiniti|info|ing|ink|institute" + + "|insure|int|international|investments|ipiranga|irish|ist|istanbul|itau|iwc|i[delmnoqrst])" + + "|(?:jaguar|java|jcb|jetzt|jewelry|jlc|jll|jobs|joburg|jprs|juegos|j[emop])" + + "|(?:kaufen|kddi|kia|kim|kinder|kitchen|kiwi|koeln|komatsu|krd|kred|kyoto|k[eghimnprwyz])" + + "|(?:lacaixa|lancaster|land|landrover|lasalle|lat|latrobe|law|lawyer|lds|lease|leclerc" + + "|legal|lexus|lgbt|liaison|lidl|life|lifestyle|lighting|limited|limo|linde|link|live" + + "|lixil|loan|loans|lol|london|lotte|lotto|love|ltd|ltda|lupin|luxe|luxury|l[abcikrstuvy])" + + "|(?:madrid|maif|maison|man|management|mango|market|marketing|markets|marriott|mba" + + "|media|meet|melbourne|meme|memorial|men|menu|meo|miami|microsoft|mil|mini|mma|mobi|moda" + + "|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov|movie|movistar" + + "|mtn|mtpc|mtr|museum|mutuelle|m[acdeghklmnopqrstuvwxyz])" + + "|(?:nadex|nagoya|name|navy|nec|net|netbank|network|neustar|new|news|nexus|ngo|nhk" + + "|nico|ninja|nissan|nokia|nra|nrw|ntt|nyc|n[acefgilopruz])" + + "|(?:obi|office|okinawa|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|osaka" + + "|otsuka|ovh|om)" + + "|(?:page|panerai|paris|partners|parts|party|pet|pharmacy|philips|photo|photography" + + "|photos|physio|piaget|pics|pictet|pictures|ping|pink|pizza|place|play|playstation|plumbing" + + "|plus|pohl|poker|porn|post|praxi|press|pro|prod|productions|prof|properties|property" + + "|protection|pub|p[aefghklmnrstwy])" + + "|(?:qpon|quebec|qa)" + + "|(?:racing|realtor|realty|recipes|red|redstone|rehab|reise|reisen|reit|ren|rent|rentals" + + "|repair|report|republican|rest|restaurant|review|reviews|rich|ricoh|rio|rip|rocher|rocks" + + "|rodeo|rsvp|ruhr|run|rwe|ryukyu|r[eosuw])" + + "|(?:saarland|sakura|sale|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|saxo" + + "|sbs|sca|scb|schmidt|scholarships|school|schule|schwarz|science|scor|scot|seat|security" + + "|seek|sener|services|seven|sew|sex|sexy|shiksha|shoes|show|shriram|singles|site|ski" + + "|sky|skype|sncf|soccer|social|software|sohu|solar|solutions|sony|soy|space|spiegel|spreadbetting" + + "|srl|stada|starhub|statoil|stc|stcgroup|stockholm|studio|study|style|sucks|supplies" + + "|supply|support|surf|surgery|suzuki|swatch|swiss|sydney|systems|s[abcdeghijklmnortuvxyz])" + + "|(?:tab|taipei|tatamotors|tatar|tattoo|tax|taxi|team|tech|technology|tel|telefonica" + + "|temasek|tennis|thd|theater|theatre|tickets|tienda|tips|tires|tirol|today|tokyo|tools" + + "|top|toray|toshiba|tours|town|toyota|toys|trade|trading|training|travel|trust|tui|t[cdfghjklmnortvwz])" + + "|(?:ubs|university|uno|uol|u[agksyz])" + + "|(?:vacations|vana|vegas|ventures|versicherung|vet|viajes|video|villas|vin|virgin" + + "|vision|vista|vistaprint|viva|vlaanderen|vodka|vote|voting|voto|voyage|v[aceginu])" + + "|(?:wales|walter|wang|watch|webcam|website|wed|wedding|weir|whoswho|wien|wiki|williamhill" + + "|win|windows|wine|wme|work|works|world|wtc|wtf|w[fs])" + + "|(?:\u03b5\u03bb|\u0431\u0435\u043b|\u0434\u0435\u0442\u0438|\u043a\u043e\u043c|\u043c\u043a\u0434" + + "|\u043c\u043e\u043d|\u043c\u043e\u0441\u043a\u0432\u0430|\u043e\u043d\u043b\u0430\u0439\u043d" + + "|\u043e\u0440\u0433|\u0440\u0443\u0441|\u0440\u0444|\u0441\u0430\u0439\u0442|\u0441\u0440\u0431" + + "|\u0443\u043a\u0440|\u049b\u0430\u0437|\u0570\u0561\u0575|\u05e7\u05d5\u05dd|\u0627\u0631\u0627\u0645\u0643\u0648" + + "|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629" + + "|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0627\u06cc\u0631\u0627\u0646" + + "|\u0628\u0627\u0632\u0627\u0631|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633" + + "|\u0633\u0648\u062f\u0627\u0646|\u0633\u0648\u0631\u064a\u0629|\u0634\u0628\u0643\u0629" + + "|\u0639\u0631\u0627\u0642|\u0639\u0645\u0627\u0646|\u0641\u0644\u0633\u0637\u064a\u0646" + + "|\u0642\u0637\u0631|\u0643\u0648\u0645|\u0645\u0635\u0631|\u0645\u0644\u064a\u0633\u064a\u0627" + + "|\u0645\u0648\u0642\u0639|\u0915\u0949\u092e|\u0928\u0947\u091f|\u092d\u093e\u0930\u0924" + + "|\u0938\u0902\u0917\u0920\u0928|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4" + + "|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd" + + "|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e04\u0e2d\u0e21|\u0e44\u0e17\u0e22" + + "|\u10d2\u10d4|\u307f\u3093\u306a|\u30b0\u30fc\u30b0\u30eb|\u30b3\u30e0|\u4e16\u754c" + + "|\u4e2d\u4fe1|\u4e2d\u56fd|\u4e2d\u570b|\u4e2d\u6587\u7f51|\u4f01\u4e1a|\u4f5b\u5c71" + + "|\u4fe1\u606f|\u5065\u5eb7|\u516b\u5366|\u516c\u53f8|\u516c\u76ca|\u53f0\u6e7e|\u53f0\u7063" + + "|\u5546\u57ce|\u5546\u5e97|\u5546\u6807|\u5728\u7ebf|\u5927\u62ff|\u5a31\u4e50|\u5de5\u884c" + + "|\u5e7f\u4e1c|\u6148\u5584|\u6211\u7231\u4f60|\u624b\u673a|\u653f\u52a1|\u653f\u5e9c" + + "|\u65b0\u52a0\u5761|\u65b0\u95fb|\u65f6\u5c1a|\u673a\u6784|\u6de1\u9a6c\u9521|\u6e38\u620f" + + "|\u70b9\u770b|\u79fb\u52a8|\u7ec4\u7ec7\u673a\u6784|\u7f51\u5740|\u7f51\u5e97|\u7f51\u7edc" + + "|\u8c37\u6b4c|\u96c6\u56e2|\u98de\u5229\u6d66|\u9910\u5385|\u9999\u6e2f|\ub2f7\ub137" + + "|\ub2f7\ucef4|\uc0bc\uc131|\ud55c\uad6d|xbox" + + "|xerox|xin|xn\\-\\-11b4c3d|xn\\-\\-1qqw23a|xn\\-\\-30rr7y|xn\\-\\-3bst00m|xn\\-\\-3ds443g" + + "|xn\\-\\-3e0b707e|xn\\-\\-3pxu8k|xn\\-\\-42c2d9a|xn\\-\\-45brj9c|xn\\-\\-45q11c|xn\\-\\-4gbrim" + + "|xn\\-\\-55qw42g|xn\\-\\-55qx5d|xn\\-\\-6frz82g|xn\\-\\-6qq986b3xl|xn\\-\\-80adxhks" + + "|xn\\-\\-80ao21a|xn\\-\\-80asehdb|xn\\-\\-80aswg|xn\\-\\-90a3ac|xn\\-\\-90ais|xn\\-\\-9dbq2a" + + "|xn\\-\\-9et52u|xn\\-\\-b4w605ferd|xn\\-\\-c1avg|xn\\-\\-c2br7g|xn\\-\\-cg4bki|xn\\-\\-clchc0ea0b2g2a9gcd" + + "|xn\\-\\-czr694b|xn\\-\\-czrs0t|xn\\-\\-czru2d|xn\\-\\-d1acj3b|xn\\-\\-d1alf|xn\\-\\-efvy88h" + + "|xn\\-\\-estv75g|xn\\-\\-fhbei|xn\\-\\-fiq228c5hs|xn\\-\\-fiq64b|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s" + + "|xn\\-\\-fjq720a|xn\\-\\-flw351e|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-gecrj9c" + + "|xn\\-\\-h2brj9c|xn\\-\\-hxt814e|xn\\-\\-i1b6b1a6a2e|xn\\-\\-imr513n|xn\\-\\-io0a7i" + + "|xn\\-\\-j1aef|xn\\-\\-j1amh|xn\\-\\-j6w193g|xn\\-\\-kcrx77d1x4a|xn\\-\\-kprw13d|xn\\-\\-kpry57d" + + "|xn\\-\\-kput3i|xn\\-\\-l1acc|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgb9awbf|xn\\-\\-mgba3a3ejt" + + "|xn\\-\\-mgba3a4f16a|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbab2bd|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e" + + "|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-mgbpl2fh|xn\\-\\-mgbtx2b|xn\\-\\-mgbx4cd0ab" + + "|xn\\-\\-mk1bu44c|xn\\-\\-mxtq1m|xn\\-\\-ngbc5azd|xn\\-\\-node|xn\\-\\-nqv7f|xn\\-\\-nqv7fs00ema" + + "|xn\\-\\-nyqy26a|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1acf|xn\\-\\-p1ai|xn\\-\\-pgbs0dh" + + "|xn\\-\\-pssy2u|xn\\-\\-q9jyb4c|xn\\-\\-qcka1pmc|xn\\-\\-qxam|xn\\-\\-rhqv96g|xn\\-\\-s9brj9c" + + "|xn\\-\\-ses554g|xn\\-\\-t60b56a|xn\\-\\-tckwe|xn\\-\\-unup4y|xn\\-\\-vermgensberater\\-ctb" + + "|xn\\-\\-vermgensberatung\\-pwb|xn\\-\\-vhquv|xn\\-\\-vuq861b|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a" + + "|xn\\-\\-xhq521b|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-y9a3aq|xn\\-\\-yfro4i67o" + + "|xn\\-\\-ygbi2ammx|xn\\-\\-zfr164b|xperia|xxx|xyz)" + + "|(?:yachts|yamaxun|yandex|yodobashi|yoga|yokohama|youtube|y[et])" + + "|(?:zara|zip|zone|zuerich|z[amw]))"; + + /** + * Kept for backward compatibility reasons. + * + * @deprecated Deprecated since it does not include all IRI characters defined in RFC 3987 + */ + @Deprecated public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF"; @@ -125,35 +251,148 @@ public class Patterns { + "|[1-9][0-9]|[0-9]))"); /** + * Valid UCS characters defined in RFC 3987. + */ + private static final String UCS_CHAR = + "\u00A0-\uD7FF" + + "\uF900-\uFDCF" + + "\uFDF0-\uFFEF" + + "\uD800\uDC00-\uD83F\uDFFD" + + "\uD840\uDC00-\uD87F\uDFFD" + + "\uD880\uDC00-\uD8BF\uDFFD" + + "\uD8C0\uDC00-\uD8FF\uDFFD" + + "\uD900\uDC00-\uD93F\uDFFD" + + "\uD940\uDC00-\uD97F\uDFFD" + + "\uD980\uDC00-\uD9BF\uDFFD" + + "\uD9C0\uDC00-\uD9FF\uDFFD" + + "\uDA00\uDC00-\uDA3F\uDFFD" + + "\uDA40\uDC00-\uDA7F\uDFFD" + + "\uDA80\uDC00-\uDABF\uDFFD" + + "\uDAC0\uDC00-\uDAFF\uDFFD" + + "\uDB00\uDC00-\uDB3F\uDFFD" + + "\uDB44\uDC00-\uDB7F\uDFFD"; + + /** + * Valid characters for IRI label defined in RFC 3987. + */ + private static final String LABEL_CHAR = "a-zA-Z0-9" + UCS_CHAR; + + /** + * Valid characters for IRI TLD defined in RFC 3987. + */ + private static final String TLD_CHAR = "a-zA-Z" + UCS_CHAR; + + /** * RFC 1035 Section 2.3.4 limits the labels to a maximum 63 octets. */ - private static final String IRI - = "[" + GOOD_IRI_CHAR + "]([" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]){0,1}"; + private static final String IRI_LABEL = + "[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "\\-]{0,61}[" + LABEL_CHAR + "]){0,1}"; - private static final String GOOD_GTLD_CHAR = - "a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF"; - private static final String GTLD = "[" + GOOD_GTLD_CHAR + "]{2,63}"; - private static final String HOST_NAME = "(" + IRI + "\\.)+" + GTLD; + /** + * RFC 3492 references RFC 1034 and limits Punycode algorithm output to 63 characters. + */ + private static final String PUNYCODE_TLD = "xn\\-\\-[\\w\\-]{0,58}\\w"; + + private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" +")"; + + private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD; public static final Pattern DOMAIN_NAME = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")"); + private static final String PROTOCOL = "(?i:http|https|rtsp):\\/\\/"; + + /* A word boundary or end of input. This is to stop foo.sure from matching as foo.su */ + private static final String WORD_BOUNDARY = "(?:\\b|$|^)"; + + private static final String USER_INFO = "(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)" + + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" + + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@"; + + private static final String PORT_NUMBER = "\\:\\d{1,5}"; + + private static final String PATH_AND_QUERY = "\\/(?:(?:[" + LABEL_CHAR + + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus optional query params + + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*"; + /** * Regular expression pattern to match most part of RFC 3987 - * Internationalized URLs, aka IRIs. Commonly used Unicode characters are - * added. - */ - public static final Pattern WEB_URL = Pattern.compile( - "((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)" - + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" - + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?" - + "(?:" + DOMAIN_NAME + ")" - + "(?:\\:\\d{1,5})?)" // plus option port number - + "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params - + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?" - + "(?:\\b|$)"); // and finally, a word boundary or end of - // input. This is to stop foo.sure from - // matching as foo.su + * Internationalized URLs, aka IRIs. + */ + public static final Pattern WEB_URL = Pattern.compile("(" + + "(" + + "(?:" + PROTOCOL + "(?:" + USER_INFO + ")?" + ")?" + + "(?:" + DOMAIN_NAME + ")" + + "(?:" + PORT_NUMBER + ")?" + + ")" + + "(" + PATH_AND_QUERY + ")?" + + WORD_BOUNDARY + + ")"); + + /** + * Regular expression that matches known TLDs and punycode TLDs + */ + private static final String STRICT_TLD = "(?:" + + IANA_TOP_LEVEL_DOMAINS + "|" + PUNYCODE_TLD + ")"; + + /** + * Regular expression that matches host names using {@link #STRICT_TLD} + */ + private static final String STRICT_HOST_NAME = "(?:(?:" + IRI_LABEL + "\\.)+" + + STRICT_TLD + ")"; + + /** + * Regular expression that matches domain names using either {@link #STRICT_HOST_NAME} or + * {@link #IP_ADDRESS} + */ + private static final Pattern STRICT_DOMAIN_NAME + = Pattern.compile("(?:" + STRICT_HOST_NAME + "|" + IP_ADDRESS + ")"); + + /** + * Regular expression that matches domain names without a TLD + */ + private static final String RELAXED_DOMAIN_NAME = + "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS + ")"; + + /** + * Regular expression to match strings that do not start with a supported protocol. The TLDs + * are expected to be one of the known TLDs. + */ + private static final String WEB_URL_WITHOUT_PROTOCOL = "(" + + WORD_BOUNDARY + + "(?<!:\\/\\/)" + + "(" + + "(?:" + STRICT_DOMAIN_NAME + ")" + + "(?:" + PORT_NUMBER + ")?" + + ")" + + "(?:" + PATH_AND_QUERY + ")?" + + WORD_BOUNDARY + + ")"; + + /** + * Regular expression to match strings that start with a supported protocol. Rules for domain + * names and TLDs are more relaxed. TLDs are optional. + */ + private static final String WEB_URL_WITH_PROTOCOL = "(" + + WORD_BOUNDARY + + "(?:" + + "(?:" + PROTOCOL + "(?:" + USER_INFO + ")?" + ")" + + "(?:" + RELAXED_DOMAIN_NAME + ")?" + + "(?:" + PORT_NUMBER + ")?" + + ")" + + "(?:" + PATH_AND_QUERY + ")?" + + WORD_BOUNDARY + + ")"; + + /** + * Regular expression pattern to match IRIs. If a string starts with http(s):// the expression + * tries to match the URL structure with a relaxed rule for TLDs. If the string does not start + * with http(s):// the TLDs are expected to be one of the known TLDs. + * + * @hide + */ + public static final Pattern AUTOLINK_WEB_URL = Pattern.compile( + "(" + WEB_URL_WITH_PROTOCOL + "|" + WEB_URL_WITHOUT_PROTOCOL + ")"); public static final Pattern EMAIL_ADDRESS = Pattern.compile( diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 7a544b8f0bb3..a0f51425ab1e 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -973,6 +973,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * </p> * * @see #getAxisValue(int, int) + * {@hide} */ public static final int AXIS_SCROLL = 26; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 3a1e9ab2cac3..68f1ac3c1108 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3592,6 +3592,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private int[] mDrawableState = null; + /** Whether draw() is currently being called. */ + private boolean mInDraw = false; + ViewOutlineProvider mOutlineProvider = ViewOutlineProvider.BACKGROUND; /** @@ -16470,6 +16473,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @CallSuper public void draw(Canvas canvas) { + mInDraw = true; + final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); @@ -16514,6 +16519,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, onDrawForeground(canvas); // we're done... + mInDraw = false; return; } @@ -16661,6 +16667,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); + + mInDraw = false; } /** @@ -17105,7 +17113,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @Override public void invalidateDrawable(@NonNull Drawable drawable) { - if (verifyDrawable(drawable)) { + // Don't invalidate if a drawable changes during drawing. + if (verifyDrawable(drawable) && !mInDraw) { final Rect dirty = drawable.getDirtyBounds(); final int scrollX = mScrollX; final int scrollY = mScrollY; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1c9f3b403ef8..0fb39516d4d2 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -384,6 +384,8 @@ public final class ViewRootImpl implements ViewParent, int localChanges; } + private String mTag = TAG; + public ViewRootImpl(Context context, Display display) { mContext = context; mWindowSession = WindowManagerGlobal.getWindowSession(); @@ -510,6 +512,7 @@ public final class ViewRootImpl implements ViewParent, mWindowAttributes.packageName = mBasePackageName; } attrs = mWindowAttributes; + setTag(); // Keep track of the actual window flags supplied by the client. mClientWindowLayoutFlags = attrs.flags; @@ -546,7 +549,7 @@ public final class ViewRootImpl implements ViewParent, attrs.backup(); mTranslator.translateWindowLayout(attrs); } - if (DEBUG_LAYOUT) Log.d(TAG, "WindowLayout in setView:" + attrs); + if (DEBUG_LAYOUT) Log.d(mTag, "WindowLayout in setView:" + attrs); if (!compatibilityInfo.supportsScreen()) { attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; @@ -607,7 +610,7 @@ public final class ViewRootImpl implements ViewParent, mPendingContentInsets.set(mAttachInfo.mContentInsets); mPendingStableInsets.set(mAttachInfo.mStableInsets); mPendingVisibleInsets.set(0, 0, 0, 0); - if (DEBUG_LAYOUT) Log.v(TAG, "Added window " + mWindow); + if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow); if (res < WindowManagerGlobal.ADD_OKAY) { mAttachInfo.mRootView = null; mAdded = false; @@ -701,6 +704,13 @@ public final class ViewRootImpl implements ViewParent, } } + private void setTag() { + final String[] split = mWindowAttributes.getTitle().toString().split("\\."); + if (split.length > 0) { + mTag = TAG + "[" + split[split.length - 1] + "]"; + } + } + /** Whether the window is in local focus mode or not */ private boolean isInLocalFocusMode() { return (mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0; @@ -990,7 +1000,7 @@ public final class ViewRootImpl implements ViewParent, @Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); - if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty); + if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty); if (dirty == null) { invalidate(); @@ -1174,7 +1184,7 @@ public final class ViewRootImpl implements ViewParent, private boolean collectViewAttributes() { if (mAttachInfo.mRecomputeGlobalAttributes) { - //Log.i(TAG, "Computing view hierarchy attributes!"); + //Log.i(mTag, "Computing view hierarchy attributes!"); mAttachInfo.mRecomputeGlobalAttributes = false; boolean oldScreenOn = mAttachInfo.mKeepScreenOn; mAttachInfo.mKeepScreenOn = false; @@ -1215,7 +1225,7 @@ public final class ViewRootImpl implements ViewParent, int childHeightMeasureSpec; boolean windowSizeMayChange = false; - if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(TAG, + if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag, "Measuring " + host + " in display " + desiredWindowWidth + "x" + desiredWindowHeight + "..."); @@ -1231,26 +1241,26 @@ public final class ViewRootImpl implements ViewParent, if (mTmpValue.type == TypedValue.TYPE_DIMENSION) { baseSize = (int)mTmpValue.getDimension(packageMetrics); } - if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": baseSize=" + baseSize); + if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize); if (baseSize != 0 && desiredWindowWidth > baseSize) { childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); - if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured (" + if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { goodMeasure = true; } else { // Didn't fit in that size... try expanding a bit. baseSize = (baseSize+desiredWindowWidth)/2; - if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": next baseSize=" + if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize=" + baseSize); childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); - if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured (" + if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { - if (DEBUG_DIALOG) Log.v(TAG, "Good!"); + if (DEBUG_DIALOG) Log.v(mTag, "Good!"); goodMeasure = true; } } @@ -1350,8 +1360,8 @@ public final class ViewRootImpl implements ViewParent, int desiredWindowHeight; final int viewVisibility = getHostVisibility(); - boolean viewVisibilityChanged = mViewVisibility != viewVisibility - || mNewSurfaceNeeded; + final boolean viewVisibilityChanged = !mFirst + && (mViewVisibility != viewVisibility || mNewSurfaceNeeded); WindowManager.LayoutParams params = null; if (mWindowAttributesChanged) { @@ -1401,7 +1411,6 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mHasWindowFocus = false; mAttachInfo.mWindowVisibility = viewVisibility; mAttachInfo.mRecomputeGlobalAttributes = false; - viewVisibilityChanged = false; mLastConfiguration.setTo(host.getResources().getConfiguration()); mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility; // Set the layout direction if it has not been set before (inherit is the default) @@ -1411,14 +1420,13 @@ public final class ViewRootImpl implements ViewParent, host.dispatchAttachedToWindow(mAttachInfo, 0); mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); dispatchApplyInsets(host); - //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn); + //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn); } else { desiredWindowWidth = frame.width(); desiredWindowHeight = frame.height(); if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) { - if (DEBUG_ORIENTATION) Log.v(TAG, - "View " + host + " resized to: " + frame); + if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame); mFullRedrawNeeded = true; mLayoutRequested = true; windowSizeMayChange = true; @@ -1471,28 +1479,22 @@ public final class ViewRootImpl implements ViewParent, } if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) { mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); - if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: " + if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: " + mAttachInfo.mVisibleInsets); } if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) { insetsChanged = true; } - if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT - || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { + if ((lp.width == ViewGroup.LayoutParams.WRAP_CONTENT + || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) + && (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL + || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD)) { windowSizeMayChange = true; - - if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL - || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) { - // NOTE -- system code, won't try to do compat mode. - Point size = new Point(); - mDisplay.getRealSize(size); - desiredWindowWidth = size.x; - desiredWindowHeight = size.y; - } else { - DisplayMetrics packageMetrics = res.getDisplayMetrics(); - desiredWindowWidth = packageMetrics.widthPixels; - desiredWindowHeight = packageMetrics.heightPixels; - } + // NOTE -- system code, won't try to do compat mode. + Point size = new Point(); + mDisplay.getRealSize(size); + desiredWindowWidth = size.x; + desiredWindowHeight = size.y; } } @@ -1616,7 +1618,7 @@ public final class ViewRootImpl implements ViewParent, try { if (DEBUG_LAYOUT) { - Log.i(TAG, "host=w:" + host.getMeasuredWidth() + ", h:" + + Log.i(mTag, "host=w:" + host.getMeasuredWidth() + ", h:" + host.getMeasuredHeight() + ", params=" + params); } @@ -1634,7 +1636,7 @@ public final class ViewRootImpl implements ViewParent, final int surfaceGenerationId = mSurface.getGenerationId(); relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); - if (DEBUG_LAYOUT) Log.v(TAG, "relayout: frame=" + frame.toShortString() + if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString() + " overscan=" + mPendingOverscanInsets.toShortString() + " content=" + mPendingContentInsets.toShortString() + " visible=" + mPendingVisibleInsets.toShortString() @@ -1643,7 +1645,7 @@ public final class ViewRootImpl implements ViewParent, + " surface=" + mSurface); if (mPendingConfiguration.seq != 0) { - if (DEBUG_CONFIGURATION) Log.v(TAG, "Visible with new config: " + if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: " + mPendingConfiguration); updateConfiguration(new Configuration(mPendingConfiguration), !mFirst); mPendingConfiguration.seq = 0; @@ -1662,19 +1664,19 @@ public final class ViewRootImpl implements ViewParent, & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0; if (contentInsetsChanged) { mAttachInfo.mContentInsets.set(mPendingContentInsets); - if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: " + if (DEBUG_LAYOUT) Log.v(mTag, "Content insets changing to: " + mAttachInfo.mContentInsets); } if (overscanInsetsChanged) { mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets); - if (DEBUG_LAYOUT) Log.v(TAG, "Overscan insets changing to: " + if (DEBUG_LAYOUT) Log.v(mTag, "Overscan insets changing to: " + mAttachInfo.mOverscanInsets); // Need to relayout with content insets. contentInsetsChanged = true; } if (stableInsetsChanged) { mAttachInfo.mStableInsets.set(mPendingStableInsets); - if (DEBUG_LAYOUT) Log.v(TAG, "Decor insets changing to: " + if (DEBUG_LAYOUT) Log.v(mTag, "Decor insets changing to: " + mAttachInfo.mStableInsets); // Need to relayout with content insets. contentInsetsChanged = true; @@ -1691,7 +1693,7 @@ public final class ViewRootImpl implements ViewParent, } if (visibleInsetsChanged) { mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); - if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: " + if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: " + mAttachInfo.mVisibleInsets); } @@ -1872,7 +1874,7 @@ public final class ViewRootImpl implements ViewParent, int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); - if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth=" + if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth=" + mWidth + " measuredWidth=" + host.getMeasuredWidth() + " mHeight=" + mHeight + " measuredHeight=" + host.getMeasuredHeight() @@ -1902,7 +1904,7 @@ public final class ViewRootImpl implements ViewParent, } if (measureAgain) { - if (DEBUG_LAYOUT) Log.v(TAG, + if (DEBUG_LAYOUT) Log.v(mTag, "And hey let's measure once more: width=" + width + " height=" + height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); @@ -2024,15 +2026,15 @@ public final class ViewRootImpl implements ViewParent, if (mFirst) { // handle first focus request - if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: mView.hasFocus()=" + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus()); if (mView != null) { if (!mView.hasFocus()) { mView.requestFocus(View.FOCUS_FORWARD); - if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: requested focused view=" + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view=" + mView.findFocus()); } else { - if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: existing focused view=" + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: existing focused view=" + mView.findFocus()); } } @@ -2104,11 +2106,11 @@ public final class ViewRootImpl implements ViewParent, } private void handleOutOfResourcesException(Surface.OutOfResourcesException e) { - Log.e(TAG, "OutOfResourcesException initializing HW surface", e); + Log.e(mTag, "OutOfResourcesException initializing HW surface", e); try { if (!mWindowSession.outOfMemory(mWindow) && Process.myUid() != Process.SYSTEM_UID) { - Slog.w(TAG, "No processes killed for memory; killing self"); + Slog.w(mTag, "No processes killed for memory; killing self"); Process.killProcess(Process.myPid()); } } catch (RemoteException ex) { @@ -2184,7 +2186,7 @@ public final class ViewRootImpl implements ViewParent, final View host = mView; if (DEBUG_ORIENTATION || DEBUG_LAYOUT) { - Log.v(TAG, "Laying out " + host + " to (" + + Log.v(mTag, "Laying out " + host + " to (" + host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")"); } @@ -2427,11 +2429,11 @@ public final class ViewRootImpl implements ViewParent, String thisHash = Integer.toHexString(System.identityHashCode(this)); long frameTime = nowTime - mFpsPrevTime; long totalTime = nowTime - mFpsStartTime; - Log.v(TAG, "0x" + thisHash + "\tFrame time:\t" + frameTime); + Log.v(mTag, "0x" + thisHash + "\tFrame time:\t" + frameTime); mFpsPrevTime = nowTime; if (totalTime > 1000) { float fps = (float) mFpsNumFrames * 1000 / totalTime; - Log.v(TAG, "0x" + thisHash + "\tFPS:\t" + fps); + Log.v(mTag, "0x" + thisHash + "\tFPS:\t" + fps); mFpsStartTime = nowTime; mFpsNumFrames = 0; } @@ -2473,7 +2475,7 @@ public final class ViewRootImpl implements ViewParent, try { mWindowDrawCountDown.await(); } catch (InterruptedException e) { - Log.e(TAG, "Window redraw count down interruped!"); + Log.e(mTag, "Window redraw count down interruped!"); } mWindowDrawCountDown = null; } @@ -2483,7 +2485,7 @@ public final class ViewRootImpl implements ViewParent, } if (LOCAL_LOGV) { - Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle()); + Log.v(mTag, "FINISHED DRAWING: " + mWindowAttributes.getTitle()); } if (mSurfaceHolder != null && mSurface.isValid()) { mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder); @@ -2566,7 +2568,7 @@ public final class ViewRootImpl implements ViewParent, } if (DEBUG_ORIENTATION || DEBUG_DRAW) { - Log.v(TAG, "Draw " + mView + "/" + Log.v(mTag, "Draw " + mView + "/" + mWindowAttributes.getTitle() + ": dirty={" + dirty.left + "," + dirty.top + "," + dirty.right + "," + dirty.bottom + "} surface=" @@ -2700,7 +2702,7 @@ public final class ViewRootImpl implements ViewParent, handleOutOfResourcesException(e); return false; } catch (IllegalArgumentException e) { - Log.e(TAG, "Could not lock surface", e); + Log.e(mTag, "Could not lock surface", e); // Don't assume this is due to out of memory, it could be // something else, and if it is something else then we could // kill stuff (or ourself) for no reason. @@ -2710,7 +2712,7 @@ public final class ViewRootImpl implements ViewParent, try { if (DEBUG_ORIENTATION || DEBUG_DRAW) { - Log.v(TAG, "Surface " + surface + " drawing to bitmap w=" + Log.v(mTag, "Surface " + surface + " drawing to bitmap w=" + canvas.getWidth() + ", h=" + canvas.getHeight()); //canvas.drawARGB(255, 255, 0, 0); } @@ -2733,7 +2735,7 @@ public final class ViewRootImpl implements ViewParent, if (DEBUG_DRAW) { Context cxt = mView.getContext(); - Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + + Log.i(mTag, "Drawing: package:" + cxt.getPackageName() + ", metrics=" + cxt.getResources().getDisplayMetrics() + ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); } @@ -2758,14 +2760,14 @@ public final class ViewRootImpl implements ViewParent, try { surface.unlockCanvasAndPost(canvas); } catch (IllegalArgumentException e) { - Log.e(TAG, "Could not unlock surface", e); + Log.e(mTag, "Could not unlock surface", e); mLayoutRequested = true; // ask wm for a new surface next time. //noinspection ReturnInsideFinallyBlock return false; } if (LOCAL_LOGV) { - Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost"); + Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost"); } } return true; @@ -2870,14 +2872,14 @@ public final class ViewRootImpl implements ViewParent, // view is visible. rectangle = null; } - if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Eval scroll: focus=" + focus + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Eval scroll: focus=" + focus + " rectangle=" + rectangle + " ci=" + ci + " vi=" + vi); if (focus == lastScrolledFocus && !mScrollMayChange && rectangle == null) { // Optimization: if the focus hasn't changed since last // time, and no layout has happened, then just leave things // as they are. - if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Keeping scroll y=" + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Keeping scroll y=" + mScrollY + " vi=" + vi.toShortString()); } else { // We need to determine if the currently focused view is @@ -2885,51 +2887,51 @@ public final class ViewRootImpl implements ViewParent, // a pan so it can be seen. mLastScrolledFocus = new WeakReference<View>(focus); mScrollMayChange = false; - if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Need to scroll?"); + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Need to scroll?"); // Try to find the rectangle from the focus view. if (focus.getGlobalVisibleRect(mVisRect, null)) { - if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Root w=" + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Root w=" + mView.getWidth() + " h=" + mView.getHeight() + " ci=" + ci.toShortString() + " vi=" + vi.toShortString()); if (rectangle == null) { focus.getFocusedRect(mTempRect); - if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Focus " + focus + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Focus " + focus + ": focusRect=" + mTempRect.toShortString()); if (mView instanceof ViewGroup) { ((ViewGroup) mView).offsetDescendantRectToMyCoords( focus, mTempRect); } - if (DEBUG_INPUT_RESIZE) Log.v(TAG, + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Focus in window: focusRect=" + mTempRect.toShortString() + " visRect=" + mVisRect.toShortString()); } else { mTempRect.set(rectangle); - if (DEBUG_INPUT_RESIZE) Log.v(TAG, + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Request scroll to rect: " + mTempRect.toShortString() + " visRect=" + mVisRect.toShortString()); } if (mTempRect.intersect(mVisRect)) { - if (DEBUG_INPUT_RESIZE) Log.v(TAG, + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Focus window visible rect: " + mTempRect.toShortString()); if (mTempRect.height() > (mView.getHeight()-vi.top-vi.bottom)) { // If the focus simply is not going to fit, then // best is probably just to leave things as-is. - if (DEBUG_INPUT_RESIZE) Log.v(TAG, + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Too tall; leaving scrollY=" + scrollY); } else if ((mTempRect.top-scrollY) < vi.top) { scrollY -= vi.top - (mTempRect.top-scrollY); - if (DEBUG_INPUT_RESIZE) Log.v(TAG, + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Top covered; scrollY=" + scrollY); } else if ((mTempRect.bottom-scrollY) > (mView.getHeight()-vi.bottom)) { scrollY += (mTempRect.bottom-scrollY) - (mView.getHeight()-vi.bottom); - if (DEBUG_INPUT_RESIZE) Log.v(TAG, + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Bottom covered; scrollY=" + scrollY); } handled = true; @@ -2939,7 +2941,7 @@ public final class ViewRootImpl implements ViewParent, } if (scrollY != mScrollY) { - if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Pan scroll changed: old=" + if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Pan scroll changed: old=" + mScrollY + " , new=" + scrollY); if (!immediate) { if (mScroller == null) { @@ -3018,7 +3020,7 @@ public final class ViewRootImpl implements ViewParent, void setPointerCapture(View view) { if (!mAttachInfo.mHasWindowFocus) { - Log.w(TAG, "Can't set capture if it's not focused."); + Log.w(mTag, "Can't set capture if it's not focused."); return; } if (mCapturingView == view) { @@ -3044,7 +3046,7 @@ public final class ViewRootImpl implements ViewParent, @Override public void requestChildFocus(View child, View focused) { if (DEBUG_INPUT_RESIZE) { - Log.v(TAG, "Request child focus: focus now " + focused); + Log.v(mTag, "Request child focus: focus now " + focused); } checkThread(); scheduleTraversals(); @@ -3053,7 +3055,7 @@ public final class ViewRootImpl implements ViewParent, @Override public void clearChildFocus(View child) { if (DEBUG_INPUT_RESIZE) { - Log.v(TAG, "Clearing child focus"); + Log.v(mTag, "Clearing child focus"); } checkThread(); scheduleTraversals(); @@ -3152,7 +3154,7 @@ public final class ViewRootImpl implements ViewParent, } void updateConfiguration(Configuration config, boolean force) { - if (DEBUG_CONFIGURATION) Log.v(TAG, + if (DEBUG_CONFIGURATION) Log.v(mTag, "Applying new config to window " + mWindowAttributes.getTitle() + ": " + config); @@ -3388,10 +3390,10 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mHardwareRenderer.initializeIfNeeded( mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets); } catch (OutOfResourcesException e) { - Log.e(TAG, "OutOfResourcesException locking surface", e); + Log.e(mTag, "OutOfResourcesException locking surface", e); try { if (!mWindowSession.outOfMemory(mWindow)) { - Slog.w(TAG, "No processes killed for memory; killing self"); + Slog.w(mTag, "No processes killed for memory; killing self"); Process.killProcess(Process.myPid()); } } catch (RemoteException ex) { @@ -3714,7 +3716,7 @@ public final class ViewRootImpl implements ViewParent, */ protected void onDeliverToNext(QueuedInputEvent q) { if (DEBUG_INPUT_STAGES) { - Log.v(TAG, "Done with " + getClass().getSimpleName() + ". " + q); + Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q); } if (mNext != null) { mNext.deliver(q); @@ -3725,7 +3727,7 @@ public final class ViewRootImpl implements ViewParent, protected boolean shouldDropInputEvent(QueuedInputEvent q) { if (mView == null || !mAdded) { - Slog.w(TAG, "Dropping event due to root view being removed: " + q.mEvent); + Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent); return true; } else if ((!mAttachInfo.mHasWindowFocus && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) || mStopped @@ -3736,12 +3738,12 @@ public final class ViewRootImpl implements ViewParent, if (isTerminalInputEvent(q.mEvent)) { // Don't drop terminal input events, however mark them as canceled. q.mEvent.cancel(); - Slog.w(TAG, "Cancelling event due to no window focus: " + q.mEvent); + Slog.w(mTag, "Cancelling event due to no window focus: " + q.mEvent); return false; } // Drop non-terminal input events. - Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent); + Slog.w(mTag, "Dropping event due to no window focus: " + q.mEvent); return true; } return false; @@ -3981,7 +3983,7 @@ public final class ViewRootImpl implements ViewParent, InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { final InputEvent event = q.mEvent; - if (DEBUG_IMF) Log.v(TAG, "Sending input event to IME: " + event); + if (DEBUG_IMF) Log.v(mTag, "Sending input event to IME: " + event); int result = imm.dispatchInputEvent(event, q, this, mHandler); if (result == InputMethodManager.DISPATCH_HANDLED) { return FINISH_HANDLED; @@ -4413,7 +4415,7 @@ public final class ViewRootImpl implements ViewParent, break; } - if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + mX.position + " step=" + if (DEBUG_TRACKBALL) Log.v(mTag, "TB X=" + mX.position + " step=" + mX.step + " dir=" + mX.dir + " acc=" + mX.acceleration + " move=" + event.getX() + " / Y=" + mY.position + " step=" @@ -4452,11 +4454,11 @@ public final class ViewRootImpl implements ViewParent, if (keycode != 0) { if (movement < 0) movement = -movement; int accelMovement = (int)(movement * accel); - if (DEBUG_TRACKBALL) Log.v(TAG, "Move: movement=" + movement + if (DEBUG_TRACKBALL) Log.v(mTag, "Move: movement=" + movement + " accelMovement=" + accelMovement + " accel=" + accel); if (accelMovement > movement) { - if (DEBUG_TRACKBALL) Log.v(TAG, "Delivering fake DPAD: " + if (DEBUG_TRACKBALL) Log.v(mTag, "Delivering fake DPAD: " + keycode); movement--; int repeatCount = accelMovement - movement; @@ -4466,7 +4468,7 @@ public final class ViewRootImpl implements ViewParent, InputDevice.SOURCE_KEYBOARD)); } while (movement > 0) { - if (DEBUG_TRACKBALL) Log.v(TAG, "Delivering fake DPAD: " + if (DEBUG_TRACKBALL) Log.v(mTag, "Delivering fake DPAD: " + keycode); movement--; curTime = SystemClock.uptimeMillis(); @@ -4708,7 +4710,7 @@ public final class ViewRootImpl implements ViewParent, update(event, true); break; default: - Log.w(TAG, "Unexpected action: " + event.getActionMasked()); + Log.w(mTag, "Unexpected action: " + event.getActionMasked()); } } @@ -5347,7 +5349,7 @@ public final class ViewRootImpl implements ViewParent, mWindowSession.dragRecipientEntered(mWindow); } } catch (RemoteException e) { - Slog.e(TAG, "Unable to note drag target change"); + Slog.e(mTag, "Unable to note drag target change"); } } @@ -5355,10 +5357,10 @@ public final class ViewRootImpl implements ViewParent, if (what == DragEvent.ACTION_DROP) { mDragDescription = null; try { - Log.i(TAG, "Reporting drop result: " + result); + Log.i(mTag, "Reporting drop result: " + result); mWindowSession.reportDropResult(mWindow, result); } catch (RemoteException e) { - Log.e(TAG, "Unable to report drop result"); + Log.e(mTag, "Unable to report drop result"); } } @@ -5444,14 +5446,14 @@ public final class ViewRootImpl implements ViewParent, mTranslator.translateWindowLayout(params); } if (params != null) { - if (DBG) Log.d(TAG, "WindowLayout in layoutWindow:" + params); + if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params); } mPendingConfiguration.seq = 0; - //Log.d(TAG, ">>>>>> CALLING relayout"); + //Log.d(mTag, ">>>>>> CALLING relayout"); if (params != null && mOrigWindowType != params.type) { // For compatibility with old apps, don't crash here. if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - Slog.w(TAG, "Window type can not be changed after " + Slog.w(mTag, "Window type can not be changed after " + "the window is added; ignoring change of " + mView); params.type = mOrigWindowType; } @@ -5463,7 +5465,7 @@ public final class ViewRootImpl implements ViewParent, viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets, mPendingStableInsets, mPendingOutsets, mPendingConfiguration, mSurface); - //Log.d(TAG, "<<<<<< BACK FROM relayout"); + //Log.d(mTag, "<<<<<< BACK FROM relayout"); if (restore) { params.restore(); } @@ -5510,7 +5512,7 @@ public final class ViewRootImpl implements ViewParent, } } catch (IllegalStateException e) { // Exception thrown by getAudioManager() when mView is null - Log.e(TAG, "FATAL EXCEPTION when attempting to play sound effect: " + e); + Log.e(mTag, "FATAL EXCEPTION when attempting to play sound effect: " + e); e.printStackTrace(); } } @@ -5633,7 +5635,7 @@ public final class ViewRootImpl implements ViewParent, if (!mIsDrawing) { destroyHardwareRenderer(); } else { - Log.e(TAG, "Attempting to destroy the window while drawing!\n" + + Log.e(mTag, "Attempting to destroy the window while drawing!\n" + " window=" + this + ", title=" + mWindowAttributes.getTitle()); } mHandler.sendEmptyMessage(MSG_DIE); @@ -5642,7 +5644,7 @@ public final class ViewRootImpl implements ViewParent, void doDie() { checkThread(); - if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface); + if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface); synchronized (this) { if (mRemoved) { return; @@ -5735,7 +5737,7 @@ public final class ViewRootImpl implements ViewParent, public void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, Configuration newConfig, Rect backDropFrame) { - if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": frame=" + frame.toShortString() + if (DEBUG_LAYOUT) Log.v(mTag, "Resizing " + this + ": frame=" + frame.toShortString() + " contentInsets=" + contentInsets.toShortString() + " visibleInsets=" + visibleInsets.toShortString() + " reportDraw=" + reportDraw @@ -5773,7 +5775,7 @@ public final class ViewRootImpl implements ViewParent, } public void dispatchMoved(int newX, int newY) { - if (DEBUG_LAYOUT) Log.v(TAG, "Window moved " + this + ": newX=" + newX + " newY=" + newY); + if (DEBUG_LAYOUT) Log.v(mTag, "Window moved " + this + ": newX=" + newX + " newY=" + newY); if (mTranslator != null) { PointF point = new PointF(newX, newY); mTranslator.translatePointInScreenToAppWindow(point); @@ -6690,7 +6692,7 @@ public final class ViewRootImpl implements ViewParent, } void changeCanvasOpacity(boolean opaque) { - Log.d(TAG, "changeCanvasOpacity: opaque=" + opaque); + Log.d(mTag, "changeCanvasOpacity: opaque=" + opaque); if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.setOpaque(opaque); } diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 01bfbb5cea9f..ecec25852cbb 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -623,14 +623,16 @@ public interface WindowManagerPolicy { * decorations that can never be removed. That is, system bar or * button bar. */ - public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation); + public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, + int uiMode); /** * Return the display height available after excluding any screen * decorations that can never be removed. That is, system bar or * button bar. */ - public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation); + public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation, + int uiMode); /** * Return the available screen width that we should report for the @@ -638,7 +640,8 @@ public interface WindowManagerPolicy { * {@link #getNonDecorDisplayWidth(int, int, int)}; it may be smaller than * that to account for more transient decoration like a status bar. */ - public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation); + public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation, + int uiMode); /** * Return the available screen height that we should report for the @@ -646,7 +649,8 @@ public interface WindowManagerPolicy { * {@link #getNonDecorDisplayHeight(int, int, int)}; it may be smaller than * that to account for more transient decoration like a status bar. */ - public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation); + public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation, + int uiMode); /** * Return whether the given window is forcibly hiding all windows except windows with @@ -861,11 +865,11 @@ public interface WindowManagerPolicy { * @param isDefaultDisplay true if window is on {@link Display#DEFAULT_DISPLAY}. * @param displayWidth The current full width of the screen. * @param displayHeight The current full height of the screen. - * @param displayRotation The current rotation being applied to the base - * window. + * @param displayRotation The current rotation being applied to the base window. + * @param uiMode The current uiMode in configuration. */ public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight, - int displayRotation); + int displayRotation, int uiMode); /** * Returns the bottom-most layer of the system decor, above which no policy decor should diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 9442226a9be5..9595db2ccd21 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -987,9 +987,6 @@ public abstract class WebSettings { * @deprecated Database paths are managed by the implementation and calling this method * will have no effect. */ - // This will update WebCore when the Sync runs in the C++ side. - // Note that the WebCore Database Tracker only allows the path to be set - // once. @Deprecated public abstract void setDatabasePath(String databasePath); @@ -1000,8 +997,10 @@ public abstract class WebSettings { * * @param databasePath a path to the directory where databases should be * saved. + * @deprecated Geolocation database are managed by the implementation and calling this method + * will have no effect. */ - // This will update WebCore when the Sync runs in the C++ side. + @Deprecated public abstract void setGeolocationDatabasePath(String databasePath); /** @@ -1102,8 +1101,6 @@ public abstract class WebSettings { * via the JavaScript Geolocation API. * </ul> * <p> - * As an option, it is possible to store previous locations and web origin - * permissions in a database. See {@link #setGeolocationDatabasePath}. * * @param flag whether Geolocation should be enabled */ diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index 0032f177633e..48d1d2b1b32f 100644 --- a/core/java/android/widget/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -20,6 +20,8 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -134,7 +136,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter } @Override - public void initForMenu(Context context, MenuBuilder menu) { + public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) { super.initForMenu(context, menu); final Resources res = context.getResources(); @@ -629,8 +631,16 @@ public class ActionMenuPresenter extends BaseMenuPresenter } public boolean flagActionItems() { - final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems(); - final int itemsSize = visibleItems.size(); + final ArrayList<MenuItemImpl> visibleItems; + final int itemsSize; + if (mMenu != null) { + visibleItems = mMenu.getVisibleItems(); + itemsSize = visibleItems.size(); + } else { + visibleItems = null; + itemsSize = 0; + } + int maxActions = mMaxItems; int widthLimit = mActionItemWidthLimit; final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); @@ -786,7 +796,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter if (isVisible) { // Not a submenu, but treat it like one. super.onSubMenuSelected(null); - } else { + } else if (mMenu != null) { mMenu.close(false /* closeAllMenus */); } } @@ -938,7 +948,9 @@ public class ActionMenuPresenter extends BaseMenuPresenter @Override protected void onDismiss() { - mMenu.close(); + if (mMenu != null) { + mMenu.close(); + } mOverflowPopup = null; super.onDismiss(); @@ -999,7 +1011,9 @@ public class ActionMenuPresenter extends BaseMenuPresenter } public void run() { - mMenu.changeMenuMode(); + if (mMenu != null) { + mMenu.changeMenuMode(); + } final View menuView = (View) mMenuView; if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) { mOverflowPopup = mPopup; diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java index 1f02c3b33e03..4d0a1c86fd92 100644 --- a/core/java/android/widget/ActionMenuView.java +++ b/core/java/android/widget/ActionMenuView.java @@ -622,7 +622,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo } /** @hide */ - public void initialize(MenuBuilder menu) { + public void initialize(@Nullable MenuBuilder menu) { mMenu = menu; } diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java index acbf5eb8d699..8e711b07b213 100644 --- a/core/java/android/widget/Toolbar.java +++ b/core/java/android/widget/Toolbar.java @@ -2070,7 +2070,7 @@ public class Toolbar extends ViewGroup { MenuItemImpl mCurrentExpandedItem; @Override - public void initForMenu(Context context, MenuBuilder menu) { + public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) { // Clear the expanded action view when menus change. if (mMenu != null && mCurrentExpandedItem != null) { mMenu.collapseItemActionView(mCurrentExpandedItem); diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 40eaaf7bae80..cc2f7142f893 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -23,7 +23,6 @@ import com.android.internal.view.RootViewSurfaceTaker; import com.android.internal.view.StandaloneActionMode; import com.android.internal.view.menu.ContextMenuBuilder; import com.android.internal.view.menu.MenuHelper; -import com.android.internal.view.menu.MenuPresenter; import com.android.internal.widget.ActionBarContextView; import com.android.internal.widget.BackgroundFallback; import com.android.internal.widget.DecorCaptionView; @@ -72,6 +71,7 @@ import android.widget.PopupWindow; import static android.app.ActivityManager.StackId; import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.View.MeasureSpec.AT_MOST; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.getMode; @@ -194,7 +194,12 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private Drawable mCaptionBackgroundDrawable; private Drawable mUserCaptionBackgroundDrawable; - DecorView(Context context, int featureId, PhoneWindow window) { + private float mAvailableWidth; + + String mLogTag = TAG; + + DecorView(Context context, int featureId, PhoneWindow window, + WindowManager.LayoutParams params) { super(context); mFeatureId = featureId; @@ -210,7 +215,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mSemiTransparentStatusBarColor = context.getResources().getColor( R.color.system_bar_background_semi_transparent, null /* theme */); + updateAvailableWidth(); + setWindow(window); + + updateLogTag(params); } void setBackgroundFallback(int resId) { @@ -408,7 +417,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind if (mFeatureId >= 0) { if (action == MotionEvent.ACTION_DOWN) { - Log.i(TAG, "Watchiing!"); + Log.i(mLogTag, "Watchiing!"); mWatchingForMenu = true; mDownY = (int) event.getY(); return false; @@ -421,7 +430,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind int y = (int)event.getY(); if (action == MotionEvent.ACTION_MOVE) { if (y > (mDownY+30)) { - Log.i(TAG, "Closing!"); + Log.i(mLogTag, "Closing!"); mWindow.closePanel(mFeatureId); mWatchingForMenu = false; return true; @@ -433,13 +442,13 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind return false; } - //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY() + //Log.i(mLogTag, "Intercept: action=" + action + " y=" + event.getY() // + " (in " + getHeight() + ")"); if (action == MotionEvent.ACTION_DOWN) { int y = (int)event.getY(); if (y >= (getHeight()-5) && !mWindow.hasChildren()) { - Log.i(TAG, "Watching!"); + Log.i(mLogTag, "Watching!"); mWatchingForMenu = true; } return false; @@ -452,7 +461,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind int y = (int)event.getY(); if (action == MotionEvent.ACTION_MOVE) { if (y < (getHeight()-30)) { - Log.i(TAG, "Opening!"); + Log.i(mLogTag, "Opening!"); mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, new KeyEvent( KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)); mWatchingForMenu = false; @@ -543,15 +552,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); - final boolean isPortrait = metrics.widthPixels < metrics.heightPixels; + final boolean isPortrait = + getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; final int widthMode = getMode(widthMeasureSpec); final int heightMode = getMode(heightMeasureSpec); boolean fixedWidth = false; if (widthMode == AT_MOST) { - final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor - : mWindow.mFixedWidthMajor; + final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor : mWindow.mFixedWidthMajor; if (tvw != null && tvw.type != TypedValue.TYPE_NULL) { final int w; if (tvw.type == TypedValue.TYPE_DIMENSION) { @@ -623,7 +632,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind if (tv.type == TypedValue.TYPE_DIMENSION) { min = (int)tv.getDimension(metrics); } else if (tv.type == TypedValue.TYPE_FRACTION) { - min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels); + min = (int)tv.getFraction(mAvailableWidth, mAvailableWidth); } else { min = 0; } @@ -1217,7 +1226,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind int fop = fg.getOpacity(); int bop = bg.getOpacity(); if (false) - Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop); + Log.v(mLogTag, "Background opacity: " + bop + ", Frame opacity: " + fop); if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) { opacity = PixelFormat.OPAQUE; } else if (fop == PixelFormat.UNKNOWN) { @@ -1232,16 +1241,16 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // frame with padding... there is no way to tell if the // frame and background together will draw all pixels. if (false) - Log.v(TAG, "Padding: " + mFramePadding); + Log.v(mLogTag, "Padding: " + mFramePadding); opacity = PixelFormat.TRANSLUCENT; } } if (false) - Log.v(TAG, "Background: " + bg + ", Frame: " + fg); + Log.v(mLogTag, "Background: " + bg + ", Frame: " + fg); } if (false) - Log.v(TAG, "Selected default opacity: " + opacity); + Log.v(mLogTag, "Selected default opacity: " + opacity); mDefaultOpacity = opacity; if (mFeatureId < 0) { @@ -1600,6 +1609,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind enableCaption(StackId.hasWindowDecor(workspaceId)); } } + updateAvailableWidth(); initializeElevation(); } @@ -1744,7 +1754,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // We shouldn't really get here as the background fallback should be always available since // it is defaulted by the system. - Log.w(TAG, "Failed to find background drawable for PhoneWindow=" + mWindow); + Log.w(mLogTag, "Failed to find background drawable for PhoneWindow=" + mWindow); return null; } @@ -1761,7 +1771,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind try { workspaceId = callback.getWindowStackId(); } catch (RemoteException ex) { - Log.e(TAG, "Failed to get the workspace ID of a PhoneWindow."); + Log.e(mLogTag, "Failed to get the workspace ID of a PhoneWindow."); } } if (workspaceId == INVALID_STACK_ID) { @@ -1927,6 +1937,19 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } + void updateLogTag(WindowManager.LayoutParams params) { + final String[] split = params.getTitle().toString().split("\\."); + if (split.length > 0) { + mLogTag = TAG + "[" + split[split.length - 1] + "]"; + } + } + + private void updateAvailableWidth() { + Resources res = getResources(); + mAvailableWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + res.getConfiguration().screenWidthDp, res.getDisplayMetrics()); + } + private static class ColorViewState { View view = null; int targetVisibility = View.INVISIBLE; @@ -1988,12 +2011,12 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind isPrimary = mode == mPrimaryActionMode; isFloating = mode == mFloatingActionMode; if (!isPrimary && mode.getType() == ActionMode.TYPE_PRIMARY) { - Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; " + Log.e(mLogTag, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; " + mode + " was not the current primary action mode! Expected " + mPrimaryActionMode); } if (!isFloating && mode.getType() == ActionMode.TYPE_FLOATING) { - Log.e(TAG, "Destroying unexpected ActionMode instance of TYPE_FLOATING; " + Log.e(mLogTag, "Destroying unexpected ActionMode instance of TYPE_FLOATING; " + mode + " was not the current floating action mode! Expected " + mFloatingActionMode); } diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index fafe3d1a7506..f159a4d71f20 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -113,6 +113,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private final static String TAG = "PhoneWindow"; + private static final boolean DEBUG = false; + private final static int DEFAULT_BACKGROUND_FADE_DURATION_MS = 300; private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES | @@ -146,6 +148,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // This is the view in which the window contents are placed. It is either // mDecor itself, or a child of mDecor where the contents go. ViewGroup mContentParent; + // Whether the client has explicitly set the content view. If false and mContentParent is not + // null, then the content parent was set due to window preservation. + private boolean mContentParentExplicitlySet = false; Callback2 mTakeSurfaceCallback; @@ -315,7 +320,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public boolean requestFeature(int featureId) { - if (mContentParent != null) { + if (mContentParentExplicitlySet) { throw new AndroidRuntimeException("requestFeature() must be called before adding content"); } final int features = getFeatures(); @@ -399,6 +404,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (cb != null && !isDestroyed()) { cb.onContentChanged(); } + mContentParentExplicitlySet = true; } @Override @@ -429,6 +435,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (cb != null && !isDestroyed()) { cb.onContentChanged(); } + mContentParentExplicitlySet = true; } @Override @@ -2281,7 +2288,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } else { context = getContext(); } - return new DecorView(context, featureId, this); + return new DecorView(context, featureId, this, getAttributes()); } protected ViewGroup generateLayout(DecorView decor) { @@ -2360,6 +2367,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor); a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor); + if (DEBUG) Log.d(TAG, "Min width minor: " + mMinWidthMinor.coerceToString() + + ", major: " + mMinWidthMajor.coerceToString()); if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) { if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue(); a.getValue(R.styleable.Window_windowFixedWidthMajor, @@ -3776,4 +3785,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { int getDecorCaptionShade() { return mDecorCaptionShade; } + + @Override + public void setAttributes(WindowManager.LayoutParams params) { + super.setAttributes(params); + if (mDecor != null) { + mDecor.updateLogTag(params); + } + } } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 849d3145bb56..4dd71e76d5e7 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -46,7 +46,7 @@ oneway interface IStatusBar void cancelPreloadRecentApps(); void showScreenPinningRequest(); - void showKeyboardShortcutsMenu(); + void toggleKeyboardShortcutsMenu(); /** * Notifies the status bar that an app transition is pending to delay applying some flags with diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 0a4ad0661c64..0125d3791782 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -68,7 +68,7 @@ interface IStatusBarService void preloadRecentApps(); void cancelPreloadRecentApps(); - void showKeyboardShortcutsMenu(); + void toggleKeyboardShortcutsMenu(); /** * Notifies the status bar that an app transition is pending to delay applying some flags with diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java index 406b487f643d..dc668189c771 100644 --- a/core/java/com/android/internal/util/StateMachine.java +++ b/core/java/com/android/internal/util/StateMachine.java @@ -779,7 +779,7 @@ public class StateMachine { @Override public final void handleMessage(Message msg) { if (!mHasQuit) { - if (mSm != null) { + if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) { mSm.onPreHandleMessage(msg); } @@ -807,7 +807,7 @@ public class StateMachine { // We need to check if mSm == null here as we could be quitting. if (mDbg && mSm != null) mSm.log("handleMessage: X"); - if (mSm != null) { + if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) { mSm.onPostHandleMessage(msg); } } diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java index 92e9ea6f3c18..7ac0ac368407 100644 --- a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java @@ -16,6 +16,8 @@ package com.android.internal.view.menu; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.view.LayoutInflater; import android.view.View; @@ -58,7 +60,7 @@ public abstract class BaseMenuPresenter implements MenuPresenter { } @Override - public void initForMenu(Context context, MenuBuilder menu) { + public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) { mContext = context; mInflater = LayoutInflater.from(mContext); mMenu = menu; diff --git a/core/java/com/android/internal/view/menu/IconMenuPresenter.java b/core/java/com/android/internal/view/menu/IconMenuPresenter.java index 2439b5df3acc..5223a7b6d8a1 100644 --- a/core/java/com/android/internal/view/menu/IconMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/IconMenuPresenter.java @@ -17,6 +17,8 @@ package com.android.internal.view.menu; import com.android.internal.view.menu.MenuView.ItemView; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.os.Bundle; import android.os.Parcelable; @@ -49,7 +51,7 @@ public class IconMenuPresenter extends BaseMenuPresenter { } @Override - public void initForMenu(Context context, MenuBuilder menu) { + public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) { super.initForMenu(context, menu); mMaxItems = -1; } diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java index c476354c9281..2fff3ba54ac8 100644 --- a/core/java/com/android/internal/view/menu/ListMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java @@ -16,6 +16,8 @@ package com.android.internal.view.menu; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.os.Bundle; import android.os.Parcelable; @@ -76,7 +78,7 @@ public class ListMenuPresenter implements MenuPresenter, AdapterView.OnItemClick } @Override - public void initForMenu(Context context, MenuBuilder menu) { + public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) { if (mThemeRes != 0) { mContext = new ContextThemeWrapper(context, mThemeRes); mInflater = LayoutInflater.from(mContext); diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index 465d775508e6..31b2f9619ed6 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -17,6 +17,7 @@ package com.android.internal.view.menu; +import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -1027,23 +1028,24 @@ public class MenuBuilder implements Menu { mIsActionItemsStale = true; onItemsChanged(true); } - + + @NonNull public ArrayList<MenuItemImpl> getVisibleItems() { if (!mIsVisibleItemsStale) return mVisibleItems; - + // Refresh the visible items mVisibleItems.clear(); - + final int itemsSize = mItems.size(); MenuItemImpl item; for (int i = 0; i < itemsSize; i++) { item = mItems.get(i); if (item.isVisible()) mVisibleItems.add(item); } - + mIsVisibleItemsStale = false; mIsActionItemsStale = true; - + return mVisibleItems; } diff --git a/core/java/com/android/internal/view/menu/MenuPopup.java b/core/java/com/android/internal/view/menu/MenuPopup.java index 98f5d9061e14..b151f348e58c 100644 --- a/core/java/com/android/internal/view/menu/MenuPopup.java +++ b/core/java/com/android/internal/view/menu/MenuPopup.java @@ -16,6 +16,8 @@ package com.android.internal.view.menu; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.view.MenuItem; import android.view.View; @@ -73,7 +75,7 @@ public abstract class MenuPopup implements ShowableListMenu, MenuPresenter, public abstract void setOnDismissListener(PopupWindow.OnDismissListener listener); @Override - public void initForMenu(Context context, MenuBuilder menu) { + public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) { // Don't need to do anything; we added as a presenter in the constructor. } diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java index c847c15607e1..65bdc096bc07 100644 --- a/core/java/com/android/internal/view/menu/MenuPresenter.java +++ b/core/java/com/android/internal/view/menu/MenuPresenter.java @@ -16,6 +16,8 @@ package com.android.internal.view.menu; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.os.Parcelable; import android.view.ViewGroup; @@ -49,14 +51,16 @@ public interface MenuPresenter { } /** - * Initialize this presenter for the given context and menu. - * This method is called by MenuBuilder when a presenter is - * added. See {@link MenuBuilder#addMenuPresenter(MenuPresenter)} + * Initializes this presenter for the given context and menu. + * <p> + * This method is called by MenuBuilder when a presenter is added. See + * {@link MenuBuilder#addMenuPresenter(MenuPresenter)}. * - * @param context Context for this presenter; used for view creation and resource management - * @param menu Menu to host + * @param context the context for this presenter; used for view creation + * and resource management, must be non-{@code null} + * @param menu the menu to host, or {@code null} to clear the hosted menu */ - public void initForMenu(Context context, MenuBuilder menu); + public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu); /** * Retrieve a MenuView to display the menu specified in diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 825e336a7ea3..f90b59dbdbb2 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -17,6 +17,8 @@ package com.android.internal.widget; import android.animation.LayoutTransition; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActionBar; import android.content.Context; import android.content.res.Configuration; @@ -1593,7 +1595,7 @@ public class ActionBarView extends AbsActionBarView implements DecorToolbar { MenuItemImpl mCurrentExpandedItem; @Override - public void initForMenu(Context context, MenuBuilder menu) { + public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) { // Clear the expanded action view when menus change. if (mMenu != null && mCurrentExpandedItem != null) { mMenu.collapseItemActionView(mCurrentExpandedItem); diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index d8ec22a0f697..92f781268732 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -260,6 +260,15 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding return nullObjectReturn("SkAndroidCodec::NewFromStream returned null"); } + // Do not allow ninepatch decodes to 565. In the past, decodes to 565 + // would dither, and we do not want to pre-dither ninepatches, since we + // know that they will be stretched. We no longer dither 565 decodes, + // but we continue to prevent ninepatches from decoding to 565, in order + // to maintain the old behavior. + if (peeker.mPatch && kRGB_565_SkColorType == prefColorType) { + prefColorType = kN32_SkColorType; + } + // Determine the output size and return if the client only wants the size. SkISize size = codec->getSampledDimensions(sampleSize); if (options != NULL) { @@ -369,15 +378,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding case SkCodec::kIncompleteInput: break; default: - return nullObjectReturn("codec->getAndoridPixels() failed."); - } - - // Some images may initially report that they have alpha due to the format - // of the encoded data, but then never use any colors which have alpha - // less than 100%. Here we check if the image really had alpha, and - // mark it as opaque if it is actually opaque. - if (kOpaque_SkAlphaType != alphaType && !codec->reallyHasAlpha()) { - decodingBitmap.setAlphaType(kOpaque_SkAlphaType); + return nullObjectReturn("codec->getAndroidPixels() failed."); } int scaledWidth = size.width(); diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 7860b74cc804..3746972bee15 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -1049,6 +1049,13 @@ static void android_media_AudioTrack_disableDeviceCallback( pJniStorage->mDeviceCallback.clear(); } +// FIXME +#if 0 +static jint android_media_AudioTrack_get_FCC_8(JNIEnv *env, jobject thiz) { + return FCC_8; +} +#endif + // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- @@ -1106,6 +1113,7 @@ static const JNINativeMethod gMethods[] = { {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioTrack_getRoutedDeviceId}, {"native_enableDeviceCallback", "()V", (void *)android_media_AudioTrack_enableDeviceCallback}, {"native_disableDeviceCallback", "()V", (void *)android_media_AudioTrack_disableDeviceCallback}, + // FIXME {"native_get_FCC_8", "()I", (void *)android_media_AudioTrack_get_FCC_8}, }; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c154e91bb559..7d9fd9300daf 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -277,8 +277,10 @@ <protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" /> <protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" /> <protected-broadcast android:name="android.intent.action.APPLICATION_RESTRICTIONS_CHANGED" /> - <protected-broadcast android:name="android.intent.action.BUGREPORT_FINISHED" /> <protected-broadcast android:name="android.intent.action.BUGREPORT_STARTED" /> + <protected-broadcast android:name="android.intent.action.BUGREPORT_FINISHED" /> + <protected-broadcast android:name="android.intent.action.REMOTE_BUGREPORT_FINISHED" /> + <protected-broadcast android:name="android.intent.action.REMOTE_BUGREPORT_DISPATCH" /> <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" /> <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" /> @@ -383,6 +385,12 @@ <protected-broadcast android:name="com.android.server.Wifi.action.TOGGLE_PNO" /> <protected-broadcast android:name="intent.action.ACTION_RF_BAND_INFO" /> + <protected-broadcast android:name="android.app.action.INTERRUPTION_FILTER_CHANGED" /> + <protected-broadcast android:name="android.app.action.INTERRUPTION_FILTER_CHANGED_INTERNAL" /> + <protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_CHANGED" /> + <protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED" /> + <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" /> + <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> <!-- ====================================================================== --> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 37b2c1288d91..b2482cdcf82e 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -35,6 +35,13 @@ <dimen name="navigation_bar_height_landscape">48dp</dimen> <!-- Width of the navigation bar when it is placed vertically on the screen --> <dimen name="navigation_bar_width">48dp</dimen> + <!-- Height of the bottom navigation / system bar in car mode. --> + <dimen name="navigation_bar_height_car_mode">96dp</dimen> + <!-- Height of the bottom navigation bar in portrait; often the same as + @dimen/navigation_bar_height_car_mode --> + <dimen name="navigation_bar_height_landscape_car_mode">96dp</dimen> + <!-- Width of the navigation bar when it is placed vertically on the screen in car mode --> + <dimen name="navigation_bar_width_car_mode">96dp</dimen> <!-- Height of notification icons in the status bar --> <dimen name="status_bar_icon_size">24dip</dimen> <!-- Size of the giant number (unread count) in the notifications --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 845c8c935ab9..9453c52e91de 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1497,6 +1497,9 @@ <java-symbol type="dimen" name="navigation_bar_height" /> <java-symbol type="dimen" name="navigation_bar_height_landscape" /> <java-symbol type="dimen" name="navigation_bar_width" /> + <java-symbol type="dimen" name="navigation_bar_height_car_mode" /> + <java-symbol type="dimen" name="navigation_bar_height_landscape_car_mode" /> + <java-symbol type="dimen" name="navigation_bar_width_car_mode" /> <java-symbol type="dimen" name="status_bar_height" /> <java-symbol type="drawable" name="ic_jog_dial_sound_off" /> <java-symbol type="drawable" name="ic_jog_dial_sound_on" /> diff --git a/core/tests/coretests/src/android/util/PatternsTest.java b/core/tests/coretests/src/android/util/PatternsTest.java index 253eb2585377..d3837756e495 100644 --- a/core/tests/coretests/src/android/util/PatternsTest.java +++ b/core/tests/coretests/src/android/util/PatternsTest.java @@ -17,14 +17,16 @@ package android.util; import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.Suppress; -import android.util.Patterns; import java.util.regex.Matcher; +import java.util.regex.Pattern; import junit.framework.TestCase; public class PatternsTest extends TestCase { + //Tests for Patterns.TOP_LEVEL_DOMAIN + @SmallTest public void testTldPattern() throws Exception { boolean t; @@ -40,7 +42,7 @@ public class PatternsTest extends TestCase { t = Patterns.TOP_LEVEL_DOMAIN.matcher("xn--0zwm56d").matches(); assertTrue("Missed valid TLD", t); - // One of the new top level internationalized domain. + // One of the new top level unicode domain. t = Patterns.TOP_LEVEL_DOMAIN.matcher("\uD55C\uAD6D").matches(); assertTrue("Missed valid TLD", t); @@ -54,60 +56,372 @@ public class PatternsTest extends TestCase { assertFalse("Matched invalid TLD!", t); } + //Tests for Patterns.IANA_TOP_LEVEL_DOMAINS + @SmallTest - @Suppress // Failing. - public void testUrlPattern() throws Exception { - boolean t; + public void testIanaTopLevelDomains_matchesValidTld() throws Exception { + Pattern pattern = Pattern.compile(Patterns.IANA_TOP_LEVEL_DOMAINS); + assertTrue("Should match 'com'", pattern.matcher("com").matches()); + } - t = Patterns.WEB_URL.matcher("http://www.google.com").matches(); - assertTrue("Valid URL", t); + @SmallTest + public void testIanaTopLevelDomains_matchesValidNewTld() throws Exception { + Pattern pattern = Pattern.compile(Patterns.IANA_TOP_LEVEL_DOMAINS); + assertTrue("Should match 'me'", pattern.matcher("me").matches()); + } - // Google in one of the new top level domain. - t = Patterns.WEB_URL.matcher("http://www.google.me").matches(); - assertTrue("Valid URL", t); - t = Patterns.WEB_URL.matcher("google.me").matches(); - assertTrue("Valid URL", t); + @SmallTest + public void testIanaTopLevelDomains_matchesPunycodeTld() throws Exception { + Pattern pattern = Pattern.compile(Patterns.IANA_TOP_LEVEL_DOMAINS); + assertTrue("Should match Punycode TLD", pattern.matcher("xn--qxam").matches()); + } - // Test url in Chinese: http://xn--fsqu00a.xn--0zwm56d - t = Patterns.WEB_URL.matcher("http://xn--fsqu00a.xn--0zwm56d").matches(); - assertTrue("Valid URL", t); - t = Patterns.WEB_URL.matcher("xn--fsqu00a.xn--0zwm56d").matches(); - assertTrue("Valid URL", t); + @SmallTest + public void testIanaTopLevelDomains_matchesIriTLD() throws Exception { + Pattern pattern = Pattern.compile(Patterns.IANA_TOP_LEVEL_DOMAINS); + assertTrue("Should match IRI TLD", pattern.matcher("\uD55C\uAD6D").matches()); + } - // Url for testing top level Arabic country code domain in Punycode: - // http://xn--4gbrim.xn----rmckbbajlc6dj7bxne2c.xn--wgbh1c/ar/default.aspx - t = Patterns.WEB_URL.matcher("http://xn--4gbrim.xn----rmckbbajlc6dj7bxne2c.xn--wgbh1c/ar/default.aspx").matches(); - assertTrue("Valid URL", t); - t = Patterns.WEB_URL.matcher("xn--4gbrim.xn----rmckbbajlc6dj7bxne2c.xn--wgbh1c/ar/default.aspx").matches(); - assertTrue("Valid URL", t); + @SmallTest + public void testIanaTopLevelDomains_doesNotMatchWrongTld() throws Exception { + Pattern pattern = Pattern.compile(Patterns.IANA_TOP_LEVEL_DOMAINS); + assertFalse("Should not match 'mem'", pattern.matcher("mem").matches()); + } - // Internationalized URL. - t = Patterns.WEB_URL.matcher("http://\uD604\uAE08\uC601\uC218\uC99D.kr").matches(); - assertTrue("Valid URL", t); - t = Patterns.WEB_URL.matcher("\uD604\uAE08\uC601\uC218\uC99D.kr").matches(); - assertTrue("Valid URL", t); - // URL with international TLD. - t = Patterns.WEB_URL.matcher("\uB3C4\uBA54\uC778.\uD55C\uAD6D").matches(); - assertTrue("Valid URL", t); + @SmallTest + public void testIanaTopLevelDomains_doesNotMatchWrongPunycodeTld() throws Exception { + Pattern pattern = Pattern.compile(Patterns.IANA_TOP_LEVEL_DOMAINS); + assertFalse("Should not match invalid Punycode TLD", pattern.matcher("xn").matches()); + } - t = Patterns.WEB_URL.matcher("http://brainstormtech.blogs.fortune.cnn.com/2010/03/11/" + - "top-five-moments-from-eric-schmidt\u2019s-talk-in-abu-dhabi/").matches(); - assertTrue("Valid URL", t); + //Tests for Patterns.WEB_URL - t = Patterns.WEB_URL.matcher("ftp://www.example.com").matches(); - assertFalse("Matched invalid protocol", t); + @SmallTest + public void testWebUrl_matchesValidUrlWithSchemeAndHostname() throws Exception { + String url = "http://www.android.com"; + assertTrue("Should match URL with scheme and hostname", + Patterns.WEB_URL.matcher(url).matches()); + } - t = Patterns.WEB_URL.matcher("http://www.example.com:8080").matches(); - assertTrue("Didn't match valid URL with port", t); + @SmallTest + public void testWebUrl_matchesValidUrlWithSchemeHostnameAndNewTld() throws Exception { + String url = "http://www.android.me"; + assertTrue("Should match URL with scheme, hostname and new TLD", + Patterns.WEB_URL.matcher(url).matches()); + } - t = Patterns.WEB_URL.matcher("http://www.example.com:8080/?foo=bar").matches(); - assertTrue("Didn't match valid URL with port and query args", t); + @SmallTest + public void testWebUrl_matchesValidUrlWithHostnameAndNewTld() throws Exception { + String url = "android.me"; + assertTrue("Should match URL with hostname and new TLD", + Patterns.WEB_URL.matcher(url).matches()); + } - t = Patterns.WEB_URL.matcher("http://www.example.com:8080/~user/?foo=bar").matches(); - assertTrue("Didn't match valid URL with ~", t); + @SmallTest + public void testWebUrl_matchesChinesePunycodeUrlWithProtocol() throws Exception { + String url = "http://xn--fsqu00a.xn--0zwm56d"; + assertTrue("Should match Chinese Punycode URL with protocol", + Patterns.WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testWebUrl_matchesChinesePunycodeUrlWithoutProtocol() throws Exception { + String url = "xn--fsqu00a.xn--0zwm56d"; + assertTrue("Should match Chinese Punycode URL without protocol", + Patterns.WEB_URL.matcher(url).matches()); + } + + + @SmallTest + public void testWebUrl_matchesArabicPunycodeUrlWithProtocol() throws Exception { + String url = "http://xn--4gbrim.xn----rmckbbajlc6dj7bxne2c.xn--wgbh1c/ar/default.aspx"; + assertTrue("Should match arabic Punycode URL with protocol", + Patterns.WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testWebUrl_matchesArabicPunycodeUrlWithoutProtocol() throws Exception { + String url = "xn--4gbrim.xn----rmckbbajlc6dj7bxne2c.xn--wgbh1c/ar/default.aspx"; + assertTrue("Should match Arabic Punycode URL without protocol", + Patterns.WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testWebUrl_matchesUrlWithUnicodeDomainNameWithProtocol() throws Exception { + String url = "http://\uD604\uAE08\uC601\uC218\uC99D.kr"; + assertTrue("Should match URL with Unicode domain name", + Patterns.WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testWebUrl_matchesUrlWithUnicodeDomainNameWithoutProtocol() throws Exception { + String url = "\uD604\uAE08\uC601\uC218\uC99D.kr"; + assertTrue("Should match URL without protocol and with Unicode domain name", + Patterns.WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testWebUrl_matchesUrlWithUnicodeTld() throws Exception { + String url = "\uB3C4\uBA54\uC778.\uD55C\uAD6D"; + assertTrue("Should match URL with Unicode TLD", + Patterns.WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testWebUrl_matchesUrlWithUnicodePath() throws Exception { + String url = "http://brainstormtech.blogs.fortune.cnn.com/2010/03/11/" + + "top-five-moments-from-eric-schmidt\u2019s-talk-in-abu-dhabi/"; + assertTrue("Should match URL with Unicode path", + Patterns.WEB_URL.matcher(url).matches()); } @SmallTest + public void testWebUrl_doesNotMatchValidUrlWithInvalidProtocol() throws Exception { + String url = "ftp://www.example.com"; + assertFalse("Should not match URL with invalid protocol", + Patterns.WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testWebUrl_matchesValidUrlWithPort() throws Exception { + String url = "http://www.example.com:8080"; + assertTrue("Should match URL with port", Patterns.WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testWebUrl_matchesUrlWithPortAndQuery() throws Exception { + String url = "http://www.example.com:8080/?foo=bar"; + assertTrue("Should match URL with port and query", + Patterns.WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testWebUrl_matchesUrlWithTilde() throws Exception { + String url = "http://www.example.com:8080/~user/?foo=bar"; + assertTrue("Should match URL with tilde", Patterns.WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testWebUrl_matchesProtocolCaseInsensitive() throws Exception { + String url = "hTtP://android.com"; + assertTrue("Protocol matching should be case insensitive", + Patterns.WEB_URL.matcher(url).matches()); + } + + //Tests for Patterns.AUTOLINK_WEB_URL + + @SmallTest + public void testAutoLinkWebUrl_matchesValidUrlWithSchemeAndHostname() throws Exception { + String url = "http://www.android.com"; + assertTrue("Should match URL with scheme and hostname", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesValidUrlWithSchemeHostnameAndNewTld() throws Exception { + String url = "http://www.android.me"; + assertTrue("Should match URL with scheme, hostname and new TLD", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesValidUrlWithHostnameAndNewTld() throws Exception { + String url = "android.me"; + assertTrue("Should match URL with hostname and new TLD", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + + url = "android.camera"; + assertTrue("Should match URL with hostname and new TLD", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesChinesePunycodeUrlWithProtocol() throws Exception { + String url = "http://xn--fsqu00a.xn--0zwm56d"; + assertTrue("Should match Chinese Punycode URL with protocol", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesChinesePunycodeUrlWithoutProtocol() throws Exception { + String url = "xn--fsqu00a.xn--0zwm56d"; + assertTrue("Should match Chinese Punycode URL without protocol", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesArabicPunycodeUrlWithProtocol() throws Exception { + String url = "http://xn--4gbrim.xn--rmckbbajlc6dj7bxne2c.xn--wgbh1c/ar/default.aspx"; + assertTrue("Should match Arabic Punycode URL with protocol", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesArabicPunycodeUrlWithoutProtocol() throws Exception { + String url = "xn--4gbrim.xn--rmckbbajlc6dj7bxne2c.xn--wgbh1c/ar/default.aspx"; + assertTrue("Should match Arabic Punycode URL without protocol", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_doesNotMatchPunycodeTldThatStartsWithDash() throws Exception { + String url = "http://xn--fsqu00a.-xn--0zwm56d"; + assertFalse("Should not match Punycode TLD that starts with dash", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_doesNotMatchPunycodeTldThatEndsWithDash() throws Exception { + String url = "http://xn--fsqu00a.xn--0zwm56d-"; + assertFalse("Should not match Punycode TLD that ends with dash", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesUrlWithUnicodeDomainName() throws Exception { + String url = "http://\uD604\uAE08\uC601\uC218\uC99D.kr"; + assertTrue("Should match URL with Unicode domain name", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + + url = "\uD604\uAE08\uC601\uC218\uC99D.kr"; + assertTrue("hould match URL without protocol and with Unicode domain name", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesUrlWithUnicodeTld() throws Exception { + String url = "\uB3C4\uBA54\uC778.\uD55C\uAD6D"; + assertTrue("Should match URL with Unicode TLD", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesUrlWithUnicodePath() throws Exception { + String url = "http://brainstormtech.blogs.fortune.cnn.com/2010/03/11/" + + "top-five-moments-from-eric-schmidt\u2019s-talk-in-abu-dhabi/"; + assertTrue("Should match URL with Unicode path", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_doesNotMatchValidUrlWithInvalidProtocol() throws Exception { + String url = "ftp://www.example.com"; + assertFalse("Should not match URL with invalid protocol", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesValidUrlWithPort() throws Exception { + String url = "http://www.example.com:8080"; + assertTrue("Should match URL with port", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesUrlWithPortAndQuery() throws Exception { + String url = "http://www.example.com:8080/?foo=bar"; + assertTrue("Should match URL with port and query", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesUrlWithTilde() throws Exception { + String url = "http://www.example.com:8080/~user/?foo=bar"; + assertTrue("Should match URL with tilde", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesProtocolCaseInsensitive() throws Exception { + String url = "hTtP://android.com"; + assertTrue("Protocol matching should be case insensitive", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesUrlStartingWithHttpAndDoesNotHaveTld() throws Exception { + String url = "http://android/#notld///a/n/d/r/o/i/d&p1=1&p2=2"; + assertTrue("Should match URL without a TLD and starting with http ", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_doesNotMatchUrlsWithoutProtocolAndWithUnknownTld() + throws Exception { + String url = "thank.you"; + assertFalse("Should not match URL that does not start with a protocol and " + + "does not contain a known TLD", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_doesNotMatchUrlWithInvalidRequestParameter() throws Exception { + String url = "http://android.com?p=value"; + assertFalse("Should not match URL with invalid request parameter", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_doesNotPartiallyMatchUnknownProtocol() throws Exception { + String url = "ftp://foo.bar/baz"; + assertFalse("Should not partially match URL with unknown protocol", + Patterns.AUTOLINK_WEB_URL.matcher(url).find()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesValidUrlWithEmoji() throws Exception { + String url = "Thank\u263A.com"; + assertTrue("Should match URL with emoji", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_doesNotMatchUrlsWithEmojiWithoutProtocolAndWithoutKnownTld() + throws Exception { + String url = "Thank\u263A.you"; + assertFalse("Should not match URLs containing emoji and with unknown TLD", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_doesNotMatchEmailAddress() + throws Exception { + String url = "android@android.com"; + assertFalse("Should not match email address", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesDomainNameWithSurrogatePairs() throws Exception { + String url = "android\uD83C\uDF38.com"; + assertTrue("Should match domain name with Unicode surrogate pairs", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesTldWithSurrogatePairs() throws Exception { + String url = "http://android.\uD83C\uDF38com"; + assertTrue("Should match TLD with Unicode surrogate pairs", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_matchesPathWithSurrogatePairs() throws Exception { + String url = "http://android.com/path-with-\uD83C\uDF38?v=\uD83C\uDF38"; + assertTrue("Should match path and query with Unicode surrogate pairs", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + @SmallTest + public void testAutoLinkWebUrl_doesNotMatchUrlWithExcludedSurrogate() throws Exception { + String url = "http://android\uD83F\uDFFE.com"; + assertFalse("Should not match URL with excluded Unicode surrogate pair", + Patterns.AUTOLINK_WEB_URL.matcher(url).matches()); + } + + //Tests for Patterns.IP_ADDRESS + + @SmallTest public void testIpPattern() throws Exception { boolean t; @@ -118,34 +432,85 @@ public class PatternsTest extends TestCase { assertFalse("Invalid IP", t); } + //Tests for Patterns.DOMAIN_NAME + @SmallTest - @Suppress // Failing. - public void testDomainPattern() throws Exception { - boolean t; + public void testDomain_matchesPunycodeTld() throws Exception { + String domain = "xn--fsqu00a.xn--0zwm56d"; + assertTrue("Should match domain name in Punycode", + Patterns.DOMAIN_NAME.matcher(domain).matches()); + } + + @SmallTest + public void testDomain_doesNotMatchPunycodeThatStartsWithDash() throws Exception { + String domain = "xn--fsqu00a.-xn--0zwm56d"; + assertFalse("Should not match Punycode TLD that starts with a dash", + Patterns.DOMAIN_NAME.matcher(domain).matches()); + } - t = Patterns.DOMAIN_NAME.matcher("mail.example.com").matches(); - assertTrue("Valid domain", t); + @SmallTest + public void testDomain_doesNotMatchPunycodeThatEndsWithDash() throws Exception { + String domain = "xn--fsqu00a.xn--0zwm56d-"; + assertFalse("Should not match Punycode TLD that ends with a dash", + Patterns.DOMAIN_NAME.matcher(domain).matches()); + } - t = Patterns.DOMAIN_NAME.matcher("google.me").matches(); - assertTrue("Valid domain", t); + @SmallTest + public void testDomain_doesNotMatchPunycodeLongerThanAllowed() throws Exception { + String tld = "xn--"; + for(int i=0; i<=6; i++) { + tld += "0123456789"; + } + String domain = "xn--fsqu00a." + tld; + assertFalse("Should not match Punycode TLD that is longer than 63 chars", + Patterns.DOMAIN_NAME.matcher(domain).matches()); + } - // Internationalized domains. - t = Patterns.DOMAIN_NAME.matcher("\uD604\uAE08\uC601\uC218\uC99D.kr").matches(); - assertTrue("Valid domain", t); + @SmallTest + public void testDomain_matchesObsoleteTld() throws Exception { + String domain = "test.yu"; + assertTrue("Should match domain names with obsolete TLD", + Patterns.DOMAIN_NAME.matcher(domain).matches()); + } - t = Patterns.DOMAIN_NAME.matcher("__+&42.xer").matches(); - assertFalse("Invalid domain", t); + @SmallTest + public void testDomain_matchesWithSubDomain() throws Exception { + String domain = "mail.example.com"; + assertTrue("Should match domain names with subdomains", + Patterns.DOMAIN_NAME.matcher(domain).matches()); + } - // Obsolete domain .yu - t = Patterns.DOMAIN_NAME.matcher("test.yu").matches(); - assertFalse("Obsolete country code top level domain", t); + @SmallTest + public void testDomain_matchesWithoutSubDomain() throws Exception { + String domain = "android.me"; + assertTrue("Should match domain names without subdomains", + Patterns.DOMAIN_NAME.matcher(domain).matches()); + } + + @SmallTest + public void testDomain_matchesUnicodeDomainNames() throws Exception { + String domain = "\uD604\uAE08\uC601\uC218\uC99D.kr"; + assertTrue("Should match unicodedomain names", + Patterns.DOMAIN_NAME.matcher(domain).matches()); + } - // Testing top level Arabic country code domain in Punycode: - t = Patterns.DOMAIN_NAME.matcher("xn--4gbrim.xn----rmckbbajlc6dj7bxne2c.xn--wgbh1c").matches(); - assertTrue("Valid domain", t); + @SmallTest + public void testDomain_doesNotMatchInvalidDomain() throws Exception { + String domain = "__+&42.xer"; + assertFalse("Should not match invalid domain name", + Patterns.DOMAIN_NAME.matcher(domain).matches()); } @SmallTest + public void testDomain_matchesPunycodeArabicDomainName() throws Exception { + String domain = "xn--4gbrim.xn----rmckbbajlc6dj7bxne2c.xn--wgbh1c"; + assertTrue("Should match Punycode Arabic domain name", + Patterns.DOMAIN_NAME.matcher(domain).matches()); + } + + //Tests for Patterns.PHONE + + @SmallTest public void testPhonePattern() throws Exception { boolean t; diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index bb4f7d9b7ad9..6dd1072532f8 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -97,9 +97,10 @@ public class AudioTrack /** Maximum value for sample rate */ private static final int SAMPLE_RATE_HZ_MAX = 192000; - // FCC_8 - /** Maximum value for AudioTrack channel count */ - private static final int CHANNEL_COUNT_MAX = 8; + /** Maximum value for AudioTrack channel count + * @hide public for MediaCode only, do not un-hide or change to a numeric literal + */ + public static final int CHANNEL_COUNT_MAX = 8; // FIXME was native_get_FCC_8(), unregistered! /** indicates AudioTrack state is stopped */ public static final int PLAYSTATE_STOPPED = 1; // matches SL_PLAYSTATE_STOPPED @@ -2583,6 +2584,7 @@ public class AudioTrack private native final int native_getRoutedDeviceId(); private native final void native_enableDeviceCallback(); private native final void native_disableDeviceCallback(); + // FIXME static private native int native_get_FCC_8(); //--------------------------------------------------------- // Utility methods diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index e6bc8f1048b6..9bcb5e35938b 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -907,7 +907,7 @@ public final class MediaCodecInfo { } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) { sampleRateRange = Range.create(1, 96000); bitRates = Range.create(1, 10000000); - maxChannels = 8; + maxChannels = AudioTrack.CHANNEL_COUNT_MAX; } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { sampleRateRange = Range.create(1, 655350); // lossless codec, so bitrate is ignored diff --git a/media/java/android/mtp/MtpDevice.java b/media/java/android/mtp/MtpDevice.java index 95cb5206b9b5..d24c5e825fa7 100644 --- a/media/java/android/mtp/MtpDevice.java +++ b/media/java/android/mtp/MtpDevice.java @@ -19,9 +19,10 @@ package android.mtp; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.os.CancellationSignal; -import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; +import java.io.IOException; + /** * This class represents an MTP or PTP device connected on the USB host bus. An application can * instantiate an object of this type, by referencing an attached {@link @@ -158,6 +159,22 @@ public final class MtpDevice { } /** + * Obtains object bytes in the specified range and writes it to an array. + * This call may block for an arbitrary amount of time depending on the size + * of the data and speed of the devices. + * + * @param objectHandle handle of the object to read + * @param offset Start index of reading range. + * @param size Size of reading range. + * @param buffer Array to write data. + * @return Size of bytes that are actually read. + */ + public int getPartialObject(int objectHandle, int offset, int size, byte[] buffer) + throws IOException { + return native_get_partial_object(objectHandle, offset, size, buffer); + } + + /** * Returns the thumbnail data for an object as a byte array. * The size and format of the thumbnail data can be determined via * {@link MtpObjectInfo#getThumbCompressedSize} and @@ -323,6 +340,8 @@ public final class MtpDevice { private native int[] native_get_object_handles(int storageId, int format, int objectHandle); private native MtpObjectInfo native_get_object_info(int objectHandle); private native byte[] native_get_object(int objectHandle, int objectSize); + private native int native_get_partial_object( + int objectHandle, int offset, int objectSize, byte[] buffer) throws IOException; private native byte[] native_get_thumbnail(int objectHandle); private native boolean native_delete_object(int objectHandle); private native long native_get_parent(int objectHandle); diff --git a/media/jni/android_mtp_MtpDevice.cpp b/media/jni/android_mtp_MtpDevice.cpp index 3f4d18386afa..14c15e5b8cf9 100644 --- a/media/jni/android_mtp_MtpDevice.cpp +++ b/media/jni/android_mtp_MtpDevice.cpp @@ -29,6 +29,7 @@ #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include "android_runtime/Log.h" +#include "nativehelper/ScopedLocalRef.h" #include "private/android_filesystem_config.h" #include "MtpTypes.h" @@ -41,6 +42,8 @@ using namespace android; // ---------------------------------------------------------------------------- +namespace { + static jfieldID field_context; jclass clazz_deviceInfo; @@ -93,6 +96,29 @@ static jfieldID field_objectInfo_keywords; // MtpEvent fields static jfieldID field_event_eventCode; +class JavaArrayWriter { +public: + JavaArrayWriter(JNIEnv* env, jbyteArray array) : + mEnv(env), mArray(array), mSize(mEnv->GetArrayLength(mArray)) {} + bool write(void* data, uint32_t offset, uint32_t length) { + if (static_cast<uint32_t>(mSize) < offset + length) { + return false; + } + mEnv->SetByteArrayRegion(mArray, offset, length, static_cast<jbyte*>(data)); + return true; + } + static bool writeTo(void* data, uint32_t offset, uint32_t length, void* clientData) { + return static_cast<JavaArrayWriter*>(clientData)->write(data, offset, length); + } + +private: + JNIEnv* mEnv; + jbyteArray mArray; + jsize mSize; +}; + +} + MtpDevice* get_device_from_object(JNIEnv* env, jobject javaDevice) { return (MtpDevice*)env->GetLongField(javaDevice, field_context); @@ -307,38 +333,59 @@ android_mtp_MtpDevice_get_object_info(JNIEnv *env, jobject thiz, jint objectID) return info; } -struct get_object_callback_data { - JNIEnv *env; - jbyteArray array; -}; - -static bool get_object_callback(void* data, int offset, int length, void* clientData) -{ - get_object_callback_data* cbData = (get_object_callback_data *)clientData; - cbData->env->SetByteArrayRegion(cbData->array, offset, length, (jbyte *)data); - return true; -} - static jbyteArray android_mtp_MtpDevice_get_object(JNIEnv *env, jobject thiz, jint objectID, jint objectSize) { MtpDevice* device = get_device_from_object(env, thiz); - if (!device) - return NULL; + if (!device) { + return nullptr; + } - jbyteArray array = env->NewByteArray(objectSize); - if (!array) { + ScopedLocalRef<jbyteArray> array(env, env->NewByteArray(objectSize)); + if (!array.get()) { jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - return NULL; + return nullptr; } - get_object_callback_data data; - data.env = env; - data.array = array; + JavaArrayWriter writer(env, array.get()); + + if (device->readObject(objectID, JavaArrayWriter::writeTo, objectSize, &writer)) { + return array.release(); + } + return nullptr; +} - if (device->readObject(objectID, get_object_callback, objectSize, &data)) - return array; - return NULL; +static jint +android_mtp_MtpDevice_get_partial_object(JNIEnv *env, + jobject thiz, + jint objectID, + jint offset, + jint size, + jbyteArray array) +{ + if (!array) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Array must not be null."); + return -1; + } + + MtpDevice* const device = get_device_from_object(env, thiz); + if (!device) { + jniThrowException(env, "java/io/IOException", "Failed to obtain MtpDevice."); + return -1; + } + + JavaArrayWriter writer(env, array); + uint32_t written_size; + bool success = device->readPartialObject( + objectID, offset, size, &written_size, JavaArrayWriter::writeTo, &writer); + if (!success) { + jniThrowException(env, "java/io/IOException", "Failed to read data."); + return -1; + } + // Note: assumption here is that a negative value will be treated as unsigned on the Java + // level. + // TODO: Make sure that actually holds. + return static_cast<jint>(written_size); } static jbyteArray @@ -547,6 +594,7 @@ static const JNINativeMethod gMethods[] = { {"native_get_object_info", "(I)Landroid/mtp/MtpObjectInfo;", (void *)android_mtp_MtpDevice_get_object_info}, {"native_get_object", "(II)[B",(void *)android_mtp_MtpDevice_get_object}, + {"native_get_partial_object", "(III[B)I", (void *)android_mtp_MtpDevice_get_partial_object}, {"native_get_thumbnail", "(I)[B",(void *)android_mtp_MtpDevice_get_thumbnail}, {"native_delete_object", "(I)Z", (void *)android_mtp_MtpDevice_delete_object}, {"native_get_parent", "(I)J", (void *)android_mtp_MtpDevice_get_parent}, diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp index 70d651f2c24a..b63df6fe7ff1 100644 --- a/media/jni/soundpool/SoundPool.cpp +++ b/media/jni/soundpool/SoundPool.cpp @@ -655,7 +655,7 @@ status_t Sample::doLoad() goto error; } - if ((numChannels < 1) || (numChannels > 8)) { + if ((numChannels < 1) || (numChannels > FCC_8)) { ALOGE("Sample channel count (%d) out of range", numChannels); status = BAD_VALUE; goto error; diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java index 9c0a04ca751c..a2416674f5de 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java @@ -39,7 +39,6 @@ import android.provider.DocumentsContract.Root; import android.support.annotation.LayoutRes; import android.support.annotation.Nullable; import android.util.Log; -import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -60,6 +59,7 @@ import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentStack; import com.android.documentsui.model.DurableUtils; import com.android.documentsui.model.RootInfo; +import com.android.internal.util.Preconditions; import libcore.io.IoUtils; @@ -111,6 +111,13 @@ public abstract class BaseActivity extends Activity { setContentView(mLayoutId); mRoots = DocumentsApplication.getRootsCache(this); + mRoots.setOnCacheUpdateListener( + new RootsCache.OnCacheUpdateListener() { + @Override + public void onCacheUpdate() { + new HandleRootsChangedTask().execute(getCurrentRoot()); + } + }); mDirectoryContainer = (DirectoryContainerView) findViewById(R.id.container_directory); mSearchManager = new SearchManager(); @@ -203,7 +210,25 @@ public abstract class BaseActivity extends Activity { if (mRoots.isRecentsRoot(root)) { onCurrentDirectoryChanged(ANIM_SIDE); } else { - new PickRootTask(root).executeOnExecutor(getExecutorForCurrentDirectory()); + new PickRootTask(root, true).executeOnExecutor(getExecutorForCurrentDirectory()); + } + } + + void setRoot(RootInfo root) { + // Clear entire backstack and start in new root + mState.stack.root = root; + mState.stack.clear(); + mState.stackTouched = false; + + mSearchManager.update(root); + + // Recents is always in memory, so we just load it directly. + // Otherwise we delegate loading data from disk to a task + // to ensure a responsive ui. + if (mRoots.isRecentsRoot(root)) { + onCurrentDirectoryChanged(ANIM_SIDE); + } else { + new PickRootTask(root, false).executeOnExecutor(getExecutorForCurrentDirectory()); } } @@ -483,9 +508,11 @@ public abstract class BaseActivity extends Activity { final class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> { private RootInfo mRoot; + private boolean mTouched; - public PickRootTask(RootInfo root) { + public PickRootTask(RootInfo root, boolean touched) { mRoot = root; + mTouched = touched; } @Override @@ -504,7 +531,7 @@ public abstract class BaseActivity extends Activity { protected void onPostExecute(DocumentInfo result) { if (result != null) { mState.stack.push(result); - mState.stackTouched = true; + mState.stackTouched = mTouched; onCurrentDirectoryChanged(ANIM_SIDE); } } @@ -591,6 +618,34 @@ public abstract class BaseActivity extends Activity { } } + final class HandleRootsChangedTask extends AsyncTask<RootInfo, Void, RootInfo> { + @Override + protected RootInfo doInBackground(RootInfo... roots) { + Preconditions.checkArgument(roots.length == 1); + final RootInfo currentRoot = roots[0]; + final Collection<RootInfo> cachedRoots = mRoots.getRootsBlocking(); + RootInfo homeRoot = null; + for (final RootInfo root : cachedRoots) { + if (root.isHome()) { + homeRoot = root; + } + if (root.getUri().equals(currentRoot.getUri())) { + // We don't need to change the current root as the current root was not removed. + return null; + } + } + Preconditions.checkNotNull(homeRoot); + return homeRoot; + } + + @Override + protected void onPostExecute(RootInfo result) { + if (result != null) { + setRoot(result); + } + } + } + final class ItemSelectedListener implements OnItemSelectedListener { boolean mIgnoreNextNavigation; diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java index 6a5911bb1bfd..34614b414645 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java +++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java @@ -502,11 +502,6 @@ 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. 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) { @@ -516,8 +511,7 @@ public class CopyService extends IntentService { 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. + // The virtual file is not available as any alternative streamable format. // TODO: Log failures. b/26192412 mFailedFiles.add(srcInfo); return false; @@ -640,9 +634,8 @@ public class CopyService extends IntentService { boolean success = true; try { - // 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()) { + // If the file is virtual, then try to copy it as an alternative format. + if (srcInfo.isVirtualDocument()) { final AssetFileDescriptor srcFileAsAsset = mSrcClient.openTypedAssetFileDescriptor( srcInfo.derivedUri, mimeType, null, canceller); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index 72ee6cbab5fd..21e756623bdd 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -63,6 +63,7 @@ public class RootsCache { private final Context mContext; private final ContentObserver mObserver; + private OnCacheUpdateListener mCacheUpdateListener; private final RootInfo mRecentsRoot = new RootInfo(); @@ -94,6 +95,10 @@ public class RootsCache { } } + static interface OnCacheUpdateListener { + void onCacheUpdate(); + } + /** * Gather roots from all known storage providers. */ @@ -209,6 +214,13 @@ public class RootsCache { return null; } + @Override + protected void onPostExecute(Void result) { + if (mCacheUpdateListener != null) { + mCacheUpdateListener.onCacheUpdate(); + } + } + private void handleDocumentsProvider(ProviderInfo info) { // Ignore stopped packages for now; we might query them // later during UI interaction. @@ -348,6 +360,10 @@ public class RootsCache { } } + public void setOnCacheUpdateListener(OnCacheUpdateListener cacheUpdateListener) { + mCacheUpdateListener = cacheUpdateListener; + } + @VisibleForTesting static List<RootInfo> getMatchingRoots(Collection<RootInfo> roots, State state) { final List<RootInfo> matching = new ArrayList<>(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java index 215c6e696bb7..83df18cdca13 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java @@ -255,10 +255,6 @@ public class DocumentInfo implements Durable, Parcelable { return (flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0; } - public boolean isTypedDocument() { - return (flags & Document.FLAG_SUPPORTS_TYPED_DOCUMENT) != 0; - } - public int hashCode() { return derivedUri.hashCode() + mimeType.hashCode(); } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java index 24a811393b65..4ce018529356 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java @@ -311,10 +311,8 @@ public class CopyServiceTest extends ServiceTestCase<CopyService> { 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()); + null /* streamTypes */, "I love Tokyo!".getBytes()); Intent intent = createCopyIntent(Lists.newArrayList(testFile)); startService(intent); diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java index 2c311a7cc192..50f462807bfe 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java @@ -316,12 +316,9 @@ public class StubProvider extends DocumentsProvider { String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal) throws FileNotFoundException { final StubDocument document = mStorage.get(documentId); - if (document == null || !document.file.isFile()) { + if (document == null || !document.file.isFile() || document.streamTypes == null) { 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. @@ -349,13 +346,13 @@ public class StubProvider extends DocumentsProvider { 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(); } + if (document.streamTypes == null) { + return null; + } return document.streamTypes.toArray(new String[document.streamTypes.size()]); } @@ -628,9 +625,6 @@ public class StubProvider extends DocumentsProvider { 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); } diff --git a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp index f592a1fb1ae1..f9fb85c446ad 100644 --- a/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp +++ b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp @@ -30,15 +30,13 @@ #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" +#include "nativehelper/ScopedPrimitiveArray.h" namespace { -constexpr int min(int a, int b) { - return a < b ? a : b; -} - // Maximum number of bytes to write in one request. constexpr size_t MAX_WRITE = 256 * 1024; +constexpr size_t NUM_MAX_HANDLES = 1024; // Largest possible request. // The request size is bounded by the maximum size of a FUSE_WRITE request @@ -47,6 +45,8 @@ constexpr size_t MAX_REQUEST_SIZE = sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + MAX_WRITE; static jclass app_fuse_class; +static jmethodID app_fuse_get_file_size; +static jmethodID app_fuse_get_object_bytes; struct FuseRequest { char buffer[MAX_REQUEST_SIZE]; @@ -79,15 +79,25 @@ public: * The class is used to access AppFuse class in Java from fuse handlers. */ class AppFuse { + JNIEnv* env_; + jobject self_; + + // Map between file handle and inode. + std::map<uint32_t, uint64_t> handles_; + uint32_t handle_counter_; + public: - AppFuse(JNIEnv* /*env*/, jobject /*self*/) { - } + AppFuse(JNIEnv* env, jobject self) : + env_(env), self_(self), handle_counter_(0) {} bool handle_fuse_request(int fd, const FuseRequest& req) { ALOGV("Request op=%d", req.header().opcode); switch (req.header().opcode) { // TODO: Handle more operations that are enough to provide seekable // FD. + case FUSE_LOOKUP: + invoke_handler(fd, req, &AppFuse::handle_fuse_lookup); + return true; case FUSE_INIT: invoke_handler(fd, req, &AppFuse::handle_fuse_init); return true; @@ -96,6 +106,18 @@ public: return true; case FUSE_FORGET: return false; + case FUSE_OPEN: + invoke_handler(fd, req, &AppFuse::handle_fuse_open); + return true; + case FUSE_READ: + invoke_handler(fd, req, &AppFuse::handle_fuse_read, 8192); + return true; + case FUSE_RELEASE: + invoke_handler(fd, req, &AppFuse::handle_fuse_release, 0); + return true; + case FUSE_FLUSH: + invoke_handler(fd, req, &AppFuse::handle_fuse_flush, 0); + return true; default: { ALOGV("NOTIMPL op=%d uniq=%" PRIx64 " nid=%" PRIx64 "\n", req.header().opcode, @@ -108,10 +130,37 @@ public: } private: + int handle_fuse_lookup(const fuse_in_header& header, + const char* name, + fuse_entry_out* out, + uint32_t* /*unused*/) { + if (header.nodeid != 1) { + return -ENOENT; + } + + const int n = atoi(name); + if (n == 0) { + return -ENOENT; + } + + int64_t size = get_file_size(n); + if (size < 0) { + return -ENOENT; + } + + out->nodeid = n; + out->attr_valid = 10; + out->entry_valid = 10; + out->attr.ino = n; + out->attr.mode = S_IFREG | 0777; + out->attr.size = size; + return 0; + } + int handle_fuse_init(const fuse_in_header&, const fuse_init_in* in, fuse_init_out* out, - size_t* reply_size) { + uint32_t* reply_size) { // Kernel 2.6.16 is the first stable kernel with struct fuse_init_out // defined (fuse version 7.6). The structure is the same from 7.6 through // 7.22. Beginning with 7.23, the structure increased in size and added @@ -124,7 +173,7 @@ private: } // We limit ourselves to 15 because we don't handle BATCH_FORGET yet - out->minor = min(in->minor, 15); + out->minor = std::min(in->minor, 15u); #if defined(FUSE_COMPAT_22_INIT_OUT_SIZE) // FUSE_KERNEL_VERSION >= 23. @@ -152,7 +201,7 @@ private: int handle_fuse_getattr(const fuse_in_header& header, const fuse_getattr_in* /* in */, fuse_attr_out* out, - size_t* /*unused*/) { + uint32_t* /*unused*/) { if (header.nodeid != 1) { return -ENOENT; } @@ -163,14 +212,67 @@ private: return 0; } + int handle_fuse_open(const fuse_in_header& header, + const fuse_open_in* /* in */, + fuse_open_out* out, + uint32_t* /*unused*/) { + if (handles_.size() >= NUM_MAX_HANDLES) { + // Too many open files. + return -EMFILE; + } + uint32_t handle; + do { + handle = handle_counter_++; + } while (handles_.count(handle) != 0); + + handles_.insert(std::make_pair(handle, header.nodeid)); + out->fh = handle; + return 0; + } + + int handle_fuse_read(const fuse_in_header& /* header */, + const fuse_read_in* in, + void* out, + uint32_t* reply_size) { + const std::map<uint32_t, uint64_t>::iterator it = handles_.find(in->fh); + if (it == handles_.end()) { + return -EBADF; + } + const int64_t result = get_object_bytes( + it->second, + in->offset, + in->size, + out); + if (result < 0) { + return -EIO; + } + *reply_size = static_cast<size_t>(result); + return 0; + } + + int handle_fuse_release(const fuse_in_header& /* header */, + const fuse_release_in* in, + void* /* out */, + uint32_t* /* reply_size */) { + handles_.erase(in->fh); + return 0; + } + + int handle_fuse_flush(const fuse_in_header& /* header */, + const void* /* in */, + void* /* out */, + uint32_t* /* reply_size */) { + return 0; + } + template <typename T, typename S> void invoke_handler(int fd, const FuseRequest& request, int (AppFuse::*handler)(const fuse_in_header&, const T*, S*, - size_t*), - size_t reply_size = sizeof(S)) { + uint32_t*), + uint32_t reply_size = sizeof(S)) { char reply_data[reply_size]; memset(reply_data, 0, reply_size); const int reply_code = (this->*handler)( @@ -186,6 +288,39 @@ private: reply_size); } + int64_t get_file_size(int inode) { + return static_cast<int64_t>(env_->CallLongMethod( + self_, + app_fuse_get_file_size, + static_cast<int>(inode))); + } + + int64_t get_object_bytes( + int inode, + uint64_t offset, + uint32_t size, + void* buf) { + const uint32_t read_size = static_cast<uint32_t>(std::min( + static_cast<uint64_t>(size), + get_file_size(inode) - offset)); + const jbyteArray array = (jbyteArray) env_->CallObjectMethod( + self_, + app_fuse_get_object_bytes, + inode, + offset, + read_size); + if (array == nullptr) { + return -1; + } + ScopedByteArrayRO bytes(env_, array); + if (bytes.size() != read_size || bytes.get() == nullptr) { + return -1; + } + + memcpy(buf, bytes.get(), read_size); + return read_size; + } + static void fuse_reply(int fd, int unique, int reply_code, void* reply_data, size_t reply_size) { // Don't send any data for error case. @@ -219,6 +354,7 @@ jboolean com_android_mtp_AppFuse_start_app_fuse_loop( ALOGD("Start fuse loop."); while (true) { FuseRequest request; + const ssize_t result = TEMP_FAILURE_RETRY( read(fd, request.buffer, sizeof(request.buffer))); if (result < 0) { @@ -272,12 +408,27 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { ALOGE("Can't find com/android/mtp/AppFuse"); return -1; } + app_fuse_class = static_cast<jclass>(env->NewGlobalRef(clazz)); if (app_fuse_class == nullptr) { ALOGE("Can't obtain global reference for com/android/mtp/AppFuse"); return -1; } + app_fuse_get_file_size = env->GetMethodID( + app_fuse_class, "getFileSize", "(I)J"); + if (app_fuse_get_file_size == nullptr) { + ALOGE("Can't find getFileSize"); + return -1; + } + + app_fuse_get_object_bytes = env->GetMethodID( + app_fuse_class, "getObjectBytes", "(IJI)[B"); + if (app_fuse_get_object_bytes == nullptr) { + ALOGE("Can't find getObjectBytes"); + return -1; + } + const int result = android::AndroidRuntime::registerNativeMethods( env, "com/android/mtp/AppFuse", gMethods, NELEM(gMethods)); if (result < 0) { diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java index 2c09ad135266..5ffd7cf27b5f 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java @@ -17,16 +17,14 @@ package com.android.mtp; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.storage.StorageManager; import android.util.Log; - import com.android.internal.annotations.VisibleForTesting; - import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; -import android.os.Process; - /** * TODO: Remove VisibleForTesting class. */ @@ -37,12 +35,14 @@ public class AppFuse { } private final String mName; + private final Callback mCallback; private final Thread mMessageThread; private ParcelFileDescriptor mDeviceFd; @VisibleForTesting - AppFuse(String name) { + AppFuse(String name, Callback callback) { mName = name; + mCallback = callback; mMessageThread = new Thread(new Runnable() { @Override public void run() { @@ -72,10 +72,45 @@ public class AppFuse { } } + /** + * @param i + * @throws FileNotFoundException + */ + @VisibleForTesting + public ParcelFileDescriptor openFile(int i) throws FileNotFoundException { + return ParcelFileDescriptor.open(new File( + getMountPoint(), + Integer.toString(i)), + ParcelFileDescriptor.MODE_READ_ONLY); + } + @VisibleForTesting File getMountPoint() { return new File("/mnt/appfuse/" + Process.myUid() + "_" + mName); } + static interface Callback { + long getFileSize(int inode) throws FileNotFoundException; + byte[] getObjectBytes(int inode, long offset, int size) throws IOException; + } + + @VisibleForTesting + private long getFileSize(int inode) { + try { + return mCallback.getFileSize(inode); + } catch (IOException e) { + return -1; + } + } + + @VisibleForTesting + private byte[] getObjectBytes(int inode, long offset, int size) { + try { + return mCallback.getObjectBytes(inode, offset, size); + } catch (IOException e) { + return null; + } + } + private native boolean native_start_app_fuse_loop(int fd); } diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java index b66d8ebec212..76bd2b546472 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java @@ -16,24 +16,27 @@ package com.android.mtp; +import android.os.ParcelFileDescriptor; import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; +import android.test.suitebuilder.annotation.MediumTest; import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Arrays; -@SmallTest +/** + * TODO: Enable this test after adding SELinux policies for appfuse. + */ +@MediumTest public class AppFuseTest extends AndroidTestCase { - /** - * TODO: Enable this test after adding SELinux policies for appfuse. - * @throws ErrnoException - * @throws InterruptedException - */ - public void disabled_testBasic() throws ErrnoException, InterruptedException { + + public void disabled_testMount() throws ErrnoException, InterruptedException { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); - final AppFuse appFuse = new AppFuse("test"); + final AppFuse appFuse = new AppFuse("test", new TestCallback()); appFuse.mount(storageManager); final File file = appFuse.getMountPoint(); assertTrue(file.isDirectory()); @@ -41,4 +44,86 @@ public class AppFuseTest extends AndroidTestCase { appFuse.close(); assertTrue(1 != Os.stat(file.getPath()).st_ino); } + + public void disabled_testOpenFile() throws IOException { + final StorageManager storageManager = getContext().getSystemService(StorageManager.class); + final int INODE = 10; + final AppFuse appFuse = new AppFuse( + "test", + new TestCallback() { + @Override + public long getFileSize(int inode) throws FileNotFoundException { + if (INODE == inode) { + return 1024; + } + throw new FileNotFoundException(); + } + }); + appFuse.mount(storageManager); + final ParcelFileDescriptor fd = appFuse.openFile(INODE); + fd.close(); + appFuse.close(); + } + + public void disabled_testOpenFile_error() { + final StorageManager storageManager = getContext().getSystemService(StorageManager.class); + final int INODE = 10; + final AppFuse appFuse = new AppFuse("test", new TestCallback()); + appFuse.mount(storageManager); + try { + appFuse.openFile(INODE); + fail(); + } catch (Throwable t) { + assertTrue(t instanceof FileNotFoundException); + } + appFuse.close(); + } + + public void disabled_testReadFile() throws IOException { + final StorageManager storageManager = getContext().getSystemService(StorageManager.class); + final int INODE = 10; + final byte[] BYTES = new byte[] { 'a', 'b', 'c', 'd', 'e' }; + final AppFuse appFuse = new AppFuse( + "test", + new TestCallback() { + @Override + public long getFileSize(int inode) throws FileNotFoundException { + if (inode == INODE) { + return BYTES.length; + } + return super.getFileSize(inode); + } + + @Override + public byte[] getObjectBytes(int inode, long offset, int size) + throws IOException { + if (inode == INODE) { + return Arrays.copyOfRange(BYTES, (int) offset, (int) offset + size); + } + return super.getObjectBytes(inode, offset, size); + } + }); + appFuse.mount(storageManager); + final ParcelFileDescriptor fd = appFuse.openFile(INODE); + try (final ParcelFileDescriptor.AutoCloseInputStream stream = + new ParcelFileDescriptor.AutoCloseInputStream(fd)) { + final byte[] buffer = new byte[1024]; + final int size = stream.read(buffer, 0, buffer.length); + assertEquals(5, size); + } + appFuse.close(); + } + + private static class TestCallback implements AppFuse.Callback { + @Override + public long getFileSize(int inode) throws FileNotFoundException { + throw new FileNotFoundException(); + } + + @Override + public byte[] getObjectBytes(int inode, long offset, int size) + throws IOException { + throw new IOException(); + } + } } diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java index bbd0a300c6a1..9a976591aef9 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java @@ -203,4 +203,9 @@ public class TestMtpManager extends MtpManager { } return Arrays.copyOf(result, count); } + + @Override + byte[] getObject(int deviceId, int objectHandle, int expectedSize) throws IOException { + return mImportFileBytes.get(pack(deviceId, objectHandle)); + } } diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index 97a7bffe9bf3..b662c58fa122 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -178,12 +178,6 @@ <!-- Template for the notification label for a blocked print job. [CHAR LIMIT=25] --> <string name="blocked_notification_title_template">Printer blocked <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string> - <!-- Template for the notification label for a composite (multiple items) print jobs notification. [CHAR LIMIT=25] --> - <plurals name="composite_notification_title_template"> - <item quantity="one"><xliff:g id="print_job_name" example="foo.jpg">%1$d</xliff:g> print job</item> - <item quantity="other"><xliff:g id="print_job_name" example="foo.jpg">%1$d</xliff:g> print jobs</item> - </plurals> - <!-- Label for the notification button for cancelling a print job. [CHAR LIMIT=25] --> <string name="cancel">Cancel</string> diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java index 3dc5d7eccdf6..0210693c302c 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java +++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java @@ -40,6 +40,7 @@ import android.print.PrintJobId; import android.print.PrintJobInfo; import android.print.PrintManager; import android.provider.Settings; +import android.util.ArraySet; import android.util.Log; import com.android.printspooler.R; @@ -61,13 +62,22 @@ final class NotificationController { private static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID"; + private static final String PRINT_JOB_NOTIFICATION_GROUP_KEY = "PRINT_JOB_NOTIFICATIONS"; + private static final String PRINT_JOB_NOTIFICATION_SUMMARY = "PRINT_JOB_NOTIFICATIONS_SUMMARY"; + private final Context mContext; private final NotificationManager mNotificationManager; + /** + * Mapping from printJobIds to their notification Ids. + */ + private final ArraySet<PrintJobId> mNotifications; + public NotificationController(Context context) { mContext = context; mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + mNotifications = new ArraySet<>(0); } public void onUpdateNotifications(List<PrintJobInfo> printJobs) { @@ -81,16 +91,44 @@ final class NotificationController { } } - updateNotification(notifyPrintJobs); + updateNotifications(notifyPrintJobs); } - private void updateNotification(List<PrintJobInfo> printJobs) { - if (printJobs.size() <= 0) { - removeNotification(); - } else if (printJobs.size() == 1) { - createSimpleNotification(printJobs.get(0)); - } else { + /** + * Update notifications for the given print jobs, remove all other notifications. + * + * @param printJobs The print job that we want to create notifications for. + */ + private void updateNotifications(List<PrintJobInfo> printJobs) { + ArraySet<PrintJobId> removedPrintJobs = new ArraySet<>(mNotifications); + + final int numPrintJobs = printJobs.size(); + + // Create summary notification + if (numPrintJobs > 1) { createStackedNotification(printJobs); + } else { + mNotificationManager.cancel(PRINT_JOB_NOTIFICATION_SUMMARY, 0); + } + + // Create per print job notification + for (int i = 0; i < numPrintJobs; i++) { + PrintJobInfo printJob = printJobs.get(i); + PrintJobId printJobId = printJob.getId(); + + removedPrintJobs.remove(printJobId); + mNotifications.add(printJobId); + + createSimpleNotification(printJob); + } + + // Remove notifications for print jobs that do not exist anymore + final int numRemovedPrintJobs = removedPrintJobs.size(); + for (int i = 0; i < numRemovedPrintJobs; i++) { + PrintJobId removedPrintJob = removedPrintJobs.valueAt(i); + + mNotificationManager.cancel(removedPrintJob.flattenToString(), 0); + mNotifications.remove(removedPrintJob); } } @@ -148,7 +186,8 @@ final class NotificationController { .setOngoing(true) .setShowWhen(true) .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)); + com.android.internal.R.color.system_notification_accent_color)) + .setGroup(PRINT_JOB_NOTIFICATION_GROUP_KEY); if (firstAction != null) { builder.addAction(firstAction); @@ -176,7 +215,7 @@ final class NotificationController { builder.setContentText(printJob.getPrinterName()); } - mNotificationManager.notify(0, builder.build()); + mNotificationManager.notify(printJob.getId().flattenToString(), 0, builder.build()); } private void createPrintingNotification(PrintJobInfo printJob) { @@ -204,33 +243,36 @@ final class NotificationController { .setContentIntent(createContentIntent(null)) .setWhen(System.currentTimeMillis()) .setOngoing(true) - .setShowWhen(true); + .setShowWhen(true) + .setGroup(PRINT_JOB_NOTIFICATION_GROUP_KEY) + .setGroupSummary(true); final int printJobCount = printJobs.size(); InboxStyle inboxStyle = new InboxStyle(); - inboxStyle.setBigContentTitle(String.format(mContext.getResources().getQuantityText( - R.plurals.composite_notification_title_template, - printJobCount).toString(), printJobCount)); + int icon = com.android.internal.R.drawable.ic_print; for (int i = printJobCount - 1; i>= 0; i--) { PrintJobInfo printJob = printJobs.get(i); - if (i == printJobCount - 1) { - builder.setLargeIcon(((BitmapDrawable) mContext.getResources().getDrawable( - computeNotificationIcon(printJob), null)).getBitmap()); - builder.setSmallIcon(computeNotificationIcon(printJob)); - builder.setContentTitle(computeNotificationTitle(printJob)); - builder.setContentText(printJob.getPrinterName()); - } + inboxStyle.addLine(computeNotificationTitle(printJob)); + + // if any print job is in an error state show an error icon for the summary + if (printJob.getState() == PrintJobInfo.STATE_FAILED + || printJob.getState() == PrintJobInfo.STATE_BLOCKED) { + icon = com.android.internal.R.drawable.ic_print_error; + } } + builder.setSmallIcon(icon); + builder.setLargeIcon( + ((BitmapDrawable) mContext.getResources().getDrawable(icon, null)).getBitmap()); builder.setNumber(printJobCount); builder.setStyle(inboxStyle); builder.setColor(mContext.getColor( com.android.internal.R.color.system_notification_accent_color)); - mNotificationManager.notify(0, builder.build()); + mNotificationManager.notify(PRINT_JOB_NOTIFICATION_SUMMARY, 0, builder.build()); } private String computeNotificationTitle(PrintJobInfo printJob) { @@ -264,10 +306,6 @@ final class NotificationController { } } - private void removeNotification() { - mNotificationManager.cancel(0); - } - private PendingIntent createContentIntent(PrintJobId printJobId) { Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS); if (printJobId != null) { diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java index 496a0b09b905..18160ff56ce8 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java +++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java @@ -1416,9 +1416,9 @@ public final class PrintSpoolerService extends Service { } @Override - public void removeApprovedPrintService(ComponentName serviceToRemove) { + public void pruneApprovedPrintServices(List<ComponentName> servicesToKeep) { (new ApprovedPrintServices(PrintSpoolerService.this)) - .removeApprovedService(serviceToRemove); + .pruneApprovedServices(servicesToKeep); } @Override diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java index cdfc7ee11b1e..81727ab4222a 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java @@ -66,6 +66,7 @@ import android.widget.ListView; import android.widget.SearchView; import android.widget.TextView; +import com.android.internal.content.PackageMonitor; import com.android.printspooler.R; import java.util.ArrayList; @@ -101,7 +102,8 @@ public final class SelectPrinterActivity extends Activity { private AnnounceFilterResult mAnnounceFilterResult; /** Monitor if new print services get enabled or disabled */ - private ContentObserver mPrintServicesObserver; + private ContentObserver mPrintServicesDisabledObserver; + private PackageMonitor mPackageObserver; @Override public void onCreate(Bundle savedInstanceState) { @@ -245,28 +247,45 @@ public final class SelectPrinterActivity extends Activity { * Register listener for changes to the enabled print services. */ private void registerServiceMonitor() { - mPrintServicesObserver = new ContentObserver(new Handler()) { + // Listen for services getting disabled + mPrintServicesDisabledObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { onPrintServicesUpdate(); } }; + // Listen for services getting installed or uninstalled + mPackageObserver = new PackageMonitor() { + @Override + public void onPackageModified(String packageName) { + onPrintServicesUpdate(); + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + onPrintServicesUpdate(); + } + + @Override + public void onPackageAdded(String packageName, int uid) { + onPrintServicesUpdate(); + } + }; + getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.ENABLED_PRINT_SERVICES), false, - mPrintServicesObserver); + Settings.Secure.getUriFor(Settings.Secure.DISABLED_PRINT_SERVICES), false, + mPrintServicesDisabledObserver); + + mPackageObserver.register(this, getMainLooper(), false); } /** - * Unregister {@link #mPrintServicesObserver listener for changes to the enabled print services} - * or nothing if the listener is not registered. + * Unregister the listeners for changes to the enabled print services. */ private void unregisterServiceMonitorIfNeeded() { - if (mPrintServicesObserver != null) { - getContentResolver().unregisterContentObserver(mPrintServicesObserver); - - mPrintServicesObserver = null; - } + getContentResolver().unregisterContentObserver(mPrintServicesDisabledObserver); + mPackageObserver.unregister(); } @Override diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java b/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java index dd10567f00ab..a1e3ef430c9e 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java +++ b/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java @@ -23,6 +23,7 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.printservice.PrintService; import android.util.ArraySet; +import java.util.List; import java.util.Set; /** @@ -126,29 +127,27 @@ public class ApprovedPrintServices { } /** - * If a {@link PrintService} is approved, remove it from the list of approved services. + * Remove all approved {@link PrintService print services} that are not in the given set. * - * @param serviceToRemove The {@link ComponentName} of the {@link PrintService} to be removed + * @param serviceNamesToKeep The {@link ComponentName names } of the services to keep */ - public void removeApprovedService(ComponentName serviceToRemove) { + public void pruneApprovedServices(List<ComponentName> serviceNamesToKeep) { synchronized (sLock) { - if (isApprovedService(serviceToRemove)) { - // Copy approved services. - ArraySet<String> approvedServices = new ArraySet<String>( - mPreferences.getStringSet(APPROVED_SERVICES_PREFERENCE, null)); + Set<String> approvedServices = getApprovedServices(); + Set<String> newApprovedServices = new ArraySet<>(approvedServices.size()); + + final int numServiceNamesToKeep = serviceNamesToKeep.size(); + for(int i = 0; i < numServiceNamesToKeep; i++) { + String serviceToKeep = serviceNamesToKeep.get(i).flattenToShortString(); + if (approvedServices.contains(serviceToKeep)) { + newApprovedServices.add(serviceToKeep); + } + } + if (approvedServices.size() != newApprovedServices.size()) { SharedPreferences.Editor editor = mPreferences.edit(); - final int numApprovedServices = approvedServices.size(); - for (int i = 0; i < numApprovedServices; i++) { - if (approvedServices.valueAt(i) - .equals(serviceToRemove.flattenToShortString())) { - approvedServices.removeAt(i); - break; - } - } - - editor.putStringSet(APPROVED_SERVICES_PREFERENCE, approvedServices); + editor.putStringSet(APPROVED_SERVICES_PREFERENCE, newApprovedServices); editor.apply(); } } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index c6d9e98774d0..7416fb564e27 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -151,6 +151,14 @@ </intent-filter> </receiver> + <receiver + android:name=".RemoteBugreportReceiver" + android:permission="android.permission.DUMP"> + <intent-filter> + <action android:name="android.intent.action.REMOTE_BUGREPORT_FINISHED" /> + </intent-filter> + </receiver> + <service android:name=".BugreportProgressService" android:exported="false"/> diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index f7a2d75a216f..5c807e1bd652 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -21,6 +21,7 @@ import static com.android.shell.BugreportPrefs.STATE_SHOW; import static com.android.shell.BugreportPrefs.getWarningState; import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -28,10 +29,13 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; import java.text.NumberFormat; import java.util.ArrayList; +import java.util.Enumeration; import java.util.List; import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import libcore.io.Streams; @@ -112,6 +116,10 @@ public class BugreportProgressService extends Service { // External intents sent by dumpstate. static final String INTENT_BUGREPORT_STARTED = "android.intent.action.BUGREPORT_STARTED"; static final String INTENT_BUGREPORT_FINISHED = "android.intent.action.BUGREPORT_FINISHED"; + static final String INTENT_REMOTE_BUGREPORT_FINISHED = + "android.intent.action.REMOTE_BUGREPORT_FINISHED"; + static final String INTENT_REMOTE_BUGREPORT_DISPATCH = + "android.intent.action.REMOTE_BUGREPORT_DISPATCH"; // Internal intents used on notification actions. static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL"; @@ -813,6 +821,9 @@ public class BugreportProgressService extends Service { Log.e(TAG, "INTERNAL ERROR: no info for PID " + pid + ": " + mProcesses); return; } + + addDetailsToZipFile(info); + final Intent sendIntent = buildSendIntent(mContext, info); final Intent notifIntent; @@ -868,7 +879,7 @@ public class BugreportProgressService extends Service { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { - info.bugreportFile = zipBugreport(info.bugreportFile); + zipBugreport(info); sendBugreportNotification(context, info); return null; } @@ -879,35 +890,92 @@ public class BugreportProgressService extends Service { * Zips a bugreport file, returning the path to the new file (or to the * original in case of failure). */ - private static File zipBugreport(File bugreportFile) { - String bugreportPath = bugreportFile.getAbsolutePath(); - String zippedPath = bugreportPath.replace(".txt", ".zip"); + private static void zipBugreport(BugreportInfo info) { + final String bugreportPath = info.bugreportFile.getAbsolutePath(); + final String zippedPath = bugreportPath.replace(".txt", ".zip"); Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath); - File bugreportZippedFile = new File(zippedPath); - try (InputStream is = new FileInputStream(bugreportFile); + final File bugreportZippedFile = new File(zippedPath); + try (InputStream is = new FileInputStream(info.bugreportFile); ZipOutputStream zos = new ZipOutputStream( new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) { - ZipEntry entry = new ZipEntry(bugreportFile.getName()); - entry.setTime(bugreportFile.lastModified()); - zos.putNextEntry(entry); - int totalBytes = Streams.copy(is, zos); - Log.v(TAG, "size of original bugreport: " + totalBytes + " bytes"); - zos.closeEntry(); - // Delete old file; - boolean deleted = bugreportFile.delete(); + addEntry(zos, info.bugreportFile.getName(), is); + // Delete old file + final boolean deleted = info.bugreportFile.delete(); if (deleted) { Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")"); } else { Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")"); } - return bugreportZippedFile; + info.bugreportFile = bugreportZippedFile; } catch (IOException e) { Log.e(TAG, "exception zipping file " + zippedPath, e); - return bugreportFile; // Return original. } } /** + * Adds the user-provided info into the bugreport zip file. + * <p> + * If user provided a title, it will be saved into a {@code title.txt} entry; similarly, the + * description will be saved on {@code description.txt}. + */ + private void addDetailsToZipFile(BugreportInfo info) { + // It's not possible to add a new entry into an existing file, so we need to create a new + // zip, copy all entries, then rename it. + final File dir = info.bugreportFile.getParentFile(); + final File tmpZip = new File(dir, "tmp-" + info.bugreportFile.getName()); + Log.d(TAG, "Writing temporary zip file (" + tmpZip + ")"); + try (ZipFile oldZip = new ZipFile(info.bugreportFile); + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tmpZip))) { + + // First copy contents from original zip. + Enumeration<? extends ZipEntry> entries = oldZip.entries(); + while (entries.hasMoreElements()) { + final ZipEntry entry = entries.nextElement(); + final String entryName = entry.getName(); + if (!entry.isDirectory()) { + addEntry(zos, entryName, entry.getTime(), oldZip.getInputStream(entry)); + } else { + Log.w(TAG, "skipping directory entry: " + entryName); + } + } + + // Then add the user-provided info. + addEntry(zos, "title.txt", info.title); + addEntry(zos, "description.txt", info.description); + } catch (IOException e) { + Log.e(TAG, "exception zipping file " + tmpZip, e); + return; + } + + if (!tmpZip.renameTo(info.bugreportFile)) { + Log.e(TAG, "Could not rename " + tmpZip + " to " + info.bugreportFile); + } + } + + private static void addEntry(ZipOutputStream zos, String entry, String text) + throws IOException { + if (DEBUG) Log.v(TAG, "adding entry '" + entry + "': " + text); + if (!TextUtils.isEmpty(text)) { + addEntry(zos, entry, new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8))); + } + } + + private static void addEntry(ZipOutputStream zos, String entryName, InputStream is) + throws IOException { + addEntry(zos, entryName, System.currentTimeMillis(), is); + } + + private static void addEntry(ZipOutputStream zos, String entryName, long timestamp, + InputStream is) throws IOException { + final ZipEntry entry = new ZipEntry(entryName); + entry.setTime(timestamp); + zos.putNextEntry(entry); + final int totalBytes = Streams.copy(is, zos); + if (DEBUG) Log.v(TAG, "size of '" + entryName + "' entry: " + totalBytes + " bytes"); + zos.closeEntry(); + } + + /** * Find the best matching {@link Account} based on build properties. */ private static Account findSendToAccount(Context context) { @@ -941,7 +1009,7 @@ public class BugreportProgressService extends Service { return foundAccount; } - private static Uri getUri(Context context, File file) { + static Uri getUri(Context context, File file) { return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null; } diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java index b818343b6ce0..c8898b97d878 100644 --- a/packages/Shell/src/com/android/shell/BugreportReceiver.java +++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java @@ -52,7 +52,7 @@ public class BugreportReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // Clean up older bugreports in background - cleanupOldFiles(intent); + cleanupOldFiles(this, intent, INTENT_BUGREPORT_FINISHED, MIN_KEEP_COUNT, MIN_KEEP_AGE); // Delegate intent handling to service. Intent serviceIntent = new Intent(context, BugreportProgressService.class); @@ -60,8 +60,9 @@ public class BugreportReceiver extends BroadcastReceiver { context.startService(serviceIntent); } - private void cleanupOldFiles(Intent intent) { - if (!INTENT_BUGREPORT_FINISHED.equals(intent.getAction())) { + static void cleanupOldFiles(BroadcastReceiver br, Intent intent, String expectedAction, + final int minCount, final long minAge) { + if (!expectedAction.equals(intent.getAction())) { return; } final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT); @@ -69,12 +70,11 @@ public class BugreportReceiver extends BroadcastReceiver { Log.e(TAG, "Not deleting old files because file " + bugreportFile + " doesn't exist"); return; } - final PendingResult result = goAsync(); + final PendingResult result = br.goAsync(); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { - FileUtils.deleteOlderFiles( - bugreportFile.getParentFile(), MIN_KEEP_COUNT, MIN_KEEP_AGE); + FileUtils.deleteOlderFiles(bugreportFile.getParentFile(), minCount, minAge); result.finish(); return null; } diff --git a/packages/Shell/src/com/android/shell/RemoteBugreportReceiver.java b/packages/Shell/src/com/android/shell/RemoteBugreportReceiver.java new file mode 100644 index 000000000000..6f783a1fbabf --- /dev/null +++ b/packages/Shell/src/com/android/shell/RemoteBugreportReceiver.java @@ -0,0 +1,63 @@ +/* + * 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 com.android.shell; + +import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT; +import static com.android.shell.BugreportProgressService.INTENT_REMOTE_BUGREPORT_FINISHED; +import static com.android.shell.BugreportProgressService.INTENT_REMOTE_BUGREPORT_DISPATCH; +import static com.android.shell.BugreportProgressService.getFileExtra; +import static com.android.shell.BugreportProgressService.getUri; +import static com.android.shell.BugreportReceiver.cleanupOldFiles; + +import java.io.File; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.UserHandle; + +/** + * Receiver that handles finished remote bugreports, by re-sending + * the intent with appended bugreport zip file URI. + * + * <p> Remote bugreport never contains a screenshot. + */ +public class RemoteBugreportReceiver extends BroadcastReceiver { + + private static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport"; + private static final String EXTRA_REMOTE_BUGREPORT_HASH = + "android.intent.extra.REMOTE_BUGREPORT_HASH"; + + /** Always keep just the last remote bugreport zip file */ + private static final int MIN_KEEP_COUNT = 1; + + @Override + public void onReceive(Context context, Intent intent) { + cleanupOldFiles(this, intent, INTENT_REMOTE_BUGREPORT_FINISHED, MIN_KEEP_COUNT, 0); + + final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT); + final Uri bugreportUri = getUri(context, bugreportFile); + final String bugreportHash = intent.getStringExtra(EXTRA_REMOTE_BUGREPORT_HASH); + + final Intent newIntent = new Intent(INTENT_REMOTE_BUGREPORT_DISPATCH); + newIntent.setDataAndType(bugreportUri, BUGREPORT_MIMETYPE); + newIntent.putExtra(EXTRA_REMOTE_BUGREPORT_HASH, bugreportHash); + context.sendBroadcastAsUser(newIntent, UserHandle.SYSTEM, + android.Manifest.permission.DUMP); + } +} diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java index 8e8924ae6bf3..d1a07ea21fc4 100644 --- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java +++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java @@ -60,6 +60,7 @@ import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject; import android.test.InstrumentationTestCase; import android.test.suitebuilder.annotation.LargeTest; +import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; @@ -103,6 +104,12 @@ public class BugreportReceiverTest extends InstrumentationTestCase { private static final String NAME = "BUG, Y U NO REPORT?"; private static final String NEW_NAME = "Bug_Forrest_Bug"; private static final String TITLE = "Wimbugdom Champion 2015"; + + private static final String NO_DESCRIPTION = null; + private static final String NO_NAME = null; + private static final String NO_SCREENSHOT = null; + private static final String NO_TITLE = null; + private String mDescription; private String mPlainTextPath; @@ -157,8 +164,8 @@ public class BugreportReceiverTest extends InstrumentationTestCase { Bundle extras = sendBugreportFinishedAndGetSharedIntent(PID, mPlainTextPath, mScreenshotPath); - assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, NAME, ZIP_FILE, - null, 1, true); + assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, ZIP_FILE, + NAME, NO_TITLE, NO_DESCRIPTION, 1, true); assertServiceNotRunning(); } @@ -174,13 +181,14 @@ public class BugreportReceiverTest extends InstrumentationTestCase { Bundle extras = sendBugreportFinishedAndGetSharedIntent(PID, mPlainTextPath, mScreenshotPath); - assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, NAME, ZIP_FILE, - null, 2, true); + assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, ZIP_FILE, + NAME, NO_TITLE, NO_DESCRIPTION, 2, true); assertServiceNotRunning(); } - public void testProgress_changeDetails() throws Exception { + public void testProgress_changeDetailsInvalidInput() throws Exception { + resetProperties(); sendBugreportStarted(1000); waitForScreenshotButtonEnabled(true); @@ -219,8 +227,47 @@ public class BugreportReceiverTest extends InstrumentationTestCase { Bundle extras = sendBugreportFinishedAndGetSharedIntent(PID, mPlainTextPath, mScreenshotPath); - assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, NEW_NAME, TITLE, - mDescription, 1, true); + assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, TITLE, + NEW_NAME, TITLE, mDescription, 1, true); + + assertServiceNotRunning(); + } + + public void testProgress_changeDetailsPlainBugreport() throws Exception { + changeDetailsTest(true); + } + + public void testProgress_changeDetailsZippedBugreport() throws Exception { + changeDetailsTest(false); + } + + public void changeDetailsTest(boolean plainText) throws Exception { + + resetProperties(); + sendBugreportStarted(1000); + waitForScreenshotButtonEnabled(true); + + DetailsUi detailsUi = new DetailsUi(mUiBot); + + // Check initial name. + String actualName = detailsUi.nameField.getText().toString(); + assertEquals("Wrong value on field 'name'", NAME, actualName); + + // Change fields. + detailsUi.reOpen(); + detailsUi.nameField.setText(NEW_NAME); + detailsUi.titleField.setText(TITLE); + detailsUi.descField.setText(mDescription); + + detailsUi.clickOk(); + + assertPropertyValue(NAME_PROPERTY, NEW_NAME); + assertProgressNotification(NEW_NAME, "0.00%"); + + Bundle extras = sendBugreportFinishedAndGetSharedIntent(PID, + plainText? mPlainTextPath : mZipPath, mScreenshotPath); + assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, TITLE, + NEW_NAME, TITLE, mDescription, 1, true); assertServiceNotRunning(); } @@ -270,8 +317,8 @@ public class BugreportReceiverTest extends InstrumentationTestCase { // Finally, share bugreport. Bundle extras = acceptBugreportAndGetSharedIntent(); - assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, NAME, TITLE, - mDescription, 1, waitScreenshot); + assertActionSendMultiple(extras, BUGREPORT_FILE, BUGREPORT_CONTENT, PID, TITLE, + NAME, TITLE, mDescription, 1, waitScreenshot); assertServiceNotRunning(); } @@ -296,7 +343,7 @@ public class BugreportReceiverTest extends InstrumentationTestCase { // Share the bugreport. mUiBot.chooseActivity(UI_NAME); Bundle extras = mListener.getExtras(); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, null); + assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT); // Make sure it's hidden now. int newState = BugreportPrefs.getWarningState(mContext, BugreportPrefs.STATE_UNKNOWN); @@ -314,13 +361,13 @@ public class BugreportReceiverTest extends InstrumentationTestCase { } public void testBugreportFinished_plainBugreportAndNoScreenshot() throws Exception { - Bundle extras = sendBugreportFinishedAndGetSharedIntent(mPlainTextPath, null); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, null); + Bundle extras = sendBugreportFinishedAndGetSharedIntent(mPlainTextPath, NO_SCREENSHOT); + assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT); } public void testBugreportFinished_zippedBugreportAndNoScreenshot() throws Exception { - Bundle extras = sendBugreportFinishedAndGetSharedIntent(mZipPath, null); - assertActionSendMultiple(extras, BUGREPORT_CONTENT, null); + Bundle extras = sendBugreportFinishedAndGetSharedIntent(mZipPath, NO_SCREENSHOT); + assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT); } private void cancelExistingNotifications() { @@ -426,8 +473,8 @@ public class BugreportReceiverTest extends InstrumentationTestCase { */ private void assertActionSendMultiple(Bundle extras, String bugreportContent, String screenshotContent) throws IOException { - assertActionSendMultiple(extras, bugreportContent, screenshotContent, PID, null, ZIP_FILE, - null, 0, false); + assertActionSendMultiple(extras, bugreportContent, screenshotContent, PID, ZIP_FILE, + NO_NAME, NO_TITLE, NO_DESCRIPTION, 0, false); } /** @@ -437,14 +484,16 @@ public class BugreportReceiverTest extends InstrumentationTestCase { * @param bugreportContent expected content in the bugreport file * @param screenshotContent expected content in the screenshot file (sent by dumpstate), if any * @param pid emulated dumpstate pid - * @param name bugreport name as provided by the user - * @param title bugreport name as provided by the user (or received by dumpstate) + * @param name expected subject + * @param name bugreport name as provided by the user (or received by dumpstate) + * @param title bugreport name as provided by the user * @param description bugreport description as provided by the user * @param numberScreenshots expected number of screenshots taken by Shell. * @param renamedScreenshots whether the screenshots are expected to be renamed */ private void assertActionSendMultiple(Bundle extras, String bugreportContent, - String screenshotContent, int pid, String name, String title, String description, + String screenshotContent, int pid, String subject, + String name, String title, String description, int numberScreenshots, boolean renamedScreenshots) throws IOException { String body = extras.getString(Intent.EXTRA_TEXT); assertContainsRegex("missing build info", @@ -455,7 +504,7 @@ public class BugreportReceiverTest extends InstrumentationTestCase { assertContainsRegex("missing description", description, body); } - assertEquals("wrong subject", title, extras.getString(Intent.EXTRA_SUBJECT)); + assertEquals("wrong subject", subject, extras.getString(Intent.EXTRA_SUBJECT)); List<Uri> attachments = extras.getParcelableArrayList(Intent.EXTRA_STREAM); int expectedNumberScreenshots = numberScreenshots; @@ -478,6 +527,12 @@ public class BugreportReceiverTest extends InstrumentationTestCase { } assertNotNull("did not get .zip attachment", zipUri); assertZipContent(zipUri, BUGREPORT_FILE, BUGREPORT_CONTENT); + if (!TextUtils.isEmpty(title)) { + assertZipContent(zipUri, "title.txt", title); + } + if (!TextUtils.isEmpty(description)) { + assertZipContent(zipUri, "description.txt", description); + } // URI of the screenshot taken by dumpstate. Uri externalScreenshotUri = null; diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index 6a10c2c62508..bc182215c0d4 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -10,6 +10,7 @@ public void setGlowScale(float); } +-keep class com.android.systemui.statusbar.car.CarStatusBar -keep class com.android.systemui.statusbar.phone.PhoneStatusBar -keep class com.android.systemui.statusbar.tv.TvStatusBar diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_carmode.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_carmode.png Binary files differnew file mode 100644 index 000000000000..6242084ea175 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_carmode.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_ime_carmode.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_ime_carmode.png Binary files differnew file mode 100644 index 000000000000..1b37a47338aa --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_back_ime_carmode.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_home_carmode.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_home_carmode.png Binary files differnew file mode 100644 index 000000000000..9e0575883a06 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_home_carmode.png diff --git a/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_sysbar_back_carmode.png b/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_sysbar_back_carmode.png Binary files differnew file mode 100644 index 000000000000..2fcfdde08164 --- /dev/null +++ b/packages/SystemUI/res/drawable-ldrtl-hdpi/ic_sysbar_back_carmode.png diff --git a/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_sysbar_back_carmode.png b/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_sysbar_back_carmode.png Binary files differnew file mode 100644 index 000000000000..48708a5099a8 --- /dev/null +++ b/packages/SystemUI/res/drawable-ldrtl-mdpi/ic_sysbar_back_carmode.png diff --git a/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_sysbar_back_carmode.png b/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_sysbar_back_carmode.png Binary files differnew file mode 100644 index 000000000000..3d731840a93f --- /dev/null +++ b/packages/SystemUI/res/drawable-ldrtl-xhdpi/ic_sysbar_back_carmode.png diff --git a/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_sysbar_back_carmode.png b/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_sysbar_back_carmode.png Binary files differnew file mode 100644 index 000000000000..786935d5d185 --- /dev/null +++ b/packages/SystemUI/res/drawable-ldrtl-xxhdpi/ic_sysbar_back_carmode.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_carmode.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_carmode.png Binary files differnew file mode 100644 index 000000000000..e4bd4bc3e39d --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_carmode.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_ime_carmode.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_ime_carmode.png Binary files differnew file mode 100644 index 000000000000..94ccf7912e8f --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_back_ime_carmode.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_home_carmode.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_home_carmode.png Binary files differnew file mode 100644 index 000000000000..980bbbcb4ceb --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_home_carmode.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_carmode.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_carmode.png Binary files differnew file mode 100644 index 000000000000..201be3b3c8b5 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_carmode.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_ime_carmode.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_ime_carmode.png Binary files differnew file mode 100644 index 000000000000..2770d6264da0 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_back_ime_carmode.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_home_carmode.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_home_carmode.png Binary files differnew file mode 100644 index 000000000000..8ac64937007b --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_home_carmode.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_back_carmode.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_back_carmode.png Binary files differnew file mode 100644 index 000000000000..8e3678b292b8 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_back_carmode.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_back_ime_carmode.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_back_ime_carmode.png Binary files differnew file mode 100644 index 000000000000..6c7cb0582733 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_back_ime_carmode.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_home_carmode.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_home_carmode.png Binary files differnew file mode 100644 index 000000000000..ea2b108e1e4c --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_home_carmode.png diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_back_carmode.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_back_carmode.png Binary files differnew file mode 100644 index 000000000000..3e11023ef7e1 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_back_carmode.png diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_back_ime_carmode.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_back_ime_carmode.png Binary files differnew file mode 100644 index 000000000000..fe8213d6390e --- /dev/null +++ b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_back_ime_carmode.png diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_home_carmode.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_home_carmode.png Binary files differnew file mode 100644 index 000000000000..c117efd1e654 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_home_carmode.png diff --git a/packages/SystemUI/res/layout/car_navigation_bar.xml b/packages/SystemUI/res/layout/car_navigation_bar.xml new file mode 100644 index 000000000000..f7f673db6197 --- /dev/null +++ b/packages/SystemUI/res/layout/car_navigation_bar.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2016, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<com.android.systemui.statusbar.car.CarNavigationBarView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:background="@drawable/system_bar_background"> + + <!-- phone.NavigationBarView has rot0 and rot90 but we expect the car head unit to have a fixed + rotation so skip this level of the heirarchy. + --> + <LinearLayout + android:layout_height="match_parent" + android:layout_width="match_parent" + android:orientation="horizontal" + android:clipChildren="false" + android:clipToPadding="false" + android:id="@+id/nav_buttons" + android:animateLayoutChanges="true"> + + <!-- Buttons get populated here from a car_arrays.xml. --> + </LinearLayout> + + <!-- lights out layout to match exactly --> + <LinearLayout + android:layout_height="match_parent" + android:layout_width="match_parent" + android:orientation="horizontal" + android:id="@+id/lights_out" + android:visibility="gone"> + <!-- Must match nav_buttons. --> + </LinearLayout> + +</com.android.systemui.statusbar.car.CarNavigationBarView> diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_view.xml new file mode 100644 index 000000000000..460433ea1c21 --- /dev/null +++ b/packages/SystemUI/res/layout/keyboard_shortcuts_view.xml @@ -0,0 +1,24 @@ +<?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 + --> +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyboard_shortcuts_wrapper" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="40dp" + android:focusable="true"> +</RelativeLayout> diff --git a/packages/SystemUI/res/values/arrays_car.xml b/packages/SystemUI/res/values/arrays_car.xml new file mode 100644 index 000000000000..230479d2c9db --- /dev/null +++ b/packages/SystemUI/res/values/arrays_car.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** 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. +*/ +--> +<resources> + <!-- These should be overriden in an overlay. The default implementation is empty. + There needs to be correspondence per index between these arrays, which means that if there + isn't a longpress action associated with a shortcut item, put in an empty item to make + sure everything lines up. + --> + <array name="car_shortcut_icons" /> + <array name="car_shortcut_intent_uris" /> + <array name="car_shortcut_longpress_intent_uris" /> +</resources> diff --git a/packages/SystemUI/res/values/internal.xml b/packages/SystemUI/res/values/internal.xml index 67685ee09c08..e0d3cd23565f 100644 --- a/packages/SystemUI/res/values/internal.xml +++ b/packages/SystemUI/res/values/internal.xml @@ -17,6 +17,7 @@ <resources> <dimen name="status_bar_height">@*android:dimen/status_bar_height</dimen> <dimen name="navigation_bar_height">@*android:dimen/navigation_bar_height</dimen> + <dimen name="navigation_bar_height_car_mode">@*android:dimen/navigation_bar_height_car_mode</dimen> <color name="screen_pinning_primary_text">@*android:color/primary_text_default_material_light</color> </resources> 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 c95c73bc4f89..ad1ab1470d1c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -145,14 +145,11 @@ public class RecentsView extends FrameLayout { RecentsConfiguration config = Recents.getConfiguration(); RecentsActivityLaunchState launchState = config.getLaunchState(); mStack = stack; - // Disable reusing task stack views until the visibility bug is fixed. b/25998134 - if (false && launchState.launchedReuseTaskStackViews) { + if (launchState.launchedReuseTaskStackViews) { if (mTaskStackView != null) { // If onRecentsHidden is not triggered, we need to the stack view again here mTaskStackView.reset(); mTaskStackView.setStack(stack); - removeView(mTaskStackView); - addView(mTaskStackView); } else { mTaskStackView = new TaskStackView(getContext(), stack); addView(mTaskStackView); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 5906bdac4523..fdfce6cf2592 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar; -import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_MAX; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.TimeInterpolator; @@ -80,11 +78,8 @@ import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.view.animation.AnimationUtils; -import android.widget.DateTimeView; -import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RemoteViews; -import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; @@ -116,6 +111,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_MAX; import static com.android.keyguard.KeyguardHostView.OnDismissAction; public abstract class BaseStatusBar extends SystemUI implements @@ -126,9 +122,6 @@ public abstract class BaseStatusBar extends SystemUI implements public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public static final boolean MULTIUSER_DEBUG = false; - // STOPSHIP disable once we resolve b/18102199 - private static final boolean NOTIFICATION_CLICK_DEBUG = true; - public static final boolean ENABLE_REMOTE_INPUT = SystemProperties.getBoolean("debug.enable_remote_input", true); public static final boolean ENABLE_CHILD_NOTIFICATIONS @@ -141,11 +134,9 @@ public abstract class BaseStatusBar extends SystemUI implements protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024; protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025; - protected static final int MSG_SHOW_KEYBOARD_SHORTCUTS_MENU = 1026; + protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026; protected static final boolean ENABLE_HEADS_UP = true; - // scores above this threshold should be displayed in heads up mode. - protected static final int INTERRUPTION_THRESHOLD = 10; protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; // Should match the values in PhoneWindowManager @@ -200,14 +191,10 @@ public abstract class BaseStatusBar extends SystemUI implements protected IDreamManager mDreamManager; PowerManager mPowerManager; protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - protected int mRowMinHeightLegacy; - protected int mRowMinHeight; - protected int mRowMaxHeight; // public mode, private notifications, etc private boolean mLockscreenPublicMode = false; private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); - private NotificationColorUtil mNotificationColorUtil; private UserManager mUserManager; @@ -237,6 +224,8 @@ public abstract class BaseStatusBar extends SystemUI implements private TimeInterpolator mLinearOutSlowIn, mFastOutLinearIn; + private KeyboardShortcuts mKeyboardShortcuts; + /** * The {@link StatusBarState} of the status bar. */ @@ -357,9 +346,6 @@ public abstract class BaseStatusBar extends SystemUI implements ViewGroup actionGroup = (ViewGroup) parent; index = actionGroup.indexOfChild(view); } - if (NOTIFICATION_CLICK_DEBUG) { - Log.d(TAG, "Clicked on button " + index + " for " + key); - } try { mBarService.onNotificationActionClick(key, index); } catch (RemoteException e) { @@ -617,8 +603,6 @@ public abstract class BaseStatusBar extends SystemUI implements mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService( Context.DEVICE_POLICY_SERVICE); - mNotificationColorUtil = NotificationColorUtil.getInstance(mContext); - mNotificationData = new NotificationData(this); mAccessibilityManager = (AccessibilityManager) @@ -987,6 +971,7 @@ public abstract class BaseStatusBar extends SystemUI implements row.findViewById(R.id.done).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + guts.saveImportance(sbn); dismissPopups(); } }); @@ -1118,8 +1103,8 @@ public abstract class BaseStatusBar extends SystemUI implements } @Override - public void showKeyboardShortcutsMenu() { - int msg = MSG_SHOW_KEYBOARD_SHORTCUTS_MENU; + public void toggleKeyboardShortcutsMenu() { + int msg = MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU; mHandler.removeMessages(msg); mHandler.sendEmptyMessage(msg); } @@ -1142,7 +1127,7 @@ public abstract class BaseStatusBar extends SystemUI implements return new H(); } - static void sendCloseSystemWindows(Context context, String reason) { + protected void sendCloseSystemWindows(String reason) { if (ActivityManagerNative.isSystemReady()) { try { ActivityManagerNative.getDefault().closeSystemDialogs(reason); @@ -1177,7 +1162,7 @@ public abstract class BaseStatusBar extends SystemUI implements protected void showRecents(boolean triggeredFromAltTab) { if (mRecents != null) { - sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS); + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS); mRecents.showRecents(triggeredFromAltTab, getStatusBarView()); } } @@ -1200,8 +1185,8 @@ public abstract class BaseStatusBar extends SystemUI implements } } - protected void showKeyboardShortcuts() { - Toast.makeText(mContext, "Show keyboard shortcuts screen", Toast.LENGTH_LONG).show(); + protected void toggleKeyboardShortcuts() { + getKeyboardShortcuts().toggleKeyboardShortcuts(mContext); } protected void cancelPreloadingRecents() { @@ -1324,8 +1309,8 @@ public abstract class BaseStatusBar extends SystemUI implements case MSG_SHOW_PREV_AFFILIATED_TASK: showRecentsPreviousAffiliatedTask(); break; - case MSG_SHOW_KEYBOARD_SHORTCUTS_MENU: - showKeyboardShortcuts(); + case MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU: + toggleKeyboardShortcuts(); break; } } @@ -1531,6 +1516,14 @@ public abstract class BaseStatusBar extends SystemUI implements } } + protected KeyboardShortcuts getKeyboardShortcuts() { + if (mKeyboardShortcuts == null) { + mKeyboardShortcuts = new KeyboardShortcuts(); + } + + return mKeyboardShortcuts; + } + public void startPendingIntentDismissingKeyguard(final PendingIntent intent) { if (!isDeviceProvisioned()) return; @@ -1613,9 +1606,6 @@ public abstract class BaseStatusBar extends SystemUI implements } }); - if (NOTIFICATION_CLICK_DEBUG) { - Log.d(TAG, "Clicked on content of " + notificationKey); - } final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); final boolean afterKeyguardGone = intent.isActivity() && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index deedae0f6a80..5a2758d89de8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -65,7 +65,7 @@ public class CommandQueue extends IStatusBar.Stub { private static final int MSG_ASSIST_DISCLOSURE = 22 << MSG_SHIFT; private static final int MSG_START_ASSIST = 23 << MSG_SHIFT; private static final int MSG_CAMERA_LAUNCH_GESTURE = 24 << MSG_SHIFT; - private static final int MSG_SHOW_KEYBOARD_SHORTCUTS = 25 << MSG_SHIFT; + private static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS = 25 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -100,7 +100,7 @@ public class CommandQueue extends IStatusBar.Stub { public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey); public void toggleRecentApps(); public void preloadRecentApps(); - public void showKeyboardShortcutsMenu(); + public void toggleKeyboardShortcutsMenu(); public void cancelPreloadRecentApps(); public void setWindowState(int window, int state); public void buzzBeepBlinked(); @@ -229,10 +229,10 @@ public class CommandQueue extends IStatusBar.Stub { } @Override - public void showKeyboardShortcutsMenu() { + public void toggleKeyboardShortcutsMenu() { synchronized (mList) { - mHandler.removeMessages(MSG_SHOW_KEYBOARD_SHORTCUTS); - mHandler.obtainMessage(MSG_SHOW_KEYBOARD_SHORTCUTS).sendToTarget(); + mHandler.removeMessages(MSG_TOGGLE_KEYBOARD_SHORTCUTS); + mHandler.obtainMessage(MSG_TOGGLE_KEYBOARD_SHORTCUTS).sendToTarget(); } } @@ -380,8 +380,8 @@ public class CommandQueue extends IStatusBar.Stub { case MSG_CANCEL_PRELOAD_RECENT_APPS: mCallbacks.cancelPreloadRecentApps(); break; - case MSG_SHOW_KEYBOARD_SHORTCUTS: - mCallbacks.showKeyboardShortcutsMenu(); + case MSG_TOGGLE_KEYBOARD_SHORTCUTS: + mCallbacks.toggleKeyboardShortcutsMenu(); break; case MSG_SET_WINDOW_STATE: mCallbacks.setWindowState(msg.arg1, msg.arg2); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java new file mode 100644 index 000000000000..3e0ea90f7694 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.graphics.drawable.ColorDrawable; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +import com.android.systemui.R; + +/** + * Contains functionality for handling keyboard shortcuts. + */ +public class KeyboardShortcuts { + private Dialog mKeyboardShortcutsDialog; + + public KeyboardShortcuts() {} + + public void toggleKeyboardShortcuts(Context context) { + if (mKeyboardShortcutsDialog == null) { + // Create dialog. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); + LayoutInflater inflater = (LayoutInflater) context.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + final View keyboardShortcutsView = inflater.inflate( + R.layout.keyboard_shortcuts_view, null); + + populateKeyboardShortcuts(keyboardShortcutsView.findViewById( + R.id.keyboard_shortcuts_wrapper)); + dialogBuilder.setView(keyboardShortcutsView); + mKeyboardShortcutsDialog = dialogBuilder.create(); + mKeyboardShortcutsDialog.setCanceledOnTouchOutside(true); + + // Setup window. + Window keyboardShortcutsWindow = mKeyboardShortcutsDialog.getWindow(); + keyboardShortcutsWindow.setType( + WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); + keyboardShortcutsWindow.setBackgroundDrawable( + new ColorDrawable(android.graphics.Color.TRANSPARENT)); + keyboardShortcutsWindow.setGravity(Gravity.TOP); + mKeyboardShortcutsDialog.show(); + } else { + dismissKeyboardShortcutsDialog(); + } + } + + public void dismissKeyboardShortcutsDialog() { + if (mKeyboardShortcutsDialog != null) { + mKeyboardShortcutsDialog.dismiss(); + mKeyboardShortcutsDialog = null; + } + } + + /** + * @return {@code true} if the keyboard shortcuts have been successfully populated. + */ + private boolean populateKeyboardShortcuts(View keyboardShortcutsLayout) { + // TODO: Populate shortcuts. + return true; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java index 6850f7ecf122..20a6e7c090a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java @@ -46,6 +46,10 @@ public class NotificationGuts extends LinearLayout { private int mClipTopAmount; private int mActualHeight; private boolean mExposed; + private RadioButton mApplyToTopic; + private SeekBar mSeekBar; + private Notification.Topic mTopic; + private INotificationManager mINotificationManager; public NotificationGuts(Context context, AttributeSet attrs) { super(context, attrs); @@ -98,67 +102,39 @@ public class NotificationGuts extends LinearLayout { void bindImportance(final StatusBarNotification sbn, final ExpandableNotificationRow row, final int importance) { - final INotificationManager sINM = INotificationManager.Stub.asInterface( + mINotificationManager = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)); - final Notification.Topic topic = sbn.getNotification().getTopic() == null + mTopic = sbn.getNotification().getTopic() == null ? new Notification.Topic(Notification.TOPIC_DEFAULT, mContext.getString( com.android.internal.R.string.default_notification_topic_label)) : sbn.getNotification().getTopic(); boolean doesAppUseTopics = false; try { - doesAppUseTopics = sINM.doesAppUseTopics(sbn.getPackageName(), sbn.getUid()); + doesAppUseTopics = + mINotificationManager.doesAppUseTopics(sbn.getPackageName(), sbn.getUid()); } catch (RemoteException e) {} final boolean appUsesTopics = doesAppUseTopics; - final RadioButton applyToTopic = (RadioButton) row.findViewById(R.id.apply_to_topic); - applyToTopic.setChecked(true); + mApplyToTopic = (RadioButton) row.findViewById(R.id.apply_to_topic); + if (appUsesTopics) { + mApplyToTopic.setChecked(true); + } final View applyToApp = row.findViewById(R.id.apply_to_app); final TextView topicSummary = ((TextView) row.findViewById(R.id.summary)); final TextView topicTitle = ((TextView) row.findViewById(R.id.title)); - final SeekBar seekBar = (SeekBar) row.findViewById(R.id.seekbar); - final RadioGroup applyTo = (RadioGroup) row.findViewById(R.id.apply_to); - applyTo.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) { - try { - switch (checkedId) { - case R.id.apply_to_topic: - sINM.setTopicImportance(sbn.getPackageName(), sbn.getUid(), topic, - seekBar.getProgress()); - break; - default: - sINM.setAppImportance(sbn.getPackageName(), sbn.getUid(), - seekBar.getProgress()); - } - } catch (RemoteException e) { - // :( - } - } - }); - - seekBar.setMax(4); - seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + mSeekBar = (SeekBar) row.findViewById(R.id.seekbar); + mSeekBar.setMax(4); + mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { updateTitleAndSummary(progress); if (fromUser) { if (appUsesTopics) { - applyToTopic.setVisibility(View.VISIBLE); - - applyToTopic.setText( - mContext.getString(R.string.apply_to_topic, topic.getLabel())); + mApplyToTopic.setVisibility(View.VISIBLE); + mApplyToTopic.setText( + mContext.getString(R.string.apply_to_topic, mTopic.getLabel())); applyToApp.setVisibility(View.VISIBLE); } - try { - if (applyToTopic.isChecked()) { - sINM.setTopicImportance(sbn.getPackageName(), sbn.getUid(), topic, - progress); - } else { - sINM.setAppImportance(sbn.getPackageName(), sbn.getUid(), progress); - } - } catch (RemoteException e) { - // :( - } } } @@ -202,7 +178,22 @@ public class NotificationGuts extends LinearLayout { } } }); - seekBar.setProgress(importance); + mSeekBar.setProgress(importance); + } + + void saveImportance(final StatusBarNotification sbn) { + int progress = mSeekBar.getProgress(); + try { + if (mApplyToTopic.isChecked()) { + mINotificationManager.setTopicImportance(sbn.getPackageName(), sbn.getUid(), mTopic, + progress); + } else { + mINotificationManager.setAppImportance( + sbn.getPackageName(), sbn.getUid(), progress); + } + } catch (RemoteException e) { + // :( + } } public void setActualHeight(int actualHeight) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java index f243b00143a2..d7e47c27dc71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java @@ -30,11 +30,11 @@ import java.util.ArrayList; public class RemoteInputController { private final ArrayList<WeakReference<NotificationData.Entry>> mRemoteInputs = new ArrayList<>(); - private final StatusBarWindowManager mStatusBarWindowManager; + private final ArrayList<Callback> mCallbacks = new ArrayList<>(3); private final HeadsUpManager mHeadsUpManager; public RemoteInputController(StatusBarWindowManager sbwm, HeadsUpManager headsUpManager) { - mStatusBarWindowManager = sbwm; + addCallback(sbwm); mHeadsUpManager = headsUpManager; } @@ -59,8 +59,12 @@ public class RemoteInputController { } private void apply(NotificationData.Entry entry) { - mStatusBarWindowManager.setRemoteInputActive(isRemoteInputActive()); mHeadsUpManager.setRemoteInputActive(entry, isRemoteInputActive(entry)); + boolean remoteInputActive = isRemoteInputActive(); + int N = mCallbacks.size(); + for (int i = 0; i < N; i++) { + mCallbacks.get(i).onRemoteInputActive(remoteInputActive); + } } /** @@ -99,4 +103,12 @@ public class RemoteInputController { } + public void addCallback(Callback callback) { + Preconditions.checkNotNull(callback); + mCallbacks.add(callback); + } + + public interface Callback { + void onRemoteInputActive(boolean active); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java new file mode 100644 index 000000000000..5c0f38c00805 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java @@ -0,0 +1,152 @@ +/* + * 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 com.android.systemui.statusbar.car; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.R.color; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.ImageButton; +import android.widget.ImageView.ScaleType; +import android.widget.LinearLayout; + +import com.android.systemui.R; +import com.android.systemui.statusbar.phone.ActivityStarter; +import com.android.systemui.statusbar.phone.NavigationBarView; +import com.android.systemui.statusbar.phone.NavigationBarGestureHelper; +import com.android.systemui.statusbar.policy.KeyButtonView; + +import java.net.URISyntaxException; + +/** + * A custom navigation bar for the automotive use case. + * <p> + * The navigation bar in the automotive use case is more like a list of shortcuts, which we + * expect to be customizable by the car OEMs. This implementation populates the nav_buttons layout + * from resources rather than the layout file so customization would then mean updating + * arrays_car.xml appropriately in an overlay. + */ +class CarNavigationBarView extends NavigationBarView { + private ActivityStarter mActivityStarter; + + public CarNavigationBarView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void onFinishInflate() { + // Read up arrays_car.xml and populate the navigation bar here. + Context context = getContext(); + Resources r = getContext().getResources(); + TypedArray icons = r.obtainTypedArray(R.array.car_shortcut_icons); + TypedArray intents = r.obtainTypedArray(R.array.car_shortcut_intent_uris); + TypedArray longpressIntents = + r.obtainTypedArray(R.array.car_shortcut_longpress_intent_uris); + + if (icons.length() != intents.length()) { + throw new RuntimeException("car_shortcut_icons and car_shortcut_intents do not match"); + } + + LinearLayout navButtons = (LinearLayout) findViewById(R.id.nav_buttons); + LinearLayout lightsOut = (LinearLayout) findViewById(R.id.lights_out); + + for (int i = 0; i < icons.length(); i++) { + Drawable icon = icons.getDrawable(i); + + try { + Intent intent = Intent.parseUri(intents.getString(i), Intent.URI_INTENT_SCHEME); + Intent longpress = null; + String longpressUri = longpressIntents.getString(i); + if (!longpressUri.isEmpty()) { + longpress = Intent.parseUri(longpressUri, Intent.URI_INTENT_SCHEME); + } + + // nav_buttons and lights_out should match exactly. + navButtons.addView(makeButton(context, icon, intent, longpress)); + lightsOut.addView(makeButton(context, icon, intent, longpress)); + } catch (URISyntaxException e) { + throw new RuntimeException("Malformed intent uri", e); + } + } + } + + private ImageButton makeButton(Context context, Drawable icon, + final Intent intent, final Intent longpress) { + ImageButton button = new ImageButton(context); + + button.setImageDrawable(icon); + button.setScaleType(ScaleType.CENTER); + button.setBackgroundColor(color.transparent); + LinearLayout.LayoutParams lp = + new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1); + button.setLayoutParams(lp); + + button.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mActivityStarter != null) { + mActivityStarter.startActivity(intent, true); + } + } + }); + + // Long click handlers are optional. + if (longpress != null) { + button.setLongClickable(true); + button.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (mActivityStarter != null) { + mActivityStarter.startActivity(longpress, true); + return true; + } + return false; + } + }); + } else { + button.setLongClickable(false); + } + + return button; + } + + public void setActivityStarter(ActivityStarter activityStarter) { + mActivityStarter = activityStarter; + } + + @Override + public void setDisabledFlags(int disabledFlags, boolean force) { + // TODO: Populate. + } + + @Override + public void reorient() { + // We expect the car head unit to always have a fixed rotation so we ignore this. The super + // class implentation expects mRotatedViews to be populated, so if you call into it, there + // is a possibility of a NullPointerException. + } + + @Override + public View getCurrentView() { + return this; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java new file mode 100644 index 000000000000..a72b5d0dd8f2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -0,0 +1,60 @@ +/* + * 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 com.android.systemui.statusbar.car; + +import android.content.Context; +import android.graphics.PixelFormat; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.WindowManager; + +import com.android.systemui.R; +import com.android.systemui.statusbar.phone.PhoneStatusBar; + +/** + * A status bar (and navigation bar) tailored for the automotive use case. + */ +public class CarStatusBar extends PhoneStatusBar { + @Override + protected void addNavigationBar() { + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, + WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + PixelFormat.TRANSLUCENT); + lp.setTitle("CarNavigationBar"); + lp.windowAnimations = 0; + mWindowManager.addView(mNavigationBarView, lp); + } + + @Override + protected void createNavigationBarView(Context context) { + if (mNavigationBarView != null) { + return; + } + + CarNavigationBarView carNavBar = + (CarNavigationBarView) View.inflate(context, R.layout.car_navigation_bar, null); + carNavBar.setActivityStarter(this); + mNavigationBarView = carNavBar; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index a3d0ce69bdd6..55c7cb7b2909 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -69,7 +69,6 @@ public class NavigationBarView extends LinearLayout { View mCurrentView = null; View[] mRotatedViews = new View[4]; - int mBarSize; boolean mVertical; boolean mScreenOn; @@ -78,6 +77,9 @@ public class NavigationBarView extends LinearLayout { int mNavigationIconHints = 0; private Drawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon; + private Drawable mBackCarModeIcon, mBackLandCarModeIcon; + private Drawable mBackAltCarModeIcon, mBackAltLandCarModeIcon; + private Drawable mHomeDefaultIcon, mHomeCarModeIcon; private Drawable mRecentIcon; private Drawable mRecentLandIcon; @@ -96,6 +98,7 @@ public class NavigationBarView extends LinearLayout { private boolean mIsLayoutRtl; private boolean mLayoutTransitionsEnabled = true; private boolean mWakeAndUnlocking; + private boolean mCarMode = false; private class NavTransitionListener implements TransitionListener { private boolean mBackTransitioning; @@ -157,8 +160,8 @@ public class NavigationBarView extends LinearLayout { final String how = "" + m.obj; final int w = getWidth(); final int h = getHeight(); - final int vw = mCurrentView.getWidth(); - final int vh = mCurrentView.getHeight(); + final int vw = getCurrentView().getWidth(); + final int vh = getCurrentView().getHeight(); if (h != vh || w != vw) { Log.w(TAG, String.format( @@ -176,16 +179,15 @@ public class NavigationBarView extends LinearLayout { public NavigationBarView(Context context, AttributeSet attrs) { super(context, attrs); - mDisplay = ((WindowManager)context.getSystemService( + mDisplay = ((WindowManager) context.getSystemService( Context.WINDOW_SERVICE)).getDefaultDisplay(); final Resources res = getContext().getResources(); - mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size); mVertical = false; mShowMenu = false; mGestureHelper = new NavigationBarGestureHelper(context); - getIcons(res); + getIcons(context); mBarTransitions = new NavigationBarTransitions(this); } @@ -230,41 +232,53 @@ public class NavigationBarView extends LinearLayout { } public KeyButtonView getRecentsButton() { - return (KeyButtonView) mCurrentView.findViewById(R.id.recent_apps); + return (KeyButtonView) getCurrentView().findViewById(R.id.recent_apps); } public View getMenuButton() { - return mCurrentView.findViewById(R.id.menu); + return getCurrentView().findViewById(R.id.menu); } public View getBackButton() { - return mCurrentView.findViewById(R.id.back); + return getCurrentView().findViewById(R.id.back); } public KeyButtonView getHomeButton() { - return (KeyButtonView) mCurrentView.findViewById(R.id.home); + return (KeyButtonView) getCurrentView().findViewById(R.id.home); } public View getImeSwitchButton() { - return mCurrentView.findViewById(R.id.ime_switcher); + return getCurrentView().findViewById(R.id.ime_switcher); } public View getAppShelf() { - return mCurrentView.findViewById(R.id.app_shelf); + return getCurrentView().findViewById(R.id.app_shelf); } - private void getIcons(Resources res) { - mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back); + private void getCarModeIcons(Context ctx) { + mBackCarModeIcon = ctx.getDrawable(R.drawable.ic_sysbar_back_carmode); + mBackLandCarModeIcon = mBackCarModeIcon; + mBackAltCarModeIcon = ctx.getDrawable(R.drawable.ic_sysbar_back_ime_carmode); + mBackAltLandCarModeIcon = mBackAltCarModeIcon; + mHomeCarModeIcon = ctx.getDrawable(R.drawable.ic_sysbar_home_carmode); + } + + private void getIcons(Context ctx) { + mBackIcon = ctx.getDrawable(R.drawable.ic_sysbar_back); mBackLandIcon = mBackIcon; - mBackAltIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime); + mBackAltIcon = ctx.getDrawable(R.drawable.ic_sysbar_back_ime); mBackAltLandIcon = mBackAltIcon; - mRecentIcon = res.getDrawable(R.drawable.ic_sysbar_recent); + + mHomeDefaultIcon = ctx.getDrawable(R.drawable.ic_sysbar_home); + + mRecentIcon = ctx.getDrawable(R.drawable.ic_sysbar_recent); mRecentLandIcon = mRecentIcon; + getCarModeIcons(ctx); } @Override public void setLayoutDirection(int layoutDirection) { - getIcons(getContext().getResources()); + getIcons(getContext()); super.setLayoutDirection(layoutDirection); } @@ -278,6 +292,18 @@ public class NavigationBarView extends LinearLayout { setNavigationIconHints(hints, false); } + private Drawable getBackIconWithAlt(boolean carMode, boolean landscape) { + return landscape + ? carMode ? mBackAltLandCarModeIcon : mBackAltLandIcon + : carMode ? mBackAltCarModeIcon : mBackAltIcon; + } + + private Drawable getBackIcon(boolean carMode, boolean landscape) { + return landscape + ? carMode ? mBackLandCarModeIcon : mBackLandIcon + : carMode ? mBackCarModeIcon : mBackIcon; + } + public void setNavigationIconHints(int hints, boolean force) { if (!force && hints == mNavigationIconHints) return; final boolean backAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; @@ -292,11 +318,23 @@ public class NavigationBarView extends LinearLayout { mNavigationIconHints = hints; - ((ImageView)getBackButton()).setImageDrawable(backAlt - ? (mVertical ? mBackAltLandIcon : mBackAltIcon) - : (mVertical ? mBackLandIcon : mBackIcon)); + // We have to replace or restore the back and home button icons when exiting or entering + // carmode, respectively. Recents are not available in CarMode in nav bar so change + // to recent icon is not required. + Drawable backIcon = (backAlt) + ? getBackIconWithAlt(mCarMode, mVertical) + : getBackIcon(mCarMode, mVertical); - ((ImageView)getRecentsButton()).setImageDrawable(mVertical ? mRecentLandIcon : mRecentIcon); + ((ImageView) getBackButton()).setImageDrawable(backIcon); + + ((ImageView) getRecentsButton()).setImageDrawable( + mVertical ? mRecentLandIcon : mRecentIcon); + + if (mCarMode) { + ((ImageView) getHomeButton()).setImageDrawable(mHomeCarModeIcon); + } else { + ((ImageView) getHomeButton()).setImageDrawable(mHomeDefaultIcon); + } final boolean showImeButton = ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0); getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE); @@ -326,7 +364,7 @@ public class NavigationBarView extends LinearLayout { setSlippery(disableHome && disableRecent && disableBack && disableSearch); } - ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons); + ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons); if (navButtons != null) { LayoutTransition lt = navButtons.getLayoutTransition(); if (lt != null) { @@ -379,7 +417,7 @@ public class NavigationBarView extends LinearLayout { private void updateLayoutTransitionsEnabled() { boolean enabled = !mWakeAndUnlocking && mLayoutTransitionsEnabled; - ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons); + ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons); LayoutTransition lt = navButtons.getLayoutTransition(); if (lt != null) { if (enabled) { @@ -546,8 +584,33 @@ public class NavigationBarView extends LinearLayout { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + boolean uiCarModeChanged = updateCarMode(newConfig); updateRTLOrder(); updateTaskSwitchHelper(); + if (uiCarModeChanged) { + // uiMode changed either from carmode or to carmode. + // replace the nav bar button icons based on which mode + // we are switching to. + setNavigationIconHints(mNavigationIconHints, true); + } + } + + /** + * If the configuration changed, update the carmode and return that it was updated. + */ + private boolean updateCarMode(Configuration newConfig) { + boolean uiCarModeChanged = false; + if (newConfig != null) { + int uiMode = newConfig.uiMode & Configuration.UI_MODE_TYPE_MASK; + if (mCarMode && uiMode != Configuration.UI_MODE_TYPE_CAR) { + mCarMode = false; + uiCarModeChanged = true; + } else if (uiMode == Configuration.UI_MODE_TYPE_CAR) { + mCarMode = true; + uiCarModeChanged = true; + } + } + return uiCarModeChanged; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 42fd8722931d..ba20679980d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -1318,7 +1318,6 @@ public class NotificationPanelView extends PanelView implements mHeader.setExpansion(getHeaderExpansionFraction()); setQsTranslation(height); requestScrollerTopPaddingUpdate(false /* animate */); - updateNotificationScrim(height); if (mKeyguardShowing) { updateHeaderKeyguard(); } @@ -1355,12 +1354,6 @@ public class NotificationPanelView extends PanelView implements } } - private void updateNotificationScrim(float height) { - int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance; - float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance); - progress = Math.max(0.0f, Math.min(progress, 1.0f)); - } - private float getHeaderExpansionFraction() { if (!mKeyguardShowing) { return getQsExpansionFraction(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java index e1a400d43638..6aa072ff2c81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -71,7 +71,6 @@ public abstract class PanelBar extends FrameLayout { Log.e(TAG, "setPanelHolder: null PanelHolder", new Throwable()); return; } - ph.setBar(this); mPanelHolder = ph; final int N = ph.getChildCount(); for (int i=0; i<N; i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java index d7f34d5b1396..5095ebb95ec1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java @@ -28,7 +28,6 @@ public class PanelHolder extends FrameLayout { public static final boolean DEBUG_GESTURES = true; private int mSelectedPanelIndex = -1; - private PanelBar mBar; public PanelHolder(Context context, AttributeSet attrs) { super(context, attrs); @@ -79,8 +78,4 @@ public class PanelHolder extends FrameLayout { } return false; } - - public void setBar(PanelBar panelBar) { - mBar = panelBar; - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 5e54ba7d9ce4..7b2498f2ac72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -58,7 +58,6 @@ public abstract class PanelView extends FrameLayout { private float mPeekHeight; private float mHintDistance; - private int mEdgeTapAreaWidth; private float mInitialOffsetOnTouch; private boolean mCollapsedAndHeadsUpOnDown; private float mExpandedFraction = 0; @@ -202,7 +201,6 @@ public abstract class PanelView extends FrameLayout { final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledTouchSlop(); mHintDistance = res.getDimension(R.dimen.hint_move_distance); - mEdgeTapAreaWidth = res.getDimensionPixelSize(R.dimen.edge_tap_area_width); mUnlockFalsingThreshold = res.getDimensionPixelSize(R.dimen.unlock_falsing_threshold); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 78d09e3e03a4..d68825053e38 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -728,32 +728,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, boolean showNav = mWindowManagerService.hasNavigationBar(); if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav); if (showNav) { - // Optionally show app shortcuts in the nav bar "shelf" area. - if (shouldShowAppShelf()) { - mNavigationBarView = (NavigationBarView) View.inflate( - context, R.layout.navigation_bar_with_apps, null); - } else { - mNavigationBarView = (NavigationBarView) View.inflate( - context, R.layout.navigation_bar, null); - } - mNavigationBarView.setDisabledFlags(mDisabled1); - mNavigationBarView.setComponents(mRecents, getComponent(Divider.class)); - mNavigationBarView.setOnVerticalChangedListener( - new NavigationBarView.OnVerticalChangedListener() { - @Override - public void onVerticalChanged(boolean isVertical) { - if (mAssistManager != null) { - mAssistManager.onConfigurationChanged(); - } - mNotificationPanel.setQsScrimEnabled(!isVertical); - } - }); - mNavigationBarView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - checkUserAutohide(v, event); - return false; - }}); + createNavigationBarView(context); } } catch (RemoteException ex) { // no window manager? good luck with that @@ -979,6 +954,35 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return mStatusBarView; } + protected void createNavigationBarView(Context context) { + // Optionally show app shortcuts in the nav bar "shelf" area. + if (shouldShowAppShelf()) { + mNavigationBarView = (NavigationBarView) View.inflate( + context, R.layout.navigation_bar_with_apps, null); + } else { + mNavigationBarView = (NavigationBarView) View.inflate( + context, R.layout.navigation_bar, null); + } + mNavigationBarView.setDisabledFlags(mDisabled1); + mNavigationBarView.setComponents(mRecents, getComponent(Divider.class)); + mNavigationBarView.setOnVerticalChangedListener( + new NavigationBarView.OnVerticalChangedListener() { + @Override + public void onVerticalChanged(boolean isVertical) { + if (mAssistManager != null) { + mAssistManager.onConfigurationChanged(); + } + mNotificationPanel.setQsScrimEnabled(!isVertical); + } + }); + mNavigationBarView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + checkUserAutohide(v, event); + return false; + }}); + } + /** Returns true if the app shelf should be shown in the nav bar. */ private boolean shouldShowAppShelf() { // Allow adb to override the default shelf behavior: @@ -1086,6 +1090,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mKeyguardIndicationController.setStatusBarKeyguardViewManager( mStatusBarKeyguardViewManager); mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); + mRemoteInputController.addCallback(mStatusBarKeyguardViewManager); mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback(); } @@ -1191,7 +1196,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } // For small-screen devices (read: phones) that lack hardware navigation buttons - private void addNavigationBar() { + protected void addNavigationBar() { if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mNavigationBarView); if (mNavigationBarView == null) return; @@ -2987,6 +2992,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, if (DEBUG) Log.v(TAG, "onReceive: " + intent); String action = intent.getAction(); if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { + getKeyboardShortcuts().dismissKeyboardShortcutsDialog(); if (isCurrentProfile(getSendingUserId())) { int flags = CommandQueue.FLAG_EXCLUDE_NONE; String reason = intent.getStringExtra("reason"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index c0887ca78b25..ab37e6abc32b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -51,7 +51,6 @@ public class PhoneStatusBarView extends PanelBar { public PhoneStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); - Resources res = getContext().getResources(); mBarTransitions = new PhoneStatusBarTransitions(this); } @@ -102,7 +101,6 @@ public class PhoneStatusBarView extends PanelBar { @Override public PanelView selectPanelForTouch(MotionEvent touch) { - // No double swiping. If either panel is open, nothing else can be pulled down. return mNotificationPanel.getExpandedHeight() > 0 ? null : mNotificationPanel; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 05f6e57d9199..f14f0d4c28a4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -31,6 +31,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.RemoteInputController; import static com.android.keyguard.KeyguardHostView.OnDismissAction; @@ -40,7 +41,7 @@ import static com.android.keyguard.KeyguardHostView.OnDismissAction; * which is in turn, reported to this class by the current * {@link com.android.keyguard.KeyguardViewBase}. */ -public class StatusBarKeyguardViewManager { +public class StatusBarKeyguardViewManager implements RemoteInputController.Callback { // When hiding the Keyguard with timing supplied from WindowManager, better be early than late. private static final long HIDE_TIMING_CORRECTION_MS = -3 * 16; @@ -69,12 +70,15 @@ public class StatusBarKeyguardViewManager { private KeyguardBouncer mBouncer; private boolean mShowing; private boolean mOccluded; + private boolean mRemoteInputActive; private boolean mFirstUpdate = true; private boolean mLastShowing; private boolean mLastOccluded; private boolean mLastBouncerShowing; private boolean mLastBouncerDismissible; + private boolean mLastRemoteInputActive; + private OnDismissAction mAfterKeyguardGoneAction; private boolean mDeviceWillWakeUp; private boolean mDeferScrimFadeOut; @@ -199,6 +203,12 @@ public class StatusBarKeyguardViewManager { mPhoneStatusBar.onScreenTurnedOn(); } + @Override + public void onRemoteInputActive(boolean active) { + mRemoteInputActive = active; + updateStates(); + } + public void onScreenTurnedOff() { mScreenTurnedOn = false; } @@ -429,18 +439,21 @@ public class StatusBarKeyguardViewManager { boolean occluded = mOccluded; boolean bouncerShowing = mBouncer.isShowing(); boolean bouncerDismissible = !mBouncer.isFullscreenBouncer(); + boolean remoteInputActive = mRemoteInputActive; - if ((bouncerDismissible || !showing) != (mLastBouncerDismissible || !mLastShowing) + if ((bouncerDismissible || !showing || remoteInputActive) != + (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive) || mFirstUpdate) { - if (bouncerDismissible || !showing) { + if (bouncerDismissible || !showing || remoteInputActive) { mContainer.setSystemUiVisibility(vis & ~View.STATUS_BAR_DISABLE_BACK); } else { mContainer.setSystemUiVisibility(vis | View.STATUS_BAR_DISABLE_BACK); } } - boolean navBarVisible = (!(showing && !occluded) || bouncerShowing); - boolean lastNavBarVisible = (!(mLastShowing && !mLastOccluded) || mLastBouncerShowing); + boolean navBarVisible = (!(showing && !occluded) || bouncerShowing || remoteInputActive); + boolean lastNavBarVisible = (!(mLastShowing && !mLastOccluded) || mLastBouncerShowing + || mLastRemoteInputActive); if (navBarVisible != lastNavBarVisible || mFirstUpdate) { if (mPhoneStatusBar.getNavigationBarView() != null) { if (navBarVisible) { @@ -477,6 +490,7 @@ public class StatusBarKeyguardViewManager { mLastOccluded = occluded; mLastBouncerShowing = bouncerShowing; mLastBouncerDismissible = bouncerDismissible; + mLastRemoteInputActive = remoteInputActive; mPhoneStatusBar.onKeyguardViewManagerStatesUpdated(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java index abe51ac3a5d0..9d2f0de4c1e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java @@ -30,6 +30,7 @@ import android.view.WindowManager; import com.android.keyguard.R; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.statusbar.BaseStatusBar; +import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import java.io.FileDescriptor; @@ -39,7 +40,7 @@ import java.lang.reflect.Field; /** * Encapsulates all logic for the status bar window state management. */ -public class StatusBarWindowManager { +public class StatusBarWindowManager implements RemoteInputController.Callback { private final Context mContext; private final WindowManager mWindowManager; @@ -292,7 +293,8 @@ public class StatusBarWindowManager { apply(mCurrentState); } - public void setRemoteInputActive(boolean remoteInputActive) { + @Override + public void onRemoteInputActive(boolean remoteInputActive) { mCurrentState.remoteInputActive = remoteInputActive; apply(mCurrentState); } diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 04abccace0a7..15b55026ae66 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -2383,10 +2383,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return; } synchronized (mMethodMap) { - if (mCurClient == null || client == null - || mCurClient.client.asBinder() != client.asBinder()) { - Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client); - } executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); } @@ -2716,9 +2712,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return true; case MSG_SHOW_IM_SUBTYPE_ENABLER: - args = (SomeArgs)msg.obj; - showInputMethodAndSubtypeEnabler((String)args.arg1); - args.recycle(); + showInputMethodAndSubtypeEnabler((String)msg.obj); return true; case MSG_SHOW_IM_CONFIG: @@ -3005,7 +2999,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!TextUtils.isEmpty(inputMethodId)) { intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId); } - mContext.startActivityAsUser(intent, null, UserHandle.CURRENT); + final int userId; + synchronized (mMethodMap) { + userId = mSettings.getCurrentUserId(); + } + mContext.startActivityAsUser(intent, null, UserHandle.of(userId)); } private void showConfigureInputMethods() { diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 4348913a2885..9dda32136576 100755 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1244,7 +1244,7 @@ public final class ActiveServices { sInfo.applicationInfo.uid, sInfo.packageName, callingPid); if (allowed != ActivityManager.APP_START_MODE_NORMAL) { Slog.w(TAG, "Background execution not allowed: service " - + r.intent + " to " + name.flattenToShortString() + + service + " to " + name.flattenToShortString() + " from pid=" + callingPid + " uid=" + callingUid + " pkg=" + callingPackage); return null; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index fb62a95516c6..91706f833a2f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -17839,6 +17839,11 @@ public final class ActivityManagerService extends ActivityManagerNative "Unable to find instrumentation target package: " + ii.targetPackage); return false; } + if (!ai.hasCode()) { + reportStartInstrumentationFailure(watcher, className, + "Instrumentation target has no code: " + ii.targetPackage); + return false; + } int match = mContext.getPackageManager().checkSignatures( ii.targetPackage, ii.packageName); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 8d9cb5897bd6..e123dbd3d84a 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -21,8 +21,13 @@ import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.HOME_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; +import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; +import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; +import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; +import static android.content.res.Configuration.SCREENLAYOUT_UNDEFINED; import static com.android.server.am.ActivityManagerDebugConfig.*; import static com.android.server.am.ActivityManagerService.LOCK_SCREEN_SHOWN; import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; @@ -4224,20 +4229,20 @@ final class ActivityStack { int taskChanges = oldTaskOverride.diff(taskConfig); // We don't want to use size changes if they don't cross boundaries that are important to // the app. - if ((taskChanges & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) { + if ((taskChanges & CONFIG_SCREEN_SIZE) != 0) { final boolean crosses = record.crossesHorizontalSizeThreshold( oldTaskOverride.screenWidthDp, taskConfig.screenWidthDp) || record.crossesVerticalSizeThreshold( oldTaskOverride.screenHeightDp, taskConfig.screenHeightDp); if (!crosses) { - taskChanges &= ~ActivityInfo.CONFIG_SCREEN_SIZE; + taskChanges &= ~CONFIG_SCREEN_SIZE; } } - if ((taskChanges & ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE) != 0) { + if ((taskChanges & CONFIG_SMALLEST_SCREEN_SIZE) != 0) { final int oldSmallest = oldTaskOverride.smallestScreenWidthDp; final int newSmallest = taskConfig.smallestScreenWidthDp; if (!record.crossesSmallestSizeThreshold(oldSmallest, newSmallest)) { - taskChanges &= ~ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; + taskChanges &= ~CONFIG_SMALLEST_SCREEN_SIZE; } } return catchConfigChangesFromUnset(taskConfig, oldTaskOverride, taskChanges); @@ -4249,7 +4254,7 @@ final class ActivityStack { // {@link Configuration#diff} doesn't catch changes from unset values. // Check for changes we care about. if (oldTaskOverride.orientation != taskConfig.orientation) { - taskChanges |= ActivityInfo.CONFIG_ORIENTATION; + taskChanges |= CONFIG_ORIENTATION; } // We want to explicitly track situations where the size configuration goes from // undefined to defined. We don't care about crossing the threshold in that case, @@ -4259,29 +4264,35 @@ final class ActivityStack { final int undefinedHeight = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; if ((oldHeight == undefinedHeight && newHeight != undefinedHeight) || (oldHeight != undefinedHeight && newHeight == undefinedHeight)) { - taskChanges |= ActivityInfo.CONFIG_SCREEN_SIZE; + taskChanges |= CONFIG_SCREEN_SIZE; } final int oldWidth = oldTaskOverride.screenWidthDp; final int newWidth = taskConfig.screenWidthDp; final int undefinedWidth = Configuration.SCREEN_WIDTH_DP_UNDEFINED; if ((oldWidth == undefinedWidth && newWidth != undefinedWidth) || (oldWidth != undefinedWidth && newWidth == undefinedWidth)) { - taskChanges |= ActivityInfo.CONFIG_SCREEN_SIZE; + taskChanges |= CONFIG_SCREEN_SIZE; } final int oldSmallest = oldTaskOverride.smallestScreenWidthDp; final int newSmallest = taskConfig.smallestScreenWidthDp; final int undefinedSmallest = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; if ((oldSmallest == undefinedSmallest && newSmallest != undefinedSmallest) || (oldSmallest != undefinedSmallest && newSmallest == undefinedSmallest)) { - taskChanges |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; + taskChanges |= CONFIG_SMALLEST_SCREEN_SIZE; + } + final int oldLayout = oldTaskOverride.screenLayout; + final int newLayout = taskConfig.screenLayout; + if ((oldLayout == SCREENLAYOUT_UNDEFINED && newLayout != SCREENLAYOUT_UNDEFINED) + || (oldLayout != SCREENLAYOUT_UNDEFINED && newLayout == SCREENLAYOUT_UNDEFINED)) { + taskChanges |= CONFIG_SCREEN_LAYOUT; } } return taskChanges; } private static boolean isResizeOnlyChange(int change) { - return (change & ~(ActivityInfo.CONFIG_SCREEN_SIZE - | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) == 0; + return (change & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_ORIENTATION + | CONFIG_SCREEN_LAYOUT)) == 0; } private void relaunchActivityLocked( @@ -4595,7 +4606,7 @@ final class ActivityStack { a.forceNewConfig = true; if (starting != null && a == starting && a.visible) { a.startFreezingScreenLocked(starting.app, - ActivityInfo.CONFIG_SCREEN_LAYOUT); + CONFIG_SCREEN_LAYOUT); } } } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index c97d09c2ebbd..ae987e6f4402 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -1298,6 +1298,9 @@ final class TaskRecord { (mOverrideConfig.screenWidthDp <= mOverrideConfig.screenHeightDp) ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; + final int sl = Configuration.resetScreenLayout(serviceConfig.screenLayout); + mOverrideConfig.screenLayout = Configuration.reduceScreenLayout( + sl, mOverrideConfig.screenWidthDp, mOverrideConfig.screenHeightDp); } if (mFullscreen != oldFullscreen) { diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index d5c31134a4eb..745f4763c1ea 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -767,7 +767,7 @@ public class MediaSessionService extends SystemService implements Monitor { synchronized (mLock) { // If we don't have a media button receiver to fall back on // include non-playing sessions for dispatching - UserRecord ur = mUserRecords.get(ActivityManager.getCurrentUser()); + UserRecord ur = mUserRecords.get(mCurrentUserId); boolean useNotPlayingSessions = (ur == null) || (ur.mLastMediaButtonReceiver == null && ur.mRestoredMediaButtonReceiver == null); @@ -949,8 +949,7 @@ public class MediaSessionService extends SystemService implements Monitor { mKeyEventReceiver); } else { // Launch the last PendingIntent we had with priority - int userId = ActivityManager.getCurrentUser(); - UserRecord user = mUserRecords.get(userId); + UserRecord user = mUserRecords.get(mCurrentUserId); if (user != null && (user.mLastMediaButtonReceiver != null || user.mRestoredMediaButtonReceiver != null)) { if (DEBUG) { @@ -967,11 +966,11 @@ public class MediaSessionService extends SystemService implements Monitor { if (user.mLastMediaButtonReceiver != null) { user.mLastMediaButtonReceiver.send(getContext(), needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, - mediaButtonIntent, mKeyEventReceiver, null); + mediaButtonIntent, mKeyEventReceiver, mHandler); } else { mediaButtonIntent.setComponent(user.mRestoredMediaButtonReceiver); getContext().sendBroadcastAsUser(mediaButtonIntent, - new UserHandle(userId)); + new UserHandle(mCurrentUserId)); } } catch (CanceledException e) { Log.i(TAG, "Error sending key event to media button receiver " diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 276c6babce24..f7043a601c9d 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -570,13 +570,13 @@ public class ZenModeHelper { ZenLog.traceConfig(reason, mConfig, config); final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig), getNotificationPolicy(config)); - mConfig = config; - if (config.equals(mConfig)) { + if (!config.equals(mConfig)) { dispatchOnConfigChanged(); } if (policyChanged) { dispatchOnPolicyChanged(); } + mConfig = config; final String val = Integer.toString(config.hashCode()); Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); if (!evaluateZenMode(reason, setRingerMode)) { diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java index b254f29e0d5a..6c338c184e4a 100644 --- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java @@ -261,6 +261,7 @@ final class DefaultPermissionGrantPolicy { && doesPackageSupportRuntimePermissions(setupPackage)) { grantRuntimePermissionsLPw(setupPackage, PHONE_PERMISSIONS, userId); grantRuntimePermissionsLPw(setupPackage, CONTACTS_PERMISSIONS, userId); + grantRuntimePermissionsLPw(setupPackage, LOCATION_PERMISSIONS, userId); } // Camera diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 99f403104b01..9e0f3ce64a7d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -602,7 +602,9 @@ public class PackageManagerService extends IPackageManager.Stub { boolean mResolverReplaced = false; - private final ComponentName mIntentFilterVerifierComponent; + private final @Nullable ComponentName mIntentFilterVerifierComponent; + private final @Nullable IntentFilterVerifier<ActivityIntentInfo> mIntentFilterVerifier; + private int mIntentFilterVerificationToken = 0; /** Component that knows whether or not an ephemeral application exists */ @@ -838,8 +840,6 @@ public class PackageManagerService extends IPackageManager.Stub { filter.hasDataScheme(IntentFilter.SCHEME_HTTPS)); } - private IntentFilterVerifier mIntentFilterVerifier; - // Set of pending broadcasts for aggregating enable/disable of components. static class PendingPackageBroadcasts { // for each user id, a map of <package name -> components within that package> @@ -974,8 +974,8 @@ public class PackageManagerService extends IPackageManager.Stub { private static final String TAG_DEFAULT_APPS = "da"; private static final String TAG_INTENT_FILTER_VERIFICATION = "iv"; - final String mRequiredVerifierPackage; - final String mRequiredInstallerPackage; + final @Nullable String mRequiredVerifierPackage; + final @Nullable String mRequiredInstallerPackage; private final PackageUsage mPackageUsage = new PackageUsage(); @@ -2362,15 +2362,21 @@ public class PackageManagerService extends IPackageManager.Stub { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY, SystemClock.uptimeMillis()); - mRequiredVerifierPackage = getRequiredVerifierLPr(); - mRequiredInstallerPackage = getRequiredInstallerLPr(); + if (!mOnlyCore) { + mRequiredVerifierPackage = getRequiredVerifierLPr(); + mRequiredInstallerPackage = getRequiredInstallerLPr(); + mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr(); + mIntentFilterVerifier = new IntentVerifierProxy(mContext, + mIntentFilterVerifierComponent); + } else { + mRequiredVerifierPackage = null; + mRequiredInstallerPackage = null; + mIntentFilterVerifierComponent = null; + mIntentFilterVerifier = null; + } mInstallerService = new PackageInstallerService(context, this); - mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr(); - mIntentFilterVerifier = new IntentVerifierProxy(mContext, - mIntentFilterVerifierComponent); - final ComponentName ephemeralResolverComponent = getEphemeralResolverLPr(); final ComponentName ephemeralInstallerComponent = getEphemeralInstallerLPr(); // both the installer and resolver must be present to enable ephemeral diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 72611b7703e9..f13d964e1e1b 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -18,6 +18,8 @@ package com.android.server.policy; import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; +import static android.content.res.Configuration.UI_MODE_TYPE_CAR; +import static android.content.res.Configuration.UI_MODE_TYPE_MASK; import static android.view.WindowManager.LayoutParams.*; import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN; @@ -313,8 +315,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mCanHideNavigationBar = false; boolean mNavigationBarCanMove = false; // can the navigation bar ever move to the side? boolean mNavigationBarOnBottom = true; // is the navigation bar on the bottom *right now*? - int[] mNavigationBarHeightForRotation = new int[4]; - int[] mNavigationBarWidthForRotation = new int[4]; + int[] mNavigationBarHeightForRotationDefault = new int[4]; + int[] mNavigationBarWidthForRotationDefault = new int[4]; + int[] mNavigationBarHeightForRotationInCarMode = new int[4]; + int[] mNavigationBarWidthForRotationInCarMode = new int[4]; // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key. // This is for car dock and this is updated from resource. @@ -1674,20 +1678,37 @@ public class PhoneWindowManager implements WindowManagerPolicy { res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); // Height of the navigation bar when presented horizontally at bottom - mNavigationBarHeightForRotation[mPortraitRotation] = - mNavigationBarHeightForRotation[mUpsideDownRotation] = + mNavigationBarHeightForRotationDefault[mPortraitRotation] = + mNavigationBarHeightForRotationDefault[mUpsideDownRotation] = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height); - mNavigationBarHeightForRotation[mLandscapeRotation] = - mNavigationBarHeightForRotation[mSeascapeRotation] = res.getDimensionPixelSize( + mNavigationBarHeightForRotationDefault[mLandscapeRotation] = + mNavigationBarHeightForRotationDefault[mSeascapeRotation] = res.getDimensionPixelSize( com.android.internal.R.dimen.navigation_bar_height_landscape); // Width of the navigation bar when presented vertically along one side - mNavigationBarWidthForRotation[mPortraitRotation] = - mNavigationBarWidthForRotation[mUpsideDownRotation] = - mNavigationBarWidthForRotation[mLandscapeRotation] = - mNavigationBarWidthForRotation[mSeascapeRotation] = + mNavigationBarWidthForRotationDefault[mPortraitRotation] = + mNavigationBarWidthForRotationDefault[mUpsideDownRotation] = + mNavigationBarWidthForRotationDefault[mLandscapeRotation] = + mNavigationBarWidthForRotationDefault[mSeascapeRotation] = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width); + // Height of the navigation bar when presented horizontally at bottom + mNavigationBarHeightForRotationInCarMode[mPortraitRotation] = + mNavigationBarHeightForRotationInCarMode[mUpsideDownRotation] = + res.getDimensionPixelSize( + com.android.internal.R.dimen.navigation_bar_height_car_mode); + mNavigationBarHeightForRotationInCarMode[mLandscapeRotation] = + mNavigationBarHeightForRotationInCarMode[mSeascapeRotation] = res.getDimensionPixelSize( + com.android.internal.R.dimen.navigation_bar_height_landscape_car_mode); + + // Width of the navigation bar when presented vertically along one side + mNavigationBarWidthForRotationInCarMode[mPortraitRotation] = + mNavigationBarWidthForRotationInCarMode[mUpsideDownRotation] = + mNavigationBarWidthForRotationInCarMode[mLandscapeRotation] = + mNavigationBarWidthForRotationInCarMode[mSeascapeRotation] = + res.getDimensionPixelSize( + com.android.internal.R.dimen.navigation_bar_width_car_mode); + // SystemUI (status bar) layout policy int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / density; int longSizeDp = longSize * DisplayMetrics.DENSITY_DEFAULT / density; @@ -2239,42 +2260,61 @@ public class PhoneWindowManager implements WindowManagerPolicy { return windowTypeToLayerLw(TYPE_STATUS_BAR); } + private int getNavigationBarWidth(int rotation, int uiMode) { + if ((uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) { + return mNavigationBarWidthForRotationInCarMode[rotation]; + } else { + return mNavigationBarWidthForRotationDefault[rotation]; + } + } + @Override - public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation) { + public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, + int uiMode) { if (mHasNavigationBar) { // For a basic navigation bar, when we are in landscape mode we place // the navigation bar to the side. if (mNavigationBarCanMove && fullWidth > fullHeight) { - return fullWidth - mNavigationBarWidthForRotation[rotation]; + return fullWidth - getNavigationBarWidth(rotation, uiMode); } } return fullWidth; } + private int getNavigationBarHeight(int rotation, int uiMode) { + if ((uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) { + return mNavigationBarHeightForRotationInCarMode[rotation]; + } else { + return mNavigationBarHeightForRotationDefault[rotation]; + } + } + @Override - public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation) { + public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation, + int uiMode) { if (mHasNavigationBar) { // For a basic navigation bar, when we are in portrait mode we place // the navigation bar to the bottom. if (!mNavigationBarCanMove || fullWidth < fullHeight) { - return fullHeight - mNavigationBarHeightForRotation[rotation]; + return fullHeight - getNavigationBarHeight(rotation, uiMode); } } return fullHeight; } @Override - public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation) { - return getNonDecorDisplayWidth(fullWidth, fullHeight, rotation); + public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode) { + return getNonDecorDisplayWidth(fullWidth, fullHeight, rotation, uiMode); } @Override - public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation) { + public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode) { // There is a separate status bar at the top of the display. We don't count that as part // of the fixed decor, since it can hide; however, for purposes of configurations, // we do want to exclude it since applications can't generally use that part // of the screen. - return getNonDecorDisplayHeight(fullWidth, fullHeight, rotation) - mStatusBarHeight; + return getNonDecorDisplayHeight( + fullWidth, fullHeight, rotation, uiMode) - mStatusBarHeight; } @Override @@ -2879,7 +2919,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else if (keyCode == KeyEvent.KEYCODE_SLASH && event.isMetaPressed()) { if (down) { if (repeatCount == 0) { - showKeyboardShortcutsMenu(); + toggleKeyboardShortcutsMenu(); } } } else if (keyCode == KeyEvent.KEYCODE_ASSIST) { @@ -3311,11 +3351,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void showKeyboardShortcutsMenu() { + private void toggleKeyboardShortcutsMenu() { try { IStatusBarService statusbar = getStatusBarService(); if (statusbar != null) { - statusbar.showKeyboardShortcutsMenu(); + statusbar.toggleKeyboardShortcutsMenu(); } } catch (RemoteException e) { Slog.e(TAG, "RemoteException when showing keyboard shortcuts menu", e); @@ -3550,7 +3590,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** {@inheritDoc} */ @Override public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight, - int displayRotation) { + int displayRotation, int uiMode) { mDisplayRotation = displayRotation; final int overscanLeft, overscanTop, overscanRight, overscanBottom; if (isDefaultDisplay) { @@ -3661,7 +3701,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { navVisible |= !canHideNavigationBar(); boolean updateSysUiVisibility = layoutNavigationBar(displayWidth, displayHeight, - displayRotation, overscanRight, overscanBottom, dcf, navVisible, navTranslucent, + displayRotation, uiMode, overscanRight, overscanBottom, dcf, navVisible, navTranslucent, navAllowedHidden); if (DEBUG_LAYOUT) Slog.i(TAG, String.format("mDock rect: (%d,%d - %d,%d)", mDockLeft, mDockTop, mDockRight, mDockBottom)); @@ -3740,7 +3780,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private boolean layoutNavigationBar(int displayWidth, int displayHeight, int displayRotation, - int overscanRight, int overscanBottom, Rect dcf, boolean navVisible, + int uiMode, int overscanRight, int overscanBottom, Rect dcf, boolean navVisible, boolean navTranslucent, boolean navAllowedHidden) { if (mNavigationBar != null) { boolean transientNavBarShowing = mNavigationBarController.isTransientShowing(); @@ -3752,7 +3792,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mNavigationBarOnBottom) { // It's a system nav bar or a portrait screen; nav bar goes on bottom. int top = displayHeight - overscanBottom - - mNavigationBarHeightForRotation[displayRotation]; + - getNavigationBarHeight(displayRotation, uiMode); mTmpNavigationFrame.set(0, top, displayWidth, displayHeight - overscanBottom); mStableBottom = mStableFullscreenBottom = mTmpNavigationFrame.top; if (transientNavBarShowing) { @@ -3777,7 +3817,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else { // Landscape screen; nav bar goes to the right. int left = displayWidth - overscanRight - - mNavigationBarWidthForRotation[displayRotation]; + - getNavigationBarWidth(displayRotation, uiMode); mTmpNavigationFrame.set(left, 0, displayWidth - overscanRight, displayHeight); mStableRight = mStableFullscreenRight = mTmpNavigationFrame.left; if (transientNavBarShowing) { diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 6498dd941a32..290019c73ba4 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -16,21 +16,8 @@ package com.android.server.power; -import android.app.ActivityManager; -import android.util.SparseIntArray; -import com.android.internal.app.IAppOpsService; -import com.android.internal.app.IBatteryStats; -import com.android.internal.os.BackgroundThread; -import com.android.server.EventLogTags; -import com.android.server.ServiceThread; -import com.android.server.SystemService; -import com.android.server.am.BatteryStatsService; -import com.android.server.lights.Light; -import com.android.server.lights.LightsManager; -import com.android.server.Watchdog; - import android.Manifest; -import android.app.AppOpsManager; +import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -65,22 +52,32 @@ import android.provider.Settings; import android.service.dreams.DreamManagerInternal; import android.util.EventLog; import android.util.Slog; +import android.util.SparseIntArray; import android.util.TimeUtils; import android.view.Display; import android.view.WindowManagerPolicy; +import com.android.internal.app.IAppOpsService; +import com.android.internal.app.IBatteryStats; +import com.android.internal.os.BackgroundThread; +import com.android.server.EventLogTags; +import com.android.server.ServiceThread; +import com.android.server.SystemService; +import com.android.server.Watchdog; +import com.android.server.am.BatteryStatsService; +import com.android.server.lights.Light; +import com.android.server.lights.LightsManager; +import libcore.util.Objects; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; -import libcore.util.Objects; - import static android.os.PowerManagerInternal.POWER_HINT_INTERACTION; import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; -import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; +import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; /** * The power manager service is responsible for coordinating power management @@ -771,6 +768,10 @@ public final class PowerManagerService extends SystemService intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcast(intent); + // Send internal version that requires signature permission. + mContext.sendBroadcastAsUser(new Intent( + PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL), UserHandle.ALL, + Manifest.permission.DEVICE_POWER); } }); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index fc271704a1ec..2d38da5329e1 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -503,10 +503,10 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } @Override - public void showKeyboardShortcutsMenu() { + public void toggleKeyboardShortcutsMenu() { if (mBar != null) { try { - mBar.showKeyboardShortcutsMenu(); + mBar.toggleKeyboardShortcutsMenu(); } catch (RemoteException ex) {} } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 59421984dc1f..51787b066859 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -570,38 +570,19 @@ class DisplayContent { pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight); pw.print(subPrefix); pw.print("deferred="); pw.print(mDeferredRemoval); pw.print(" layoutNeeded="); pw.println(layoutNeeded); - for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { - final TaskStack stack = mStacks.get(stackNdx); - pw.print(prefix); pw.print("mStacks[" + stackNdx + "]"); pw.println(stack.mStackId); - stack.dump(prefix + " ", pw); - } + pw.println(); pw.println(" Application tokens in top down Z order:"); - int ndx = 0; for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { final TaskStack stack = mStacks.get(stackNdx); - pw.print(" mStackId="); pw.println(stack.mStackId); - ArrayList<Task> tasks = stack.getTasks(); - for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { - final Task task = tasks.get(taskNdx); - pw.print(" mTaskId="); pw.println(task.mTaskId); - AppTokenList tokens = task.mAppTokens; - for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx, ++ndx) { - final AppWindowToken wtoken = tokens.get(tokenNdx); - pw.print(" Activity #"); pw.print(tokenNdx); - pw.print(' '); pw.print(wtoken); pw.println(":"); - wtoken.dump(pw, " "); - } - } - } - if (ndx == 0) { - pw.println(" None"); + stack.dump(prefix + " ", pw); } + pw.println(); if (!mExitingTokens.isEmpty()) { pw.println(); pw.println(" Exiting tokens:"); - for (int i=mExitingTokens.size()-1; i>=0; i--) { + for (int i = mExitingTokens.size() - 1; i >= 0; i--) { WindowToken token = mExitingTokens.get(i); pw.print(" Exiting #"); pw.print(i); pw.print(' '); pw.print(token); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 223e03ad4cc4..72970f6c7545 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -603,12 +603,23 @@ class Task implements DimLayer.DimLayerUser { return "Task=" + mTaskId; } - public void printTo(String prefix, PrintWriter pw) { - pw.print(prefix); pw.print("taskId="); pw.println(mTaskId); - pw.print(prefix + prefix); pw.print("mFullscreen="); pw.println(mFullscreen); - pw.print(prefix + prefix); pw.print("mBounds="); pw.println(mBounds.toShortString()); - pw.print(prefix + prefix); pw.print("mdr="); pw.println(mDeferRemoval); - pw.print(prefix + prefix); pw.print("appTokens="); pw.println(mAppTokens); - pw.print(prefix + prefix); pw.print("mTempInsetBounds="); pw.println(mTempInsetBounds); + public void dump(String prefix, PrintWriter pw) { + final String doublePrefix = prefix + " "; + + pw.println(prefix + "taskId=" + mTaskId); + pw.println(doublePrefix + "mFullscreen=" + mFullscreen); + pw.println(doublePrefix + "mBounds=" + mBounds.toShortString()); + pw.println(doublePrefix + "mdr=" + mDeferRemoval); + pw.println(doublePrefix + "appTokens=" + mAppTokens); + pw.println(doublePrefix + "mTempInsetBounds=" + mTempInsetBounds.toShortString()); + + final String triplePrefix = doublePrefix + " "; + + for (int i = mAppTokens.size() - 1; i >= 0; i--) { + final AppWindowToken wtoken = mAppTokens.get(i); + pw.println(triplePrefix + "Activity #" + i + " " + wtoken); + wtoken.dump(pw, triplePrefix); + } + } } diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 67debe6e3773..fc6ad70513b1 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -619,16 +619,15 @@ public class TaskStack implements DimLayer.DimLayerUser { } public void dump(String prefix, PrintWriter pw) { - pw.print(prefix); pw.print("mStackId="); pw.println(mStackId); - pw.print(prefix); pw.print("mDeferDetach="); pw.println(mDeferDetach); - pw.print(prefix); pw.print("mFullscreen="); pw.println(mFullscreen); - pw.print(prefix); pw.print("mBounds="); pw.println(mBounds.toShortString()); - for (int taskNdx = 0; taskNdx < mTasks.size(); ++taskNdx) { - pw.print(prefix); - mTasks.get(taskNdx).printTo(prefix + " ", pw); + pw.println(prefix + "mStackId=" + mStackId); + pw.println(prefix + "mDeferDetach=" + mDeferDetach); + pw.println(prefix + "mFullscreen=" + mFullscreen); + pw.println(prefix + "mBounds=" + mBounds.toShortString()); + for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; taskNdx--) { + mTasks.get(taskNdx).dump(prefix + " ", pw); } if (mAnimationBackgroundSurface.isDimming()) { - pw.print(prefix); pw.println("mWindowAnimationBackgroundSurface:"); + pw.println(prefix + "mWindowAnimationBackgroundSurface:"); mAnimationBackgroundSurface.printTo(prefix + " ", pw); } if (!mExitingAppTokens.isEmpty()) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f6e62f6cffb6..3f57c55cc51b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -578,31 +578,23 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<WindowState> mTmpWindows = new ArrayList<>(); boolean mHardKeyboardAvailable; - boolean mShowImeWithHardKeyboard; WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener; SettingsObserver mSettingsObserver; private final class SettingsObserver extends ContentObserver { - private final Uri mShowImeWithHardKeyboardUri = - Settings.Secure.getUriFor(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD); - private final Uri mDisplayInversionEnabledUri = Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED); public SettingsObserver() { super(new Handler()); ContentResolver resolver = mContext.getContentResolver(); - resolver.registerContentObserver(mShowImeWithHardKeyboardUri, false, this, - UserHandle.USER_ALL); resolver.registerContentObserver(mDisplayInversionEnabledUri, false, this, UserHandle.USER_ALL); } @Override public void onChange(boolean selfChange, Uri uri) { - if (mShowImeWithHardKeyboardUri.equals(uri)) { - updateShowImeWithHardKeyboard(); - } else if (mDisplayInversionEnabledUri.equals(uri)) { + if (mDisplayInversionEnabledUri.equals(uri)) { updateCircularDisplayMaskIfNeeded(); } } @@ -946,7 +938,6 @@ public class WindowManagerService extends IWindowManager.Stub mContext.registerReceiver(mBroadcastReceiver, filter); mSettingsObserver = new SettingsObserver(); - updateShowImeWithHardKeyboard(); mHoldingScreenWakeLock = mPowerManager.newWakeLock( PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM); @@ -3478,6 +3469,7 @@ public class WindowManagerService extends IWindowManager.Stub // the value of the previous configuration. mTempConfiguration.setToDefaults(); mTempConfiguration.fontScale = currentConfig.fontScale; + mTempConfiguration.uiMode = currentConfig.uiMode; computeScreenConfigurationLocked(mTempConfiguration); if (currentConfig.diff(mTempConfiguration) != 0) { mWaitingForConfig = true; @@ -6295,7 +6287,7 @@ public class WindowManagerService extends IWindowManager.Stub // the top of the method, the caller is obligated to call computeNewConfigurationLocked(). // By updating the Display info here it will be available to // computeScreenConfigurationLocked later. - updateDisplayAndOrientationLocked(); + updateDisplayAndOrientationLocked(mCurConfiguration.uiMode); final DisplayInfo displayInfo = displayContent.getDisplayInfo(); if (!inTransaction) { @@ -6862,16 +6854,17 @@ public class WindowManagerService extends IWindowManager.Stub return config; } - private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh) { + private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int uiMode, + int dw, int dh) { // TODO: Multidisplay: for now only use with default display. - final int width = mPolicy.getConfigDisplayWidth(dw, dh, rotation); + final int width = mPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode); if (width < displayInfo.smallestNominalAppWidth) { displayInfo.smallestNominalAppWidth = width; } if (width > displayInfo.largestNominalAppWidth) { displayInfo.largestNominalAppWidth = width; } - final int height = mPolicy.getConfigDisplayHeight(dw, dh, rotation); + final int height = mPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode); if (height < displayInfo.smallestNominalAppHeight) { displayInfo.smallestNominalAppHeight = height; } @@ -6881,11 +6874,11 @@ public class WindowManagerService extends IWindowManager.Stub } private int reduceConfigLayout(int curLayout, int rotation, float density, - int dw, int dh) { + int dw, int dh, int uiMode) { // TODO: Multidisplay: for now only use with default display. // Get the app screen size at this rotation. - int w = mPolicy.getNonDecorDisplayWidth(dw, dh, rotation); - int h = mPolicy.getNonDecorDisplayHeight(dw, dh, rotation); + int w = mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode); + int h = mPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode); // Compute the screen layout size class for this rotation. int longSize = w; @@ -6901,7 +6894,7 @@ public class WindowManagerService extends IWindowManager.Stub } private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, boolean rotated, - int dw, int dh, float density, Configuration outConfig) { + int uiMode, int dw, int dh, float density, Configuration outConfig) { // TODO: Multidisplay: for now only use with default display. // We need to determine the smallest width that will occur under normal @@ -6920,24 +6913,24 @@ public class WindowManagerService extends IWindowManager.Stub displayInfo.smallestNominalAppHeight = 1<<30; displayInfo.largestNominalAppWidth = 0; displayInfo.largestNominalAppHeight = 0; - adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh); - adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw); - adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh); - adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, uiMode, unrotDw, unrotDh); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, uiMode, unrotDh, unrotDw); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, uiMode, unrotDw, unrotDh); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, uiMode, unrotDh, unrotDw); int sl = Configuration.resetScreenLayout(outConfig.screenLayout); - sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh); - sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw); - sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh); - sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw); + sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode); + sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode); + sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode); + sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode); outConfig.smallestScreenWidthDp = (int)(displayInfo.smallestNominalAppWidth / density); outConfig.screenLayout = sl; } - private int reduceCompatConfigWidthSize(int curSize, int rotation, DisplayMetrics dm, - int dw, int dh) { + private int reduceCompatConfigWidthSize(int curSize, int rotation, int uiMode, + DisplayMetrics dm, int dw, int dh) { // TODO: Multidisplay: for now only use with default display. - dm.noncompatWidthPixels = mPolicy.getNonDecorDisplayWidth(dw, dh, rotation); - dm.noncompatHeightPixels = mPolicy.getNonDecorDisplayHeight(dw, dh, rotation); + dm.noncompatWidthPixels = mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode); + dm.noncompatHeightPixels = mPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode); float scale = CompatibilityInfo.computeCompatibleScaling(dm, null); int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f); if (curSize == 0 || size < curSize) { @@ -6946,7 +6939,7 @@ public class WindowManagerService extends IWindowManager.Stub return curSize; } - private int computeCompatSmallestWidth(boolean rotated, DisplayMetrics dm, int dw, int dh) { + private int computeCompatSmallestWidth(boolean rotated, int uiMode, DisplayMetrics dm, int dw, int dh) { // TODO: Multidisplay: for now only use with default display. mTmpDisplayMetrics.setTo(dm); final DisplayMetrics tmpDm = mTmpDisplayMetrics; @@ -6958,15 +6951,15 @@ public class WindowManagerService extends IWindowManager.Stub unrotDw = dw; unrotDh = dh; } - int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, tmpDm, unrotDw, unrotDh); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, tmpDm, unrotDh, unrotDw); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, tmpDm, unrotDw, unrotDh); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, tmpDm, unrotDh, unrotDw); + int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw, unrotDh); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh, unrotDw); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw, unrotDh); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh, unrotDw); return sw; } /** Do not call if mDisplayReady == false */ - DisplayInfo updateDisplayAndOrientationLocked() { + DisplayInfo updateDisplayAndOrientationLocked(int uiMode) { // TODO(multidisplay): For now, apply Configuration to main screen only. final DisplayContent displayContent = getDefaultDisplayContentLocked(); @@ -6997,8 +6990,8 @@ public class WindowManagerService extends IWindowManager.Stub } // Update application display metrics. - final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation); - final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation); + final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation, uiMode); + final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation, uiMode); final DisplayInfo displayInfo = displayContent.getDisplayInfo(); displayInfo.rotation = mRotation; displayInfo.logicalWidth = dw; @@ -7030,20 +7023,24 @@ public class WindowManagerService extends IWindowManager.Stub /** Do not call if mDisplayReady == false */ void computeScreenConfigurationLocked(Configuration config) { - final DisplayInfo displayInfo = updateDisplayAndOrientationLocked(); + final DisplayInfo displayInfo = updateDisplayAndOrientationLocked( + config.uiMode); final int dw = displayInfo.logicalWidth; final int dh = displayInfo.logicalHeight; config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; config.screenWidthDp = - (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation) / mDisplayMetrics.density); + (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation, config.uiMode) / + mDisplayMetrics.density); config.screenHeightDp = - (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation) / mDisplayMetrics.density); + (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation, config.uiMode) / + mDisplayMetrics.density); final boolean rotated = (mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270); - computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh, mDisplayMetrics.density, - config); + + computeSizeRangesAndScreenLayout(displayInfo, rotated, config.uiMode, dw, dh, + mDisplayMetrics.density, config); config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK) | ((displayInfo.flags & Display.FLAG_ROUND) != 0 @@ -7052,7 +7049,7 @@ public class WindowManagerService extends IWindowManager.Stub config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale); config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale); - config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, + config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, config.uiMode, mDisplayMetrics, dw, dh); config.densityDpi = displayInfo.logicalDensityDpi; @@ -7111,9 +7108,6 @@ public class WindowManagerService extends IWindowManager.Stub mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); } - if (mShowImeWithHardKeyboard) { - config.keyboard = Configuration.KEYBOARD_NOKEYS; - } // Let the policy update hidden states. config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; @@ -7122,18 +7116,6 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence); } - public void updateShowImeWithHardKeyboard() { - synchronized (mWindowMap) { - final boolean showImeWithHardKeyboard = Settings.Secure.getIntForUser( - mContext.getContentResolver(), Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0, - mCurrentUserId) == 1; - if (mShowImeWithHardKeyboard != showImeWithHardKeyboard) { - mShowImeWithHardKeyboard = showImeWithHardKeyboard; - mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); - } - } - } - void notifyHardKeyboardStatusChange() { final boolean available; final WindowManagerInternal.OnHardKeyboardStatusChangeListener listener; @@ -8471,6 +8453,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean configChanged = updateOrientationFromAppTokensLocked(false); mTempConfiguration.setToDefaults(); mTempConfiguration.fontScale = mCurConfiguration.fontScale; + mTempConfiguration.uiMode = mCurConfiguration.uiMode; computeScreenConfigurationLocked(mTempConfiguration); configChanged |= mCurConfiguration.diff(mTempConfiguration) != 0; @@ -9790,6 +9773,10 @@ public class WindowManagerService extends IWindowManager.Stub if ("visible".equals(name) || "visible-apps".equals(name)) { final boolean appsOnly = "visible-apps".equals(name); synchronized(mWindowMap) { + if (appsOnly) { + dumpDisplayContentsLocked(pw, true); + } + final int numDisplays = mDisplayContents.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final WindowList windowList = diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index feeab27e8fae..f77e5a6cab32 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -859,7 +859,8 @@ class WindowSurfacePlacer { + displayContent.layoutNeeded + " dw=" + dw + " dh=" + dh); } - mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mService.mRotation); + mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mService.mRotation, + mService.mCurConfiguration.uiMode); if (isDefaultDisplay) { // Not needed on non-default displays. mService.mSystemDecorLayer = mService.mPolicy.getSystemDecorLayerLw(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 53edc78960d0..dd58b3c4443e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -129,6 +129,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo; +import com.android.server.pm.UserManagerService; import com.android.server.pm.UserRestrictionsUtils; import org.xmlpull.v1.XmlPullParser; @@ -426,6 +427,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String TAG_USER_RESTRICTIONS = "user-restrictions"; private static final String TAG_SHORT_SUPPORT_MESSAGE = "short-support-message"; private static final String TAG_LONG_SUPPORT_MESSAGE = "long-support-message"; + private static final String TAG_PARENT_ADMIN = "parent-admin"; final DeviceAdminInfo info; @@ -478,6 +480,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean disableScreenCapture = false; // Can only be set by a device/profile owner. boolean requireAutoTime = false; // Can only be set by a device owner. + ActiveAdmin parentAdmin; + final boolean isParent; + static class TrustAgentInfo { public PersistableBundle options; TrustAgentInfo(PersistableBundle bundle) { @@ -515,8 +520,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { String shortSupportMessage = null; String longSupportMessage = null; - ActiveAdmin(DeviceAdminInfo _info) { + ActiveAdmin(DeviceAdminInfo _info, boolean parent) { info = _info; + isParent = parent; + } + + ActiveAdmin getParentActiveAdmin() { + if (parentAdmin == null && !isParent) { + parentAdmin = new ActiveAdmin(info, /* parent */ true); + } + return parentAdmin; } int getUid() { return info.getActivityInfo().applicationInfo.uid; } @@ -704,6 +717,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.text(longSupportMessage); out.endTag(null, TAG_LONG_SUPPORT_MESSAGE); } + if (parentAdmin != null) { + out.startTag(null, TAG_PARENT_ADMIN); + parentAdmin.writeToXml(out); + out.endTag(null, TAG_PARENT_ADMIN); + } } void writePackageListToXml(XmlSerializer out, String outerTag, @@ -831,6 +849,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else { Log.w(LOG_TAG, "Missing text when loading long support message"); } + } else if (TAG_PARENT_ADMIN.equals(tag)) { + parentAdmin = new ActiveAdmin(info, /* parent */ true); + parentAdmin.readFromXml(parser); } else { Slog.w(LOG_TAG, "Unknown admin tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -2014,7 +2035,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + userHandle); } if (dai != null) { - ActiveAdmin ap = new ActiveAdmin(dai); + ActiveAdmin ap = new ActiveAdmin(dai, /* parent */ false); ap.readFromXml(parser); policy.mAdminMap.put(ap.info.getComponent(), ap); } @@ -2405,7 +2426,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { && getActiveAdminUncheckedLocked(adminReceiver, userHandle) != null) { throw new IllegalArgumentException("Admin is already added"); } - ActiveAdmin newAdmin = new ActiveAdmin(info); + ActiveAdmin newAdmin = new ActiveAdmin(info, /* parent */ false); policy.mAdminMap.put(adminReceiver, newAdmin); int replaceIndex = -1; final int N = policy.mAdminList.size(); @@ -2540,8 +2561,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private boolean isAdminApiLevelPreN(@NonNull ComponentName who, int userHandle) { + DeviceAdminInfo adminInfo = findAdmin(who, userHandle, false); + return adminInfo.getActivityInfo().applicationInfo.targetSdkVersion + < Build.VERSION_CODES.N; + } + @Override - public void setPasswordQuality(ComponentName who, int quality) { + public void setPasswordQuality(ComponentName who, int quality, boolean parent) { if (!mHasFeature) { return; } @@ -2552,6 +2579,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (parent) { + ap = ap.getParentActiveAdmin(); + } if (ap.passwordQuality != quality) { ap.passwordQuality = quality; saveSettingsLocked(userHandle); @@ -2560,7 +2590,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public int getPasswordQuality(ComponentName who, int userHandle) { + public int getPasswordQuality(ComponentName who, int userHandle, boolean parent) { if (!mHasFeature) { return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; } @@ -2570,20 +2600,43 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle); + if (parent && admin != null) { + admin = admin.getParentActiveAdmin(); + } return admin != null ? admin.passwordQuality : mode; } - // Return strictest policy for this user and profiles that are visible from this user. - List<UserInfo> profiles = mUserManager.getProfiles(userHandle); - for (UserInfo userInfo : profiles) { - DevicePolicyData policy = getUserDataUnchecked(userInfo.id); + if (LockPatternUtils.isSeparateWorkChallengeEnabled() && !parent) { + // If a Work Challenge is in use, only return its restrictions. + DevicePolicyData policy = getUserDataUnchecked(userHandle); final int N = policy.mAdminList.size(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); if (mode < admin.passwordQuality) { mode = admin.passwordQuality; } } + } else { + // Return strictest policy for this user and profiles that are visible from this + // user that do not use a separate work challenge. + // TODO: When there are separate parent restrictions the profile should just + // obey its own. + List<UserInfo> profiles = mUserManager.getProfiles(userHandle); + for (UserInfo userInfo : profiles) { + // Only aggregate data for the parent profile plus the non-work challenge + // enabled profiles. + if (!(userInfo.isManagedProfile() + && LockPatternUtils.isSeparateWorkChallengeEnabled())) { + DevicePolicyData policy = getUserDataUnchecked(userInfo.id); + final int N = policy.mAdminList.size(); + for (int i = 0; i < N; i++) { + ActiveAdmin admin = policy.mAdminList.get(i); + if (mode < admin.passwordQuality) { + mode = admin.passwordQuality; + } + } + } + } } return mode; } @@ -3143,7 +3196,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public boolean isActivePasswordSufficient(int userHandle) { + public boolean isActivePasswordSufficient(int userHandle, boolean parent) { if (!mHasFeature) { return true; } @@ -3155,8 +3208,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // This API can only be called by an active device admin, // so try to retrieve it to check that the caller is one. - getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); - if (policy.mActivePasswordQuality < getPasswordQuality(null, userHandle) + ActiveAdmin admin = + getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + ComponentName adminComponentName = admin.info.getComponent(); + // TODO: Include the Admin sdk level check in LockPatternUtils check. + ComponentName who = !isAdminApiLevelPreN(adminComponentName, userHandle) + && LockPatternUtils.isSeparateWorkChallengeEnabled() + ? adminComponentName : null; + if (policy.mActivePasswordQuality < getPasswordQuality(who, userHandle, parent) || policy.mActivePasswordLength < getPasswordMinimumLength(null, userHandle)) { return false; } @@ -3321,7 +3380,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } - quality = getPasswordQuality(null, userHandle); + quality = getPasswordQuality(null, userHandle, false); if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { int realQuality = LockPatternUtils.computePasswordQuality(password); if (realQuality < quality diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java index d5f384d3bbfb..d250739f3f26 100644 --- a/services/print/java/com/android/server/print/PrintManagerService.java +++ b/services/print/java/com/android/server/print/PrintManagerService.java @@ -24,7 +24,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.graphics.drawable.Icon; @@ -56,7 +55,6 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Iterator; import java.util.List; -import java.util.Set; /** * SystemService wrapper for the PrintManager implementation. Publishes @@ -88,11 +86,6 @@ public final class PrintManagerService extends SystemService { } class PrintManagerImpl extends IPrintManager.Stub { - private static final char COMPONENT_NAME_SEPARATOR = ':'; - - private static final String EXTRA_PRINT_SERVICE_COMPONENT_NAME = - "EXTRA_PRINT_SERVICE_COMPONENT_NAME"; - private static final int BACKGROUND_USER_ID = -10; private final Object mLock = new Object(); @@ -487,7 +480,7 @@ public final class PrintManagerService extends SystemService { private void registerContentObservers() { final Uri enabledPrintServicesUri = Settings.Secure.getUriFor( - Settings.Secure.ENABLED_PRINT_SERVICES); + Settings.Secure.DISABLED_PRINT_SERVICES); ContentObserver observer = new ContentObserver(BackgroundThread.getHandler()) { @Override public void onChange(boolean selfChange, Uri uri, int userId) { @@ -511,8 +504,7 @@ public final class PrintManagerService extends SystemService { private void registerBroadcastReceivers() { PackageMonitor monitor = new PackageMonitor() { - @Override - public void onPackageModified(String packageName) { + private void updateServices(String packageName) { if (!mUserManager.isUserUnlocked(getChangingUserId())) return; synchronized (mLock) { // A background user/profile's print jobs are running but there is @@ -520,11 +512,15 @@ public final class PrintManagerService extends SystemService { // to handle it as the change may affect ongoing print jobs. boolean servicesChanged = false; UserState userState = getOrCreateUserStateLocked(getChangingUserId()); - Iterator<ComponentName> iterator = userState.getEnabledServices().iterator(); - while (iterator.hasNext()) { - ComponentName componentName = iterator.next(); - if (packageName.equals(componentName.getPackageName())) { + + List<PrintServiceInfo> installedServices = userState + .getInstalledPrintServices(); + final int numInstalledServices = installedServices.size(); + for (int i = 0; i < numInstalledServices; i++) { + if (installedServices.get(i).getResolveInfo().serviceInfo.packageName + .equals(packageName)) { servicesChanged = true; + break; } } if (servicesChanged) { @@ -534,30 +530,15 @@ public final class PrintManagerService extends SystemService { } @Override + public void onPackageModified(String packageName) { + updateServices(packageName); + getOrCreateUserStateLocked(getChangingUserId()).prunePrintServices(); + } + + @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 - // to handle it as the change may affect ongoing print jobs. - boolean servicesRemoved = false; - UserState userState = getOrCreateUserStateLocked(getChangingUserId()); - Iterator<ComponentName> iterator = userState.getEnabledServices().iterator(); - while (iterator.hasNext()) { - ComponentName componentName = iterator.next(); - if (packageName.equals(componentName.getPackageName())) { - userState.removeApprovedPrintService(componentName); - iterator.remove(); - servicesRemoved = true; - } - } - if (servicesRemoved) { - persistComponentNamesToSettingLocked( - Settings.Secure.ENABLED_PRINT_SERVICES, - userState.getEnabledServices(), getChangingUserId()); - userState.updateIfNeededLocked(); - } - } + updateServices(packageName); + getOrCreateUserStateLocked(getChangingUserId()).prunePrintServices(); } @Override @@ -570,10 +551,10 @@ public final class PrintManagerService extends SystemService { // to handle it as the change may affect ongoing print jobs. UserState userState = getOrCreateUserStateLocked(getChangingUserId()); boolean stoppedSomePackages = false; - Iterator<ComponentName> iterator = userState.getEnabledServices() + Iterator<PrintServiceInfo> iterator = userState.getEnabledPrintServices() .iterator(); while (iterator.hasNext()) { - ComponentName componentName = iterator.next(); + ComponentName componentName = iterator.next().getComponentName(); String componentPackage = componentName.getPackageName(); for (String stoppedPackage : stoppedPackages) { if (componentPackage.equals(stoppedPackage)) { @@ -606,47 +587,10 @@ public final class PrintManagerService extends SystemService { .queryIntentServicesAsUser(intent, PackageManager.GET_SERVICES, getChangingUserId()); - if (installedServices == null) { - return; - } - - // Enable all added services by default - synchronized (mLock) { + if (installedServices != null) { UserState userState = getOrCreateUserStateLocked(getChangingUserId()); - - Set<ComponentName> enabledServices = userState.getEnabledServices(); - boolean servicesAdded = false; - - final int installedServiceCount = installedServices.size(); - for (int i = 0; i < installedServiceCount; i++) { - ServiceInfo serviceInfo = installedServices.get(i).serviceInfo; - ComponentName component = new ComponentName(serviceInfo.packageName, - serviceInfo.name); - - enabledServices.add(component); - servicesAdded = true; - } - - if (servicesAdded) { - persistComponentNamesToSettingLocked( - Settings.Secure.ENABLED_PRINT_SERVICES, enabledServices, - getChangingUserId()); - userState.updateIfNeededLocked(); - } - } - } - - private void persistComponentNamesToSettingLocked(String settingName, - Set<ComponentName> componentNames, int userId) { - StringBuilder builder = new StringBuilder(); - for (ComponentName componentName : componentNames) { - if (builder.length() > 0) { - builder.append(COMPONENT_NAME_SEPARATOR); - } - builder.append(componentName.flattenToShortString()); + userState.updateIfNeededLocked(); } - Settings.Secure.putStringForUser(mContext.getContentResolver(), - settingName, builder.toString(), userId); } }; diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java index 40a888041d2d..d179b95548f4 100644 --- a/services/print/java/com/android/server/print/RemotePrintSpooler.java +++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java @@ -37,17 +37,18 @@ import android.print.IPrintSpoolerClient; import android.print.PrintJobId; import android.print.PrintJobInfo; import android.print.PrinterId; +import android.printservice.PrintService; import android.util.Slog; import android.util.TimedRemoteCaller; +import libcore.io.IoUtils; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.List; import java.util.concurrent.TimeoutException; -import libcore.io.IoUtils; - /** * This represents the remote print spooler as a local object to the * PrintManagerService. It is responsible to connecting to the remote @@ -439,26 +440,24 @@ final class RemotePrintSpooler { } /** - * Connect to the print spooler service and remove an approved print service. + * Remove all approved {@link PrintService print services} that are not in the given set. * - * @param serviceToRemove The {@link ComponentName} of the service to be removed. + * @param servicesToKeep The {@link ComponentName names } of the services to keep */ - public final void removeApprovedPrintService(ComponentName serviceToRemove) { + public final void pruneApprovedPrintServices(List<ComponentName> servicesToKeep) { throwIfCalledOnMainThread(); synchronized (mLock) { throwIfDestroyedLocked(); mCanUnbind = false; } try { - getRemoteInstanceLazy().removeApprovedPrintService(serviceToRemove); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error removing approved print service.", re); - } catch (TimeoutException te) { - Slog.e(LOG_TAG, "Error removing approved print service.", te); + getRemoteInstanceLazy().pruneApprovedPrintServices(servicesToKeep); + } catch (RemoteException|TimeoutException re) { + Slog.e(LOG_TAG, "Error pruning approved print services.", re); } finally { if (DEBUG) { Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() - + "] removing approved print service()"); + + "] pruneApprovedPrintServices()"); } synchronized (mLock) { mCanUnbind = true; diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java index 63d330198488..dcc02a3638e8 100644 --- a/services/print/java/com/android/server/print/UserState.java +++ b/services/print/java/com/android/server/print/UserState.java @@ -21,11 +21,9 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.AsyncTask; @@ -98,7 +96,7 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { private final List<PrintServiceInfo> mInstalledServices = new ArrayList<PrintServiceInfo>(); - private final Set<ComponentName> mEnabledServices = + private final Set<ComponentName> mDisabledServices = new ArraySet<ComponentName>(); private final PrintJobForAppCache mPrintJobForAppCache = @@ -126,8 +124,15 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { mLock = lock; mSpooler = new RemotePrintSpooler(context, userId, this); mHandler = new UserStateHandler(context.getMainLooper()); + synchronized (mLock) { - enableSystemPrintServicesLocked(); + readInstalledPrintServicesLocked(); + upgradePersistentStateIfNeeded(); + readDisabledPrintServicesLocked(); + + // Some print services might have gotten installed before the User State came up + prunePrintServices(); + onConfigurationChangedLocked(); } } @@ -320,15 +325,6 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { } } - /** - * Remove an approved print service. - * - * @param serviceToRemove The {@link ComponentName} of the service to be removed. - */ - public void removeApprovedPrintService(ComponentName serviceToRemove) { - mSpooler.removeApprovedPrintService(serviceToRemove); - } - public void restartPrintJob(PrintJobId printJobId, int appId) { PrintJobInfo printJobInfo = getPrintJobInfo(printJobId, appId); if (printJobInfo == null || printJobInfo.getState() != PrintJobInfo.STATE_FAILED) { @@ -597,13 +593,6 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { } } - public Set<ComponentName> getEnabledServices() { - synchronized(mLock) { - throwIfDestroyedLocked(); - return mEnabledServices; - } - } - public void destroyLocked() { throwIfDestroyedLocked(); mSpooler.destroy(); @@ -612,7 +601,7 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { } mActiveServices.clear(); mInstalledServices.clear(); - mEnabledServices.clear(); + mDisabledServices.clear(); if (mPrinterDiscoverySession != null) { mPrinterDiscoverySession.destroyLocked(); mPrinterDiscoverySession = null; @@ -646,12 +635,12 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { .append(installedService.getAdvancedOptionsActivityName()).println(); } - pw.append(prefix).append(tab).append("enabled services:").println(); - for (ComponentName enabledService : mEnabledServices) { - String enabledServicePrefix = prefix + tab + tab; - pw.append(enabledServicePrefix).append("service:").println(); - pw.append(enabledServicePrefix).append(tab).append("componentName=") - .append(enabledService.flattenToString()); + pw.append(prefix).append(tab).append("disabled services:").println(); + for (ComponentName disabledService : mDisabledServices) { + String disabledServicePrefix = prefix + tab + tab; + pw.append(disabledServicePrefix).append("service:").println(); + pw.append(disabledServicePrefix).append(tab).append("componentName=") + .append(disabledService.flattenToString()); pw.println(); } @@ -679,7 +668,7 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { private boolean readConfigurationLocked() { boolean somethingChanged = false; somethingChanged |= readInstalledPrintServicesLocked(); - somethingChanged |= readEnabledPrintServicesLocked(); + somethingChanged |= readDisabledPrintServicesLocked(); return somethingChanged; } @@ -742,13 +731,50 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { return false; } - private boolean readEnabledPrintServicesLocked() { - Set<ComponentName> tempEnabledServiceNameSet = new HashSet<ComponentName>(); - readPrintServicesFromSettingLocked(Settings.Secure.ENABLED_PRINT_SERVICES, - tempEnabledServiceNameSet); - if (!tempEnabledServiceNameSet.equals(mEnabledServices)) { - mEnabledServices.clear(); - mEnabledServices.addAll(tempEnabledServiceNameSet); + /** + * Update persistent state from a previous version of Android. + */ + private void upgradePersistentStateIfNeeded() { + String enabledSettingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(), + Settings.Secure.ENABLED_PRINT_SERVICES, mUserId); + + // Pre N we store the enabled services, in N and later we store the disabled services. + // Hence if enabledSettingValue is still set, we need to upgrade. + if (enabledSettingValue != null) { + Set<ComponentName> enabledServiceNameSet = new HashSet<ComponentName>(); + readPrintServicesFromSettingLocked(Settings.Secure.ENABLED_PRINT_SERVICES, + enabledServiceNameSet); + + ArraySet<ComponentName> disabledServices = new ArraySet<>(); + final int numInstalledServices = mInstalledServices.size(); + for (int i = 0; i < numInstalledServices; i++) { + ComponentName serviceName = mInstalledServices.get(i).getComponentName(); + if (!enabledServiceNameSet.contains(serviceName)) { + disabledServices.add(serviceName); + } + } + + writeDisabledPrintServicesLocked(disabledServices); + + // We won't needed ENABLED_PRINT_SERVICES anymore, set to null to prevent upgrade to run + // again. + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.ENABLED_PRINT_SERVICES, null, mUserId); + } + } + + /** + * Read the set of disabled print services from the secure settings. + * + * @return true if the state changed. + */ + private boolean readDisabledPrintServicesLocked() { + Set<ComponentName> tempDisabledServiceNameSet = new HashSet<ComponentName>(); + readPrintServicesFromSettingLocked(Settings.Secure.DISABLED_PRINT_SERVICES, + tempDisabledServiceNameSet); + if (!tempDisabledServiceNameSet.equals(mDisabledServices)) { + mDisabledServices.clear(); + mDisabledServices.addAll(tempDisabledServiceNameSet); return true; } return false; @@ -774,70 +800,28 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { } } - private void enableSystemPrintServicesLocked() { - // Load enabled and installed services. - readEnabledPrintServicesLocked(); - readInstalledPrintServicesLocked(); - - // Load the system services once enabled on first boot. - Set<ComponentName> enabledOnFirstBoot = new HashSet<ComponentName>(); - readPrintServicesFromSettingLocked( - Settings.Secure.ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES, - enabledOnFirstBoot); - + /** + * Persist the disabled print services to the secure settings. + */ + private void writeDisabledPrintServicesLocked(Set<ComponentName> disabledServices) { StringBuilder builder = new StringBuilder(); - - final int serviceCount = mInstalledServices.size(); - for (int i = 0; i < serviceCount; i++) { - ServiceInfo serviceInfo = mInstalledServices.get(i).getResolveInfo().serviceInfo; - // Enable system print services if we never did that and are not enabled. - if ((serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - ComponentName serviceName = new ComponentName( - serviceInfo.packageName, serviceInfo.name); - if (!mEnabledServices.contains(serviceName) - && !enabledOnFirstBoot.contains(serviceName)) { - if (builder.length() > 0) { - builder.append(":"); - } - builder.append(serviceName.flattenToString()); - } + for (ComponentName componentName : disabledServices) { + if (builder.length() > 0) { + builder.append(COMPONENT_NAME_SEPARATOR); } - } - - // Nothing to be enabled - done. - if (builder.length() <= 0) { - return; - } - - String servicesToEnable = builder.toString(); - - // Update the enabled services setting. - String enabledServices = Settings.Secure.getStringForUser( - mContext.getContentResolver(), Settings.Secure.ENABLED_PRINT_SERVICES, mUserId); - if (TextUtils.isEmpty(enabledServices)) { - enabledServices = servicesToEnable; - } else { - enabledServices = enabledServices + ":" + servicesToEnable; + builder.append(componentName.flattenToShortString()); } Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.ENABLED_PRINT_SERVICES, enabledServices, mUserId); - - // Update the enabled on first boot services setting. - String enabledOnFirstBootServices = Settings.Secure.getStringForUser( - mContext.getContentResolver(), - Settings.Secure.ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES, mUserId); - if (TextUtils.isEmpty(enabledOnFirstBootServices)) { - enabledOnFirstBootServices = servicesToEnable; - } else { - enabledOnFirstBootServices = enabledOnFirstBootServices + ":" + enabledServices; - } - Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES, - enabledOnFirstBootServices, mUserId); + Settings.Secure.DISABLED_PRINT_SERVICES, builder.toString(), mUserId); } - private void onConfigurationChangedLocked() { - Set<ComponentName> installedComponents = new ArraySet<ComponentName>(); + /** + * Get the {@link ComponentName names} of the installed print services + * + * @return The names of the installed print services + */ + private ArrayList<ComponentName> getInstalledComponents() { + ArrayList<ComponentName> installedComponents = new ArrayList<ComponentName>(); final int installedCount = mInstalledServices.size(); for (int i = 0; i < installedCount; i++) { @@ -846,8 +830,37 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { resolveInfo.serviceInfo.name); installedComponents.add(serviceName); + } + + return installedComponents; + } + + /** + * Prune persistent state if a print service was uninstalled + */ + public void prunePrintServices() { + synchronized (mLock) { + ArrayList<ComponentName> installedComponents = getInstalledComponents(); + + // Remove unnecessary entries from persistent state "disabled services" + boolean disabledServicesUninstalled = mDisabledServices.retainAll(installedComponents); + if (disabledServicesUninstalled) { + writeDisabledPrintServicesLocked(mDisabledServices); + } + + // Remove unnecessary entries from persistent state "approved services" + mSpooler.pruneApprovedPrintServices(installedComponents); + } + } + + private void onConfigurationChangedLocked() { + ArrayList<ComponentName> installedComponents = getInstalledComponents(); + + final int installedCount = installedComponents.size(); + for (int i = 0; i < installedCount; i++) { + ComponentName serviceName = installedComponents.get(i); - if (mEnabledServices.contains(serviceName)) { + if (!mDisabledServices.contains(serviceName)) { if (!mActiveServices.containsKey(serviceName)) { RemotePrintService service = new RemotePrintService( mContext, serviceName, mUserId, mSpooler, this); diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index 85d567d8e369..5381e4ef6bf9 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -96,11 +96,27 @@ public class MockPackageManager extends PackageManager { } @Override - public int[] getPackageGids(String packageName) throws NameNotFoundException { throw new UnsupportedOperationException(); } + @Override + public int[] getPackageGids(String packageName, int flags) throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public int getPackageUid(String packageName, int flags) throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + + /** @hide */ + @Override + public int getPackageUidAsUser(String packageName, int flags, int userHandle) + throws NameNotFoundException { + throw new UnsupportedOperationException(); + } + /** @hide */ @Override public int getPackageUidAsUser(String packageName, int userHandle) diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp index e4738f5eda7d..5ad337949bee 100644 --- a/tools/aapt/Images.cpp +++ b/tools/aapt/Images.cpp @@ -1095,13 +1095,6 @@ static void write_png(const char* imageName, analyze_image(imageName, imageInfo, grayscaleTolerance, rgbPalette, alphaPalette, &paletteEntries, &hasTransparency, &color_type, outRows); - // If the image is a 9-patch, we need to preserve it as a ARGB file to make - // sure the pixels will not be pre-dithered/clamped until we decide they are - if (imageInfo.is9Patch && (color_type == PNG_COLOR_TYPE_RGB || - color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)) { - color_type = PNG_COLOR_TYPE_RGB_ALPHA; - } - if (kIsDebug) { switch (color_type) { case PNG_COLOR_TYPE_PALETTE: @@ -1180,18 +1173,11 @@ static void write_png(const char* imageName, } for (int i = 0; i < chunk_count; i++) { - unknowns[i].location = PNG_HAVE_PLTE; + unknowns[i].location = PNG_HAVE_IHDR; } png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, chunk_names, chunk_count); png_set_unknown_chunks(write_ptr, write_info, unknowns, chunk_count); -#if PNG_LIBPNG_VER < 10600 - /* Deal with unknown chunk location bug in 1.5.x and earlier */ - png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE); - if (imageInfo.haveLayoutBounds) { - png_set_unknown_chunk_location(write_ptr, write_info, 1, PNG_HAVE_PLTE); - } -#endif } diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 0f839801ff81..a4f4ba928855 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -27,6 +27,8 @@ main := Main.cpp sources := \ compile/IdAssigner.cpp \ compile/Png.cpp \ + compile/PseudolocaleGenerator.cpp \ + compile/Pseudolocalizer.cpp \ compile/XmlIdCollector.cpp \ flatten/Archive.cpp \ flatten/TableFlattener.cpp \ @@ -66,6 +68,8 @@ sources := \ testSources := \ compile/IdAssigner_test.cpp \ + compile/PseudolocaleGenerator_test.cpp \ + compile/Pseudolocalizer_test.cpp \ compile/XmlIdCollector_test.cpp \ flatten/FileExportWriter_test.cpp \ flatten/TableFlattener_test.cpp \ diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index d864f664f9db..5fce2c16f630 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -52,13 +52,17 @@ struct PrintVisitor : public ValueVisitor { void visit(Style* style) override { std::cout << "(style)"; if (style->parent) { + const Reference& parentRef = style->parent.value(); std::cout << " parent="; - if (style->parent.value().name) { - std::cout << style->parent.value().name.value() << " "; + if (parentRef.name) { + if (parentRef.privateReference) { + std::cout << "*"; + } + std::cout << parentRef.name.value() << " "; } - if (style->parent.value().id) { - std::cout << style->parent.value().id.value(); + if (parentRef.id) { + std::cout << parentRef.id.value(); } } diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index d4c536f61c45..e1f9642d27d7 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -19,9 +19,12 @@ #include "ResourceUtils.h" #include "ResourceValues.h" #include "ValueVisitor.h" +#include "util/Comparators.h" +#include "util/ImmutableMap.h" #include "util/Util.h" #include "xml/XmlPullParser.h" +#include <functional> #include <sstream> namespace aapt { @@ -35,6 +38,111 @@ static bool shouldIgnoreElement(const StringPiece16& ns, const StringPiece16& na return ns.empty() && (name == u"skip" || name == u"eat-comment"); } +static uint32_t parseFormatType(const StringPiece16& piece) { + if (piece == u"reference") return android::ResTable_map::TYPE_REFERENCE; + else if (piece == u"string") return android::ResTable_map::TYPE_STRING; + else if (piece == u"integer") return android::ResTable_map::TYPE_INTEGER; + else if (piece == u"boolean") return android::ResTable_map::TYPE_BOOLEAN; + else if (piece == u"color") return android::ResTable_map::TYPE_COLOR; + else if (piece == u"float") return android::ResTable_map::TYPE_FLOAT; + else if (piece == u"dimension") return android::ResTable_map::TYPE_DIMENSION; + else if (piece == u"fraction") return android::ResTable_map::TYPE_FRACTION; + else if (piece == u"enum") return android::ResTable_map::TYPE_ENUM; + else if (piece == u"flags") return android::ResTable_map::TYPE_FLAGS; + return 0; +} + +static uint32_t parseFormatAttribute(const StringPiece16& str) { + uint32_t mask = 0; + for (StringPiece16 part : util::tokenize(str, u'|')) { + StringPiece16 trimmedPart = util::trimWhitespace(part); + uint32_t type = parseFormatType(trimmedPart); + if (type == 0) { + return 0; + } + mask |= type; + } + return mask; +} + +/** + * A parsed resource ready to be added to the ResourceTable. + */ +struct ParsedResource { + ResourceName name; + Source source; + ResourceId id; + Maybe<SymbolState> symbolState; + std::u16string comment; + std::unique_ptr<Value> value; + std::list<ParsedResource> childResources; +}; + +bool ResourceParser::shouldStripResource(const ResourceNameRef& name, + const Maybe<std::u16string>& product) const { + if (product) { + for (const std::u16string& productToMatch : mOptions.products) { + if (product.value() == productToMatch) { + // We specified a product, and it is in the list, so don't strip. + return false; + } + } + } + + // Nothing matched, try 'default'. Default only matches if we didn't already use another + // product variant. + if (!product || product.value() == u"default") { + if (Maybe<ResourceTable::SearchResult> result = mTable->findResource(name)) { + const ResourceEntry* entry = result.value().entry; + auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), mConfig, + cmp::lessThanConfig); + if (iter != entry->values.end() && iter->config == mConfig && !iter->value->isWeak()) { + // We have a value for this config already, and it is not weak, + // so filter out this default. + return true; + } + } + return false; + } + return true; +} + +// Recursively adds resources to the ResourceTable. +static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& config, + IDiagnostics* diag, ParsedResource* res) { + if (res->symbolState) { + Symbol symbol; + symbol.state = res->symbolState.value(); + symbol.source = res->source; + symbol.comment = res->comment; + if (!table->setSymbolState(res->name, res->id, symbol, diag)) { + return false; + } + } + + if (res->value) { + // Attach the comment, source and config to the value. + res->value->setComment(std::move(res->comment)); + res->value->setSource(std::move(res->source)); + + if (!table->addResource(res->name, res->id, config, std::move(res->value), diag)) { + return false; + } + } + + bool error = false; + for (ParsedResource& child : res->childResources) { + error |= !addResourcesToTable(table, config, diag, &child); + } + return !error; +} + +// Convenient aliases for more readable function calls. +enum { + kAllowRawString = true, + kNoRawString = false +}; + ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, const ConfigDescription& config, const ResourceParserOptions& options) : @@ -146,69 +254,6 @@ bool ResourceParser::parse(xml::XmlPullParser* parser) { return !error; } -static bool shouldStripResource(const xml::XmlPullParser* parser, - const Maybe<std::u16string> productToMatch) { - assert(parser->getEvent() == xml::XmlPullParser::Event::kStartElement); - - if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) { - if (!productToMatch) { - if (maybeProduct.value() != u"default" && maybeProduct.value() != u"phone") { - // We didn't specify a product and this is not a default product, so skip. - return true; - } - } else { - if (productToMatch && maybeProduct.value() != productToMatch.value()) { - // We specified a product, but they don't match. - return true; - } - } - } - return false; -} - -/** - * A parsed resource ready to be added to the ResourceTable. - */ -struct ParsedResource { - ResourceName name; - Source source; - ResourceId id; - Maybe<SymbolState> symbolState; - std::u16string comment; - std::unique_ptr<Value> value; - std::list<ParsedResource> childResources; -}; - -// Recursively adds resources to the ResourceTable. -static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& config, - IDiagnostics* diag, ParsedResource* res) { - if (res->symbolState) { - Symbol symbol; - symbol.state = res->symbolState.value(); - symbol.source = res->source; - symbol.comment = res->comment; - if (!table->setSymbolState(res->name, res->id, symbol, diag)) { - return false; - } - } - - if (res->value) { - // Attach the comment, source and config to the value. - res->value->setComment(std::move(res->comment)); - res->value->setSource(std::move(res->source)); - - if (!table->addResource(res->name, res->id, config, std::move(res->value), diag)) { - return false; - } - } - - bool error = false; - for (ParsedResource& child : res->childResources) { - error |= !addResourcesToTable(table, config, diag, &child); - } - return !error; -} - bool ResourceParser::parseResources(xml::XmlPullParser* parser) { std::set<ResourceName> strippedResources; @@ -244,118 +289,28 @@ bool ResourceParser::parseResources(xml::XmlPullParser* parser) { continue; } - if (elementName == u"item") { - // Items simply have their type encoded in the type attribute. - if (Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type")) { - elementName = maybeType.value().toString(); - } else { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "<item> must have a 'type' attribute"); - error = true; - continue; - } - } - ParsedResource parsedResource; parsedResource.source = mSource.withLine(parser->getLineNumber()); parsedResource.comment = std::move(comment); - if (Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name")) { - parsedResource.name.entry = maybeName.value().toString(); + // Extract the product name if it exists. + Maybe<std::u16string> product; + if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) { + product = maybeProduct.value().toString(); + } - } else if (elementName != u"public-group") { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "<" << elementName << "> tag must have a 'name' attribute"); + // Parse the resource regardless of product. + if (!parseResource(parser, &parsedResource)) { error = true; continue; } - // Check if we should skip this product. - const bool stripResource = shouldStripResource(parser, mOptions.product); - - bool result = true; - if (elementName == u"id") { - parsedResource.name.type = ResourceType::kId; - parsedResource.value = util::make_unique<Id>(); - } else if (elementName == u"string") { - parsedResource.name.type = ResourceType::kString; - result = parseString(parser, &parsedResource); - } else if (elementName == u"color") { - parsedResource.name.type = ResourceType::kColor; - result = parseColor(parser, &parsedResource); - } else if (elementName == u"drawable") { - parsedResource.name.type = ResourceType::kDrawable; - result = parseColor(parser, &parsedResource); - } else if (elementName == u"bool") { - parsedResource.name.type = ResourceType::kBool; - result = parsePrimitive(parser, &parsedResource); - } else if (elementName == u"integer") { - parsedResource.name.type = ResourceType::kInteger; - result = parsePrimitive(parser, &parsedResource); - } else if (elementName == u"dimen") { - parsedResource.name.type = ResourceType::kDimen; - result = parsePrimitive(parser, &parsedResource); - } else if (elementName == u"fraction") { - parsedResource.name.type = ResourceType::kFraction; - result = parsePrimitive(parser, &parsedResource); - } else if (elementName == u"style") { - parsedResource.name.type = ResourceType::kStyle; - result = parseStyle(parser, &parsedResource); - } else if (elementName == u"plurals") { - parsedResource.name.type = ResourceType::kPlurals; - result = parsePlural(parser, &parsedResource); - } else if (elementName == u"array") { - parsedResource.name.type = ResourceType::kArray; - result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_ANY); - } else if (elementName == u"string-array") { - parsedResource.name.type = ResourceType::kArray; - result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_STRING); - } else if (elementName == u"integer-array") { - parsedResource.name.type = ResourceType::kArray; - result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_INTEGER); - } else if (elementName == u"declare-styleable") { - parsedResource.name.type = ResourceType::kStyleable; - result = parseDeclareStyleable(parser, &parsedResource); - } else if (elementName == u"attr") { - parsedResource.name.type = ResourceType::kAttr; - result = parseAttr(parser, &parsedResource); - } else if (elementName == u"public") { - result = parsePublic(parser, &parsedResource); - } else if (elementName == u"java-symbol" || elementName == u"symbol") { - result = parseSymbol(parser, &parsedResource); - } else if (elementName == u"public-group") { - result = parsePublicGroup(parser, &parsedResource); - } else if (elementName == u"add-resource") { - result = parseAddResource(parser, &parsedResource); - } else { - // Try parsing the elementName (or type) as a resource. These shall only be - // resources like 'layout' or 'xml' and they can only be references. - if (const ResourceType* type = parseResourceType(elementName)) { - parsedResource.name.type = *type; - parsedResource.value = parseXml(parser, android::ResTable_map::TYPE_REFERENCE, - false); - if (!parsedResource.value) { - mDiag->error(DiagMessage(parsedResource.source) << "invalid value for type '" - << *type << "'. Expected a reference"); - result = false; - } - } else { - mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "unknown resource type '" << elementName << "'"); - } - } - - if (result) { - // We successfully parsed the resource. - - if (stripResource) { - // Record that we stripped out this resource name. - // We will check that at least one variant of this resource was included. - strippedResources.insert(parsedResource.name); - } else { - error |= !addResourcesToTable(mTable, mConfig, mDiag, &parsedResource); - } - } else { + // We successfully parsed the resource. Check if we should include it or strip it. + if (shouldStripResource(parsedResource.name, product)) { + // Record that we stripped out this resource name. + // We will check that at least one variant of this resource was included. + strippedResources.insert(parsedResource.name); + } else if (!addResourcesToTable(mTable, mConfig, mDiag, &parsedResource)) { error = true; } } @@ -373,10 +328,173 @@ bool ResourceParser::parseResources(xml::XmlPullParser* parser) { return !error; } -enum { - kAllowRawString = true, - kNoRawString = false -}; + +bool ResourceParser::parseResource(xml::XmlPullParser* parser, ParsedResource* outResource) { + struct ItemTypeFormat { + ResourceType type; + uint32_t format; + }; + + using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*, ParsedResource*)>; + + static const auto elToItemMap = ImmutableMap<std::u16string, ItemTypeFormat>::createPreSorted({ + { u"bool", { ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN } }, + { u"color", { ResourceType::kColor, android::ResTable_map::TYPE_COLOR } }, + { u"dimen", { ResourceType::kDimen, android::ResTable_map::TYPE_FLOAT + | android::ResTable_map::TYPE_FRACTION + | android::ResTable_map::TYPE_DIMENSION } }, + { u"drawable", { ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR } }, + { u"fraction", { ResourceType::kFraction, android::ResTable_map::TYPE_FLOAT + | android::ResTable_map::TYPE_FRACTION + | android::ResTable_map::TYPE_DIMENSION } }, + { u"integer", { ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER } }, + { u"string", { ResourceType::kString, android::ResTable_map::TYPE_STRING } }, + }); + + static const auto elToBagMap = ImmutableMap<std::u16string, BagParseFunc>::createPreSorted({ + { u"add-resource", std::mem_fn(&ResourceParser::parseAddResource) }, + { u"array", std::mem_fn(&ResourceParser::parseArray) }, + { u"attr", std::mem_fn(&ResourceParser::parseAttr) }, + { u"declare-styleable", std::mem_fn(&ResourceParser::parseDeclareStyleable) }, + { u"integer-array", std::mem_fn(&ResourceParser::parseIntegerArray) }, + { u"java-symbol", std::mem_fn(&ResourceParser::parseSymbol) }, + { u"plurals", std::mem_fn(&ResourceParser::parsePlural) }, + { u"public", std::mem_fn(&ResourceParser::parsePublic) }, + { u"public-group", std::mem_fn(&ResourceParser::parsePublicGroup) }, + { u"string-array", std::mem_fn(&ResourceParser::parseStringArray) }, + { u"style", std::mem_fn(&ResourceParser::parseStyle) }, + { u"symbol", std::mem_fn(&ResourceParser::parseSymbol) }, + }); + + std::u16string resourceType = parser->getElementName(); + + // The value format accepted for this resource. + uint32_t resourceFormat = 0u; + + if (resourceType == u"item") { + // Items have their type encoded in the type attribute. + if (Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type")) { + resourceType = maybeType.value().toString(); + } else { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "<item> must have a 'type' attribute"); + return false; + } + + if (Maybe<StringPiece16> maybeFormat = xml::findNonEmptyAttribute(parser, u"format")) { + // An explicit format for this resource was specified. The resource will retain + // its type in its name, but the accepted value for this type is overridden. + resourceFormat = parseFormatType(maybeFormat.value()); + if (!resourceFormat) { + mDiag->error(DiagMessage(outResource->source) + << "'" << maybeFormat.value() << "' is an invalid format"); + return false; + } + } + } + + // Get the name of the resource. This will be checked later, because not all + // XML elements require a name. + Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name"); + + if (resourceType == u"id") { + if (!maybeName) { + mDiag->error(DiagMessage(outResource->source) + << "<" << parser->getElementName() << "> missing 'name' attribute"); + return false; + } + + outResource->name.type = ResourceType::kId; + outResource->name.entry = maybeName.value().toString(); + outResource->value = util::make_unique<Id>(); + return true; + } + + const auto itemIter = elToItemMap.find(resourceType); + if (itemIter != elToItemMap.end()) { + // This is an item, record its type and format and start parsing. + + if (!maybeName) { + mDiag->error(DiagMessage(outResource->source) + << "<" << parser->getElementName() << "> missing 'name' attribute"); + return false; + } + + outResource->name.type = itemIter->second.type; + outResource->name.entry = maybeName.value().toString(); + + // Only use the implicit format for this type if it wasn't overridden. + if (!resourceFormat) { + resourceFormat = itemIter->second.format; + } + + if (!parseItem(parser, outResource, resourceFormat)) { + return false; + } + return true; + } + + // This might be a bag or something. + const auto bagIter = elToBagMap.find(resourceType); + if (bagIter != elToBagMap.end()) { + // Ensure we have a name (unless this is a <public-group>). + if (resourceType != u"public-group") { + if (!maybeName) { + mDiag->error(DiagMessage(outResource->source) + << "<" << parser->getElementName() << "> missing 'name' attribute"); + return false; + } + + outResource->name.entry = maybeName.value().toString(); + } + + // Call the associated parse method. The type will be filled in by the + // parse func. + if (!bagIter->second(this, parser, outResource)) { + return false; + } + return true; + } + + // Try parsing the elementName (or type) as a resource. These shall only be + // resources like 'layout' or 'xml' and they can only be references. + const ResourceType* parsedType = parseResourceType(resourceType); + if (parsedType) { + if (!maybeName) { + mDiag->error(DiagMessage(outResource->source) + << "<" << parser->getElementName() << "> missing 'name' attribute"); + return false; + } + + outResource->name.type = *parsedType; + outResource->name.entry = maybeName.value().toString(); + outResource->value = parseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString); + if (!outResource->value) { + mDiag->error(DiagMessage(outResource->source) + << "invalid value for type '" << *parsedType << "'. Expected a reference"); + return false; + } + return true; + } + + mDiag->warn(DiagMessage(outResource->source) + << "unknown resource type '" << parser->getElementName() << "'"); + return false; +} + +bool ResourceParser::parseItem(xml::XmlPullParser* parser, ParsedResource* outResource, + const uint32_t format) { + if (format == android::ResTable_map::TYPE_STRING) { + return parseString(parser, outResource); + } + + outResource->value = parseXml(parser, format, kNoRawString); + if (!outResource->value) { + mDiag->error(DiagMessage(outResource->source) << "invalid " << outResource->name.type); + return false; + } + return true; +} /** * Reads the entire XML subtree and attempts to parse it as some Item, @@ -431,17 +549,15 @@ std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const return util::make_unique<RawString>( mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig })); } - return {}; } bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) { - const Source source = mSource.withLine(parser->getLineNumber()); - bool formatted = true; if (Maybe<StringPiece16> formattedAttr = xml::findAttribute(parser, u"formatted")) { if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) { - mDiag->error(DiagMessage(source) << "invalid value for 'formatted'. Must be a boolean"); + mDiag->error(DiagMessage(outResource->source) + << "invalid value for 'formatted'. Must be a boolean"); return false; } } @@ -449,7 +565,7 @@ bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* out bool translateable = mOptions.translatable; if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) { if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) { - mDiag->error(DiagMessage(source) + mDiag->error(DiagMessage(outResource->source) << "invalid value for 'translatable'. Must be a boolean"); return false; } @@ -457,81 +573,39 @@ bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* out outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString); if (!outResource->value) { - mDiag->error(DiagMessage(source) << "not a valid string"); + mDiag->error(DiagMessage(outResource->source) << "not a valid string"); return false; } - if (formatted && translateable) { - if (String* stringValue = valueCast<String>(outResource->value.get())) { + if (String* stringValue = valueCast<String>(outResource->value.get())) { + stringValue->setTranslateable(translateable); + + if (formatted && translateable) { if (!util::verifyJavaStringFormat(*stringValue->value)) { - mDiag->error(DiagMessage(source) + mDiag->error(DiagMessage(outResource->source) << "multiple substitutions specified in non-positional format; " "did you mean to add the formatted=\"false\" attribute?"); return false; } } - } - return true; -} - -bool ResourceParser::parseColor(xml::XmlPullParser* parser, ParsedResource* outResource) { - const Source source = mSource.withLine(parser->getLineNumber()); - - outResource->value = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString); - if (!outResource->value) { - mDiag->error(DiagMessage(source) << "invalid color"); - return false; - } - return true; -} - -bool ResourceParser::parsePrimitive(xml::XmlPullParser* parser, ParsedResource* outResource) { - const Source source = mSource.withLine(parser->getLineNumber()); - - uint32_t typeMask = 0; - switch (outResource->name.type) { - case ResourceType::kInteger: - typeMask |= android::ResTable_map::TYPE_INTEGER; - break; - - case ResourceType::kFraction: - // fallthrough - case ResourceType::kDimen: - typeMask |= android::ResTable_map::TYPE_DIMENSION - | android::ResTable_map::TYPE_FLOAT - | android::ResTable_map::TYPE_FRACTION; - break; - - case ResourceType::kBool: - typeMask |= android::ResTable_map::TYPE_BOOLEAN; - break; - - default: - assert(false); - break; - } - outResource->value = parseXml(parser, typeMask, kNoRawString); - if (!outResource->value) { - mDiag->error(DiagMessage(source) << "invalid " << outResource->name.type); - return false; + } else if (StyledString* stringValue = valueCast<StyledString>(outResource->value.get())) { + stringValue->setTranslateable(translateable); } return true; } bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource) { - const Source source = mSource.withLine(parser->getLineNumber()); - Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type"); if (!maybeType) { - mDiag->error(DiagMessage(source) << "<public> must have a 'type' attribute"); + mDiag->error(DiagMessage(outResource->source) << "<public> must have a 'type' attribute"); return false; } const ResourceType* parsedType = parseResourceType(maybeType.value()); if (!parsedType) { - mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value() - << "' in <public>"); + mDiag->error(DiagMessage(outResource->source) + << "invalid resource type '" << maybeType.value() << "' in <public>"); return false; } @@ -543,8 +617,8 @@ bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* out maybeId.value().size(), &val); ResourceId resourceId(val.data); if (!result || !resourceId.isValid()) { - mDiag->error(DiagMessage(source) << "invalid resource ID '" << maybeId.value() - << "' in <public>"); + mDiag->error(DiagMessage(outResource->source) + << "invalid resource ID '" << maybeId.value() << "' in <public>"); return false; } outResource->id = resourceId; @@ -560,24 +634,24 @@ bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* out } bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource) { - const Source source = mSource.withLine(parser->getLineNumber()); - Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type"); if (!maybeType) { - mDiag->error(DiagMessage(source) << "<public-group> must have a 'type' attribute"); + mDiag->error(DiagMessage(outResource->source) + << "<public-group> must have a 'type' attribute"); return false; } const ResourceType* parsedType = parseResourceType(maybeType.value()); if (!parsedType) { - mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value() - << "' in <public-group>"); + mDiag->error(DiagMessage(outResource->source) + << "invalid resource type '" << maybeType.value() << "' in <public-group>"); return false; } Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"first-id"); if (!maybeId) { - mDiag->error(DiagMessage(source) << "<public-group> must have a 'first-id' attribute"); + mDiag->error(DiagMessage(outResource->source) + << "<public-group> must have a 'first-id' attribute"); return false; } @@ -586,8 +660,8 @@ bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource maybeId.value().size(), &val); ResourceId nextId(val.data); if (!result || !nextId.isValid()) { - mDiag->error(DiagMessage(source) << "invalid resource ID '" << maybeId.value() - << "' in <public-group>"); + mDiag->error(DiagMessage(outResource->source) + << "invalid resource ID '" << maybeId.value() << "' in <public-group>"); return false; } @@ -646,18 +720,17 @@ bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource } bool ResourceParser::parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource) { - const Source source = mSource.withLine(parser->getLineNumber()); - Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type"); if (!maybeType) { - mDiag->error(DiagMessage(source) << "<" << parser->getElementName() << "> must have a " - "'type' attribute"); + mDiag->error(DiagMessage(outResource->source) + << "<" << parser->getElementName() << "> must have a 'type' attribute"); return false; } const ResourceType* parsedType = parseResourceType(maybeType.value()); if (!parsedType) { - mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value() + mDiag->error(DiagMessage(outResource->source) + << "invalid resource type '" << maybeType.value() << "' in <" << parser->getElementName() << ">"); return false; } @@ -682,40 +755,15 @@ bool ResourceParser::parseAddResource(xml::XmlPullParser* parser, ParsedResource return false; } -static uint32_t parseFormatType(const StringPiece16& piece) { - if (piece == u"reference") return android::ResTable_map::TYPE_REFERENCE; - else if (piece == u"string") return android::ResTable_map::TYPE_STRING; - else if (piece == u"integer") return android::ResTable_map::TYPE_INTEGER; - else if (piece == u"boolean") return android::ResTable_map::TYPE_BOOLEAN; - else if (piece == u"color") return android::ResTable_map::TYPE_COLOR; - else if (piece == u"float") return android::ResTable_map::TYPE_FLOAT; - else if (piece == u"dimension") return android::ResTable_map::TYPE_DIMENSION; - else if (piece == u"fraction") return android::ResTable_map::TYPE_FRACTION; - else if (piece == u"enum") return android::ResTable_map::TYPE_ENUM; - else if (piece == u"flags") return android::ResTable_map::TYPE_FLAGS; - return 0; -} - -static uint32_t parseFormatAttribute(const StringPiece16& str) { - uint32_t mask = 0; - for (StringPiece16 part : util::tokenize(str, u'|')) { - StringPiece16 trimmedPart = util::trimWhitespace(part); - uint32_t type = parseFormatType(trimmedPart); - if (type == 0) { - return 0; - } - mask |= type; - } - return mask; -} bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) { - outResource->source = mSource.withLine(parser->getLineNumber()); return parseAttrImpl(parser, outResource, false); } bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, bool weak) { + outResource->name.type = ResourceType::kAttr; + uint32_t typeMask = 0; Maybe<StringPiece16> maybeFormat = xml::findAttribute(parser, u"format"); @@ -949,7 +997,8 @@ bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) { } bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) { - const Source source = mSource.withLine(parser->getLineNumber()); + outResource->name.type = ResourceType::kStyle; + std::unique_ptr<Style> style = util::make_unique<Style>(); Maybe<StringPiece16> maybeParent = xml::findAttribute(parser, u"parent"); @@ -959,7 +1008,7 @@ bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outR std::string errStr; style->parent = ResourceUtils::parseStyleParentReference(maybeParent.value(), &errStr); if (!style->parent) { - mDiag->error(DiagMessage(source) << errStr); + mDiag->error(DiagMessage(outResource->source) << errStr); return false; } @@ -1007,9 +1056,22 @@ bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outR return true; } -bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outResource, - uint32_t typeMask) { - const Source source = mSource.withLine(parser->getLineNumber()); +bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outResource) { + return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_ANY); +} + +bool ResourceParser::parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource) { + return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_INTEGER); +} + +bool ResourceParser::parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource) { + return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_STRING); +} + +bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, + const uint32_t typeMask) { + outResource->name.type = ResourceType::kArray; + std::unique_ptr<Array> array = util::make_unique<Array>(); bool error = false; @@ -1049,7 +1111,8 @@ bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outR } bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) { - const Source source = mSource.withLine(parser->getLineNumber()); + outResource->name.type = ResourceType::kPlurals; + std::unique_ptr<Plural> plural = util::make_unique<Plural>(); bool error = false; @@ -1123,12 +1186,13 @@ bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* out } bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource) { - const Source source = mSource.withLine(parser->getLineNumber()); - std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); + outResource->name.type = ResourceType::kStyleable; // Declare-styleable is kPrivate by default, because it technically only exists in R.java. outResource->symbolState = SymbolState::kPublic; + std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); + std::u16string comment; bool error = false; const size_t depth = parser->getDepth(); diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 04db5778a456..9ad749e27dbc 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -34,11 +34,11 @@ struct ParsedResource; struct ResourceParserOptions { /** - * Optional product name by which to filter resources. + * Optional product names by which to filter resources. * This is like a preprocessor definition in that we strip out resources * that don't match before we compile them. */ - Maybe<std::u16string> product; + std::vector<std::u16string> products; /** * Whether the default setting for this parser is to allow translation. @@ -78,9 +78,11 @@ private: const bool allowRawValue); bool parseResources(xml::XmlPullParser* parser); + bool parseResource(xml::XmlPullParser* parser, ParsedResource* outResource); + + bool parseItem(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t format); bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseColor(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parsePrimitive(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource); bool parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource); bool parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource); @@ -93,9 +95,15 @@ private: bool parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource); bool parseStyleItem(xml::XmlPullParser* parser, Style* style); bool parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask); + bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask); bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource); + bool shouldStripResource(const ResourceNameRef& name, + const Maybe<std::u16string>& product) const; + IDiagnostics* mDiag; ResourceTable* mTable; Source mSource; diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 84f67c6be005..8d10ba14924b 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -48,11 +48,11 @@ struct ResourceParserTest : public ::testing::Test { } ::testing::AssertionResult testParse(const StringPiece& str, - Maybe<std::u16string> product = {}) { + std::initializer_list<std::u16string> products = {}) { std::stringstream input(kXmlPreamble); input << "<resources>\n" << str << "\n</resources>" << std::endl; ResourceParserOptions parserOptions; - parserOptions.product = product; + parserOptions.products = products; ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {}, parserOptions); xml::XmlPullParser xmlParser(input); @@ -215,7 +215,7 @@ TEST_F(ResourceParserTest, ParseFlagAttr) { ASSERT_TRUE(testParse(input)); Attribute* flagAttr = test::getValue<Attribute>(&mTable, u"@attr/foo"); - ASSERT_NE(flagAttr, nullptr); + ASSERT_NE(nullptr, flagAttr); EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS); ASSERT_EQ(flagAttr->symbols.size(), 3u); @@ -233,7 +233,7 @@ TEST_F(ResourceParserTest, ParseFlagAttr) { std::unique_ptr<BinaryPrimitive> flagValue = ResourceUtils::tryParseFlagSymbol(flagAttr, u"baz|bat"); - ASSERT_NE(flagValue, nullptr); + ASSERT_NE(nullptr, flagValue); EXPECT_EQ(flagValue->value.data, 1u | 2u); } @@ -255,7 +255,7 @@ TEST_F(ResourceParserTest, ParseStyle) { ASSERT_TRUE(testParse(input)); Style* style = test::getValue<Style>(&mTable, u"@style/foo"); - ASSERT_NE(style, nullptr); + ASSERT_NE(nullptr, style); AAPT_ASSERT_TRUE(style->parent); AAPT_ASSERT_TRUE(style->parent.value().name); EXPECT_EQ(test::parseNameOrDie(u"@style/fu"), style->parent.value().name.value()); @@ -276,7 +276,7 @@ TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { ASSERT_TRUE(testParse(input)); Style* style = test::getValue<Style>(&mTable, u"@style/foo"); - ASSERT_NE(style, nullptr); + ASSERT_NE(nullptr, style); AAPT_ASSERT_TRUE(style->parent); AAPT_ASSERT_TRUE(style->parent.value().name); EXPECT_EQ(test::parseNameOrDie(u"@com.app:style/Theme"), style->parent.value().name.value()); @@ -288,7 +288,7 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { ASSERT_TRUE(testParse(input)); Style* style = test::getValue<Style>(&mTable, u"@style/foo"); - ASSERT_NE(style, nullptr); + ASSERT_NE(nullptr, style); AAPT_ASSERT_TRUE(style->parent); AAPT_ASSERT_TRUE(style->parent.value().name); EXPECT_EQ(test::parseNameOrDie(u"@android:style/Theme"), style->parent.value().name.value()); @@ -302,7 +302,7 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { ASSERT_TRUE(testParse(input)); Style* style = test::getValue<Style>(&mTable, u"@style/foo"); - ASSERT_NE(style, nullptr); + ASSERT_NE(nullptr, style); ASSERT_EQ(1u, style->entries.size()); EXPECT_EQ(test::parseNameOrDie(u"@android:attr/bar"), style->entries[0].key.name.value()); } @@ -312,7 +312,7 @@ TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { ASSERT_TRUE(testParse(input)); Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar"); - ASSERT_NE(style, nullptr); + ASSERT_NE(nullptr, style); AAPT_ASSERT_TRUE(style->parent); AAPT_ASSERT_TRUE(style->parent.value().name); EXPECT_EQ(style->parent.value().name.value(), test::parseNameOrDie(u"@style/foo")); @@ -324,11 +324,21 @@ TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAtt ASSERT_TRUE(testParse(input)); Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar"); - ASSERT_NE(style, nullptr); + ASSERT_NE(nullptr, style); AAPT_EXPECT_FALSE(style->parent); EXPECT_FALSE(style->parentInferred); } +TEST_F(ResourceParserTest, ParseStyleWithPrivateParentReference) { + std::string input = R"EOF(<style name="foo" parent="*android:style/bar" />)EOF"; + ASSERT_TRUE(testParse(input)); + + Style* style = test::getValue<Style>(&mTable, u"@style/foo"); + ASSERT_NE(nullptr, style); + AAPT_ASSERT_TRUE(style->parent); + EXPECT_TRUE(style->parent.value().privateReference); +} + TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { std::string input = "<string name=\"foo\">@+id/bar</string>"; ASSERT_TRUE(testParse(input)); @@ -504,11 +514,15 @@ TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { } TEST_F(ResourceParserTest, FilterProductsThatDontMatch) { - std::string input = "<string name=\"foo\" product=\"phone\">hi</string>\n" - "<string name=\"foo\" product=\"no-sdcard\">ho</string>\n" - "<string name=\"bar\" product=\"\">wee</string>\n" - "<string name=\"baz\">woo</string>\n"; - ASSERT_TRUE(testParse(input, std::u16string(u"no-sdcard"))); + std::string input = R"EOF( + <string name="foo" product="phone">hi</string> + <string name="foo" product="no-sdcard">ho</string> + <string name="bar" product="">wee</string> + <string name="baz">woo</string> + <string name="bit" product="phablet">hoot</string> + <string name="bot" product="default">yes</string> + )EOF"; + ASSERT_TRUE(testParse(input, { std::u16string(u"no-sdcard"), std::u16string(u"phablet") })); String* fooStr = test::getValue<String>(&mTable, u"@string/foo"); ASSERT_NE(nullptr, fooStr); @@ -516,11 +530,25 @@ TEST_F(ResourceParserTest, FilterProductsThatDontMatch) { EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bar")); EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/baz")); + EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bit")); + EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bot")); +} + +TEST_F(ResourceParserTest, FilterProductsThatBothMatchInOrder) { + std::string input = R"EOF( + <string name="foo" product="phone">phone</string> + <string name="foo" product="default">default</string> + )EOF"; + ASSERT_TRUE(testParse(input, { std::u16string(u"phone") })); + + String* foo = test::getValue<String>(&mTable, u"@string/foo"); + ASSERT_NE(nullptr, foo); + EXPECT_EQ(std::u16string(u"phone"), *foo->value); } TEST_F(ResourceParserTest, FailWhenProductFilterStripsOutAllVersionsOfResource) { std::string input = "<string name=\"foo\" product=\"tablet\">hello</string>\n"; - ASSERT_FALSE(testParse(input, std::u16string(u"phone"))); + ASSERT_FALSE(testParse(input, { std::u16string(u"phone") })); } TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { @@ -575,4 +603,14 @@ TEST_F(ResourceParserTest, AddResourcesElementShouldAddEntryWithUndefinedSymbol) EXPECT_EQ(SymbolState::kUndefined, entry->symbolStatus.state); } +TEST_F(ResourceParserTest, ParseItemElementWithFormat) { + std::string input = R"EOF(<item name="foo" type="integer" format="float">0.3</item>)EOF"; + ASSERT_TRUE(testParse(input)); + + BinaryPrimitive* val = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo"); + ASSERT_NE(nullptr, val); + + EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType); +} + } // namespace aapt diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 36c3e702574e..1dc123e45949 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -176,10 +176,10 @@ bool isAttributeReference(const StringPiece16& str) { /* * Style parent's are a bit different. We accept the following formats: * - * @[package:]style/<entry> - * ?[package:]style/<entry> - * <package>:[style/]<entry> - * [package:style/]<entry> + * @[[*]package:]style/<entry> + * ?[[*]package:]style/<entry> + * <[*]package>:[style/]<entry> + * [[*]package:style/]<entry> */ Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) { if (str.empty()) { @@ -195,10 +195,11 @@ Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string if (name.data()[0] == u'@' || name.data()[0] == u'?') { hasLeadingIdentifiers = true; name = name.substr(1, name.size() - 1); - if (name.data()[0] == u'*') { - privateRef = true; - name = name.substr(1, name.size() - 1); - } + } + + if (name.data()[0] == u'*') { + privateRef = true; + name = name.substr(1, name.size() - 1); } ResourceNameRef ref; diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index 4bbfc32b9b37..88efa6779021 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -157,6 +157,11 @@ TEST(ResourceUtilsTest, ParseStyleParentReference) { ref = ResourceUtils::parseStyleParentReference(u"foo", &errStr); AAPT_ASSERT_TRUE(ref); EXPECT_EQ(ref.value().name.value(), kStyleFooName); + + ref = ResourceUtils::parseStyleParentReference(u"*android:style/foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + EXPECT_TRUE(ref.value().privateReference); } } // namespace aapt diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 04c375f5f974..b93e6d889ad0 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -36,10 +36,6 @@ void BaseItem<Derived>::accept(RawValueVisitor* visitor) { visitor->visit(static_cast<Derived*>(this)); } -bool Value::isWeak() const { - return false; -} - RawString::RawString(const StringPool::Ref& ref) : value(ref) { } @@ -101,10 +97,6 @@ void Reference::print(std::ostream* out) const { } } -bool Id::isWeak() const { - return true; -} - bool Id::flatten(android::Res_value* out) const { out->dataType = android::Res_value::TYPE_INT_BOOLEAN; out->data = util::hostToDevice32(0); @@ -119,7 +111,15 @@ void Id::print(std::ostream* out) const { *out << "(id)"; } -String::String(const StringPool::Ref& ref) : value(ref) { +String::String(const StringPool::Ref& ref) : value(ref), mTranslateable(true) { +} + +void String::setTranslateable(bool val) { + mTranslateable = val; +} + +bool String::isTranslateable() const { + return mTranslateable; } bool String::flatten(android::Res_value* outValue) const { @@ -144,7 +144,15 @@ void String::print(std::ostream* out) const { *out << "(string) \"" << *value << "\""; } -StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) { +StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref), mTranslateable(true) { +} + +void StyledString::setTranslateable(bool val) { + mTranslateable = val; +} + +bool StyledString::isTranslateable() const { + return mTranslateable; } bool StyledString::flatten(android::Res_value* outValue) const { @@ -238,13 +246,10 @@ void BinaryPrimitive::print(std::ostream* out) const { } Attribute::Attribute(bool w, uint32_t t) : - weak(w), typeMask(t), + typeMask(t), minInt(std::numeric_limits<int32_t>::min()), maxInt(std::numeric_limits<int32_t>::max()) { -} - -bool Attribute::isWeak() const { - return weak; + mWeak = w; } Attribute* Attribute::clone(StringPool* /*newPool*/) const { @@ -359,7 +364,7 @@ void Attribute::print(std::ostream* out) const { << "]"; } - if (weak) { + if (isWeak()) { *out << " [weak]"; } } @@ -457,6 +462,9 @@ Style* Style::clone(StringPool* newPool) const { void Style::print(std::ostream* out) const { *out << "(style) "; if (parent && parent.value().name) { + if (parent.value().privateReference) { + *out << "*"; + } *out << parent.value().name.value(); } *out << " [" diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index a03828206c91..8e317dbcd1b1 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -43,9 +43,15 @@ struct Value { /** * Whether this value is weak and can be overridden without - * warning or error. Default for base class is false. + * warning or error. Default is false. */ - virtual bool isWeak() const; + bool isWeak() const { + return mWeak; + } + + void setWeak(bool val) { + mWeak = val; + } /** * Returns the source where this value was defined. @@ -95,6 +101,7 @@ struct Value { protected: Source mSource; std::u16string mComment; + bool mWeak = false; }; /** @@ -159,7 +166,7 @@ struct Reference : public BaseItem<Reference> { * An ID resource. Has no real value, just a place holder. */ struct Id : public BaseItem<Id> { - bool isWeak() const override; + Id() { mWeak = true; } bool flatten(android::Res_value* out) const override; Id* clone(StringPool* newPool) const override; void print(std::ostream* out) const override; @@ -185,9 +192,17 @@ struct String : public BaseItem<String> { String(const StringPool::Ref& ref); + // Whether the string is marked as translateable. This does not persist when flattened. + // It is only used during compilation phase. + void setTranslateable(bool val); + bool isTranslateable() const; + bool flatten(android::Res_value* outValue) const override; String* clone(StringPool* newPool) const override; void print(std::ostream* out) const override; + +private: + bool mTranslateable; }; struct StyledString : public BaseItem<StyledString> { @@ -195,9 +210,17 @@ struct StyledString : public BaseItem<StyledString> { StyledString(const StringPool::StyleRef& ref); + // Whether the string is marked as translateable. This does not persist when flattened. + // It is only used during compilation phase. + void setTranslateable(bool val); + bool isTranslateable() const; + bool flatten(android::Res_value* outValue) const override; StyledString* clone(StringPool* newPool) const override; void print(std::ostream* out) const override; + +private: + bool mTranslateable; }; struct FileReference : public BaseItem<FileReference> { @@ -232,7 +255,6 @@ struct Attribute : public BaseValue<Attribute> { uint32_t value; }; - bool weak; uint32_t typeMask; int32_t minInt; int32_t maxInt; @@ -240,7 +262,6 @@ struct Attribute : public BaseValue<Attribute> { Attribute(bool w, uint32_t t = 0u); - bool isWeak() const override; Attribute* clone(StringPool* newPool) const override; void printMask(std::ostream* out) const; void print(std::ostream* out) const override; diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp index 90e35d52788c..b3b0f65e54da 100644 --- a/tools/aapt2/compile/Compile.cpp +++ b/tools/aapt2/compile/Compile.cpp @@ -21,6 +21,7 @@ #include "ResourceTable.h" #include "compile/IdAssigner.h" #include "compile/Png.h" +#include "compile/PseudolocaleGenerator.h" #include "compile/XmlIdCollector.h" #include "flatten/Archive.h" #include "flatten/FileExportWriter.h" @@ -104,7 +105,8 @@ static Maybe<ResourcePathData> extractResourcePathData(const std::string& path, struct CompileOptions { std::string outputPath; Maybe<std::string> resDir; - Maybe<std::u16string> product; + std::vector<std::u16string> products; + bool pseudolocalize = false; bool verbose = false; }; @@ -189,7 +191,7 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options, xml::XmlPullParser xmlParser(fin); ResourceParserOptions parserOptions; - parserOptions.product = options.product; + parserOptions.products = options.products; // If the filename includes donottranslate, then the default translatable is false. parserOptions.translatable = pathData.name.find(u"donottranslate") == std::string::npos; @@ -203,6 +205,16 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options, fin.close(); } + if (options.pseudolocalize) { + // Generate pseudo-localized strings (en-XA and ar-XB). + // These are created as weak symbols, and are only generated from default configuration + // strings and plurals. + PseudolocaleGenerator pseudolocaleGenerator; + if (!pseudolocaleGenerator.consume(context, &table)) { + return false; + } + } + // Ensure we have the compilation package at least. table.createPackage(context->getCompilationPackage()); @@ -418,18 +430,23 @@ public: int compile(const std::vector<StringPiece>& args) { CompileOptions options; - Maybe<std::string> product; + Maybe<std::string> productList; Flags flags = Flags() .requiredFlag("-o", "Output path", &options.outputPath) - .optionalFlag("--product", "Product type to compile", &product) + .optionalFlag("--product", "Comma separated list of product types to compile", + &productList) .optionalFlag("--dir", "Directory to scan for resources", &options.resDir) + .optionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales " + "(en-XA and ar-XB)", &options.pseudolocalize) .optionalSwitch("-v", "Enables verbose logging", &options.verbose); if (!flags.parse("aapt2 compile", args, &std::cerr)) { return 1; } - if (product) { - options.product = util::utf8ToUtf16(product.value()); + if (productList) { + for (StringPiece part : util::tokenize<char>(productList.value(), ',')) { + options.products.push_back(util::utf8ToUtf16(part)); + } } CompileContext context; diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp new file mode 100644 index 000000000000..2963d135cbca --- /dev/null +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ResourceTable.h" +#include "ResourceValues.h" +#include "ValueVisitor.h" +#include "compile/PseudolocaleGenerator.h" +#include "compile/Pseudolocalizer.h" +#include "util/Comparators.h" + +namespace aapt { + +std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string, + Pseudolocalizer::Method method, + StringPool* pool) { + Pseudolocalizer localizer(method); + + const StringPiece16 originalText = *string->value->str; + + StyleString localized; + + // Copy the spans. We will update their offsets when we localize. + localized.spans.reserve(string->value->spans.size()); + for (const StringPool::Span& span : string->value->spans) { + localized.spans.push_back(Span{ *span.name, span.firstChar, span.lastChar }); + } + + // The ranges are all represented with a single value. This is the start of one range and + // end of another. + struct Range { + size_t start; + + // Once the new string is localized, these are the pointers to the spans to adjust. + // Since this struct represents the start of one range and end of another, we have + // the two pointers respectively. + uint32_t* updateStart; + uint32_t* updateEnd; + }; + + auto cmp = [](const Range& r, size_t index) -> bool { + return r.start < index; + }; + + // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7] + // The ranges are the spaces in between. In this example, with a total string length of 9, + // the vector represents: (0,1], (2,4], (5,6], (7,9] + // + std::vector<Range> ranges; + ranges.push_back(Range{ 0 }); + ranges.push_back(Range{ originalText.size() - 1 }); + for (size_t i = 0; i < string->value->spans.size(); i++) { + const StringPool::Span& span = string->value->spans[i]; + + // Insert or update the Range marker for the start of this span. + auto iter = std::lower_bound(ranges.begin(), ranges.end(), span.firstChar, cmp); + if (iter != ranges.end() && iter->start == span.firstChar) { + iter->updateStart = &localized.spans[i].firstChar; + } else { + ranges.insert(iter, + Range{ span.firstChar, &localized.spans[i].firstChar, nullptr }); + } + + // Insert or update the Range marker for the end of this span. + iter = std::lower_bound(ranges.begin(), ranges.end(), span.lastChar, cmp); + if (iter != ranges.end() && iter->start == span.lastChar) { + iter->updateEnd = &localized.spans[i].lastChar; + } else { + ranges.insert(iter, + Range{ span.lastChar, nullptr, &localized.spans[i].lastChar }); + } + } + + localized.str += localizer.start(); + + // Iterate over the ranges and localize each section. + for (size_t i = 0; i < ranges.size(); i++) { + const size_t start = ranges[i].start; + size_t len = originalText.size() - start; + if (i + 1 < ranges.size()) { + len = ranges[i + 1].start - start; + } + + if (ranges[i].updateStart) { + *ranges[i].updateStart = localized.str.size(); + } + + if (ranges[i].updateEnd) { + *ranges[i].updateEnd = localized.str.size(); + } + + localized.str += localizer.text(originalText.substr(start, len)); + } + + localized.str += localizer.end(); + + std::unique_ptr<StyledString> localizedString = util::make_unique<StyledString>( + pool->makeRef(localized)); + localizedString->setSource(string->getSource()); + return localizedString; +} + +namespace { + +struct Visitor : public RawValueVisitor { + StringPool* mPool; + Pseudolocalizer::Method mMethod; + Pseudolocalizer mLocalizer; + + // Either value or item will be populated upon visiting the value. + std::unique_ptr<Value> mValue; + std::unique_ptr<Item> mItem; + + Visitor(StringPool* pool, Pseudolocalizer::Method method) : + mPool(pool), mMethod(method), mLocalizer(method) { + } + + void visit(Array* array) override { + std::unique_ptr<Array> localized = util::make_unique<Array>(); + localized->items.resize(array->items.size()); + for (size_t i = 0; i < array->items.size(); i++) { + Visitor subVisitor(mPool, mMethod); + array->items[i]->accept(&subVisitor); + if (subVisitor.mItem) { + localized->items[i] = std::move(subVisitor.mItem); + } else { + localized->items[i] = std::unique_ptr<Item>(array->items[i]->clone(mPool)); + } + } + localized->setSource(array->getSource()); + localized->setWeak(true); + mValue = std::move(localized); + } + + void visit(Plural* plural) override { + std::unique_ptr<Plural> localized = util::make_unique<Plural>(); + for (size_t i = 0; i < plural->values.size(); i++) { + Visitor subVisitor(mPool, mMethod); + if (plural->values[i]) { + plural->values[i]->accept(&subVisitor); + if (subVisitor.mValue) { + localized->values[i] = std::move(subVisitor.mItem); + } else { + localized->values[i] = std::unique_ptr<Item>(plural->values[i]->clone(mPool)); + } + } + } + localized->setSource(plural->getSource()); + localized->setWeak(true); + mValue = std::move(localized); + } + + void visit(String* string) override { + if (!string->isTranslateable()) { + return; + } + + std::u16string result = mLocalizer.start() + mLocalizer.text(*string->value) + + mLocalizer.end(); + std::unique_ptr<String> localized = util::make_unique<String>(mPool->makeRef(result)); + localized->setSource(string->getSource()); + localized->setWeak(true); + mItem = std::move(localized); + } + + void visit(StyledString* string) override { + if (!string->isTranslateable()) { + return; + } + + mItem = pseudolocalizeStyledString(string, mMethod, mPool); + mItem->setWeak(true); + } +}; + +ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base, + Pseudolocalizer::Method m) { + ConfigDescription modified = base; + switch (m) { + case Pseudolocalizer::Method::kAccent: + modified.language[0] = 'e'; + modified.language[1] = 'n'; + modified.country[0] = 'X'; + modified.country[1] = 'A'; + break; + + case Pseudolocalizer::Method::kBidi: + modified.language[0] = 'a'; + modified.language[1] = 'r'; + modified.country[0] = 'X'; + modified.country[1] = 'B'; + break; + default: + break; + } + return modified; +} + +void pseudolocalizeIfNeeded(std::vector<ResourceConfigValue>* configValues, + Pseudolocalizer::Method method, StringPool* pool, Value* value) { + Visitor visitor(pool, method); + value->accept(&visitor); + + std::unique_ptr<Value> localizedValue; + if (visitor.mValue) { + localizedValue = std::move(visitor.mValue); + } else if (visitor.mItem) { + localizedValue = std::move(visitor.mItem); + } + + if (localizedValue) { + ConfigDescription pseudolocalizedConfig = modifyConfigForPseudoLocale(ConfigDescription{}, + method); + auto iter = std::lower_bound(configValues->begin(), configValues->end(), + pseudolocalizedConfig, cmp::lessThanConfig); + if (iter == configValues->end() || iter->config != pseudolocalizedConfig) { + // The pseudolocalized config doesn't exist, add it. + configValues->insert(iter, ResourceConfigValue{ pseudolocalizedConfig, + std::move(localizedValue) }); + } + } +} + +} // namespace + +bool PseudolocaleGenerator::consume(IAaptContext* context, ResourceTable* table) { + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), + ConfigDescription{}, cmp::lessThanConfig); + if (iter != entry->values.end() && iter->config == ConfigDescription{}) { + // Only pseudolocalize the default configuration. + + // The iterator will be invalidated, so grab a pointer to the value. + Value* originalValue = iter->value.get(); + + pseudolocalizeIfNeeded(&entry->values, Pseudolocalizer::Method::kAccent, + &table->stringPool, originalValue); + pseudolocalizeIfNeeded(&entry->values, Pseudolocalizer::Method::kBidi, + &table->stringPool, originalValue); + } + } + } + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/compile/PseudolocaleGenerator.h b/tools/aapt2/compile/PseudolocaleGenerator.h new file mode 100644 index 000000000000..4fbc51607595 --- /dev/null +++ b/tools/aapt2/compile/PseudolocaleGenerator.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H +#define AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H + +#include "StringPool.h" +#include "compile/Pseudolocalizer.h" +#include "process/IResourceTableConsumer.h" + +namespace aapt { + +std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string, + Pseudolocalizer::Method method, + StringPool* pool); + +struct PseudolocaleGenerator : public IResourceTableConsumer { + bool consume(IAaptContext* context, ResourceTable* table) override; +}; + +} // namespace aapt + +#endif /* AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H */ diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp new file mode 100644 index 000000000000..4cb6ea2db565 --- /dev/null +++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compile/PseudolocaleGenerator.h" +#include "test/Builders.h" +#include "test/Common.h" +#include "test/Context.h" +#include "util/Util.h" + +#include <androidfw/ResourceTypes.h> +#include <gtest/gtest.h> + +namespace aapt { + +TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { + StringPool pool; + StyleString originalStyle; + originalStyle.str = u"Hello world!"; + originalStyle.spans = { Span{ u"b", 2, 3 }, Span{ u"b", 6, 7 }, Span{ u"i", 1, 10 } }; + + std::unique_ptr<StyledString> newString = pseudolocalizeStyledString( + util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(), + Pseudolocalizer::Method::kNone, &pool); + + EXPECT_EQ(originalStyle.str, *newString->value->str); + ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size()); + + EXPECT_EQ(2u, newString->value->spans[0].firstChar); + EXPECT_EQ(3u, newString->value->spans[0].lastChar); + EXPECT_EQ(std::u16string(u"b"), *newString->value->spans[0].name); + + EXPECT_EQ(6u, newString->value->spans[1].firstChar); + EXPECT_EQ(7u, newString->value->spans[1].lastChar); + EXPECT_EQ(std::u16string(u"b"), *newString->value->spans[1].name); + + EXPECT_EQ(1u, newString->value->spans[2].firstChar); + EXPECT_EQ(10u, newString->value->spans[2].lastChar); + EXPECT_EQ(std::u16string(u"i"), *newString->value->spans[2].name); + + originalStyle.spans.push_back(Span{ u"em", 0, 11u }); + + newString = pseudolocalizeStyledString( + util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(), + Pseudolocalizer::Method::kAccent, &pool); + + EXPECT_EQ(std::u16string(u"[Ĥéļļö ŵöŕļð¡ one two]"), *newString->value->str); + ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size()); + + EXPECT_EQ(3u, newString->value->spans[0].firstChar); + EXPECT_EQ(4u, newString->value->spans[0].lastChar); + + EXPECT_EQ(7u, newString->value->spans[1].firstChar); + EXPECT_EQ(8u, newString->value->spans[1].lastChar); + + EXPECT_EQ(2u, newString->value->spans[2].firstChar); + EXPECT_EQ(11u, newString->value->spans[2].lastChar); + + EXPECT_EQ(1u, newString->value->spans[3].firstChar); + EXPECT_EQ(12u, newString->value->spans[3].lastChar); +} + +TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .addString(u"@android:string/one", u"one") + .addString(u"@android:string/two", ResourceId{}, test::parseConfigOrDie("en"), u"two") + .addString(u"@android:string/three", u"three") + .addString(u"@android:string/three", ResourceId{}, test::parseConfigOrDie("en-rXA"), + u"three") + .addString(u"@android:string/four", u"four") + .build(); + + String* val = test::getValue<String>(table.get(), u"@android:string/four"); + val->setTranslateable(false); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + PseudolocaleGenerator generator; + ASSERT_TRUE(generator.consume(context.get(), table.get())); + + // Normal pseudolocalization should take place. + ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/one", + test::parseConfigOrDie("en-rXA"))); + ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/one", + test::parseConfigOrDie("ar-rXB"))); + + // No default config for android:string/two, so no pseudlocales should exist. + ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/two", + test::parseConfigOrDie("en-rXA"))); + ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/two", + test::parseConfigOrDie("ar-rXB"))); + + + // Check that we didn't override manual pseudolocalization. + val = test::getValueForConfig<String>(table.get(), u"@android:string/three", + test::parseConfigOrDie("en-rXA")); + ASSERT_NE(nullptr, val); + EXPECT_EQ(std::u16string(u"three"), *val->value); + + ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/three", + test::parseConfigOrDie("ar-rXB"))); + + // Check that four's translateable marker was honored. + ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/four", + test::parseConfigOrDie("en-rXA"))); + ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), u"@android:string/four", + test::parseConfigOrDie("ar-rXB"))); + +} + +} // namespace aapt + diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp new file mode 100644 index 000000000000..eae52d778744 --- /dev/null +++ b/tools/aapt2/compile/Pseudolocalizer.cpp @@ -0,0 +1,394 @@ +/* + * 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. + */ + +#include "compile/Pseudolocalizer.h" +#include "util/Util.h" + +namespace aapt { + +// String basis to generate expansion +static const std::u16string k_expansion_string = u"one two three " + "four five six seven eight nine ten eleven twelve thirteen " + "fourteen fiveteen sixteen seventeen nineteen twenty"; + +// Special unicode characters to override directionality of the words +static const std::u16string k_rlm = u"\u200f"; +static const std::u16string k_rlo = u"\u202e"; +static const std::u16string k_pdf = u"\u202c"; + +// Placeholder marks +static const std::u16string k_placeholder_open = u"\u00bb"; +static const std::u16string k_placeholder_close = u"\u00ab"; + +static const char16_t k_arg_start = u'{'; +static const char16_t k_arg_end = u'}'; + +class PseudoMethodNone : public PseudoMethodImpl { +public: + std::u16string text(const StringPiece16& text) override { return text.toString(); } + std::u16string placeholder(const StringPiece16& text) override { return text.toString(); } +}; + +class PseudoMethodBidi : public PseudoMethodImpl { +public: + std::u16string text(const StringPiece16& text) override; + std::u16string placeholder(const StringPiece16& text) override; +}; + +class PseudoMethodAccent : public PseudoMethodImpl { +public: + PseudoMethodAccent() : mDepth(0), mWordCount(0), mLength(0) {} + std::u16string start() override; + std::u16string end() override; + std::u16string text(const StringPiece16& text) override; + std::u16string placeholder(const StringPiece16& text) override; +private: + size_t mDepth; + size_t mWordCount; + size_t mLength; +}; + +Pseudolocalizer::Pseudolocalizer(Method method) : mLastDepth(0) { + setMethod(method); +} + +void Pseudolocalizer::setMethod(Method method) { + switch (method) { + case Method::kNone: + mImpl = util::make_unique<PseudoMethodNone>(); + break; + case Method::kAccent: + mImpl = util::make_unique<PseudoMethodAccent>(); + break; + case Method::kBidi: + mImpl = util::make_unique<PseudoMethodBidi>(); + break; + } +} + +std::u16string Pseudolocalizer::text(const StringPiece16& text) { + std::u16string out; + size_t depth = mLastDepth; + size_t lastpos, pos; + const size_t length = text.size(); + const char16_t* str = text.data(); + bool escaped = false; + for (lastpos = pos = 0; pos < length; pos++) { + char16_t c = str[pos]; + if (escaped) { + escaped = false; + continue; + } + if (c == '\'') { + escaped = true; + continue; + } + + if (c == k_arg_start) { + depth++; + } else if (c == k_arg_end && depth) { + depth--; + } + + if (mLastDepth != depth || pos == length - 1) { + bool pseudo = ((mLastDepth % 2) == 0); + size_t nextpos = pos; + if (!pseudo || depth == mLastDepth) { + nextpos++; + } + size_t size = nextpos - lastpos; + if (size) { + std::u16string chunk = text.substr(lastpos, size).toString(); + if (pseudo) { + chunk = mImpl->text(chunk); + } else if (str[lastpos] == k_arg_start && str[nextpos - 1] == k_arg_end) { + chunk = mImpl->placeholder(chunk); + } + out.append(chunk); + } + if (pseudo && depth < mLastDepth) { // End of message + out.append(mImpl->end()); + } else if (!pseudo && depth > mLastDepth) { // Start of message + out.append(mImpl->start()); + } + lastpos = nextpos; + mLastDepth = depth; + } + } + return out; +} + +static const char16_t* pseudolocalizeChar(const char16_t c) { + switch (c) { + case 'a': return u"\u00e5"; + case 'b': return u"\u0253"; + case 'c': return u"\u00e7"; + case 'd': return u"\u00f0"; + case 'e': return u"\u00e9"; + case 'f': return u"\u0192"; + case 'g': return u"\u011d"; + case 'h': return u"\u0125"; + case 'i': return u"\u00ee"; + case 'j': return u"\u0135"; + case 'k': return u"\u0137"; + case 'l': return u"\u013c"; + case 'm': return u"\u1e3f"; + case 'n': return u"\u00f1"; + case 'o': return u"\u00f6"; + case 'p': return u"\u00fe"; + case 'q': return u"\u0051"; + case 'r': return u"\u0155"; + case 's': return u"\u0161"; + case 't': return u"\u0163"; + case 'u': return u"\u00fb"; + case 'v': return u"\u0056"; + case 'w': return u"\u0175"; + case 'x': return u"\u0445"; + case 'y': return u"\u00fd"; + case 'z': return u"\u017e"; + case 'A': return u"\u00c5"; + case 'B': return u"\u03b2"; + case 'C': return u"\u00c7"; + case 'D': return u"\u00d0"; + case 'E': return u"\u00c9"; + case 'G': return u"\u011c"; + case 'H': return u"\u0124"; + case 'I': return u"\u00ce"; + case 'J': return u"\u0134"; + case 'K': return u"\u0136"; + case 'L': return u"\u013b"; + case 'M': return u"\u1e3e"; + case 'N': return u"\u00d1"; + case 'O': return u"\u00d6"; + case 'P': return u"\u00de"; + case 'Q': return u"\u0071"; + case 'R': return u"\u0154"; + case 'S': return u"\u0160"; + case 'T': return u"\u0162"; + case 'U': return u"\u00db"; + case 'V': return u"\u03bd"; + case 'W': return u"\u0174"; + case 'X': return u"\u00d7"; + case 'Y': return u"\u00dd"; + case 'Z': return u"\u017d"; + case '!': return u"\u00a1"; + case '?': return u"\u00bf"; + case '$': return u"\u20ac"; + default: return NULL; + } +} + +static bool isPossibleNormalPlaceholderEnd(const char16_t c) { + switch (c) { + case 's': return true; + case 'S': return true; + case 'c': return true; + case 'C': return true; + case 'd': return true; + case 'o': return true; + case 'x': return true; + case 'X': return true; + case 'f': return true; + case 'e': return true; + case 'E': return true; + case 'g': return true; + case 'G': return true; + case 'a': return true; + case 'A': return true; + case 'b': return true; + case 'B': return true; + case 'h': return true; + case 'H': return true; + case '%': return true; + case 'n': return true; + default: return false; + } +} + +static std::u16string pseudoGenerateExpansion(const unsigned int length) { + std::u16string result = k_expansion_string; + const char16_t* s = result.data(); + if (result.size() < length) { + result += u" "; + result += pseudoGenerateExpansion(length - result.size()); + } else { + int ext = 0; + // Should contain only whole words, so looking for a space + for (unsigned int i = length + 1; i < result.size(); ++i) { + ++ext; + if (s[i] == ' ') { + break; + } + } + result = result.substr(0, length + ext); + } + return result; +} + +std::u16string PseudoMethodAccent::start() { + std::u16string result; + if (mDepth == 0) { + result = u"["; + } + mWordCount = mLength = 0; + mDepth++; + return result; +} + +std::u16string PseudoMethodAccent::end() { + std::u16string result; + if (mLength) { + result += u" "; + result += pseudoGenerateExpansion(mWordCount > 3 ? mLength : mLength / 2); + } + mWordCount = mLength = 0; + mDepth--; + if (mDepth == 0) { + result += u"]"; + } + return result; +} + +/** + * Converts characters so they look like they've been localized. + * + * Note: This leaves placeholder syntax untouched. + */ +std::u16string PseudoMethodAccent::text(const StringPiece16& source) +{ + const char16_t* s = source.data(); + std::u16string result; + const size_t I = source.size(); + bool lastspace = true; + for (size_t i = 0; i < I; i++) { + char16_t c = s[i]; + if (c == '%') { + // Placeholder syntax, no need to pseudolocalize + std::u16string chunk; + bool end = false; + chunk.append(&c, 1); + while (!end && i < I) { + ++i; + c = s[i]; + chunk.append(&c, 1); + if (isPossibleNormalPlaceholderEnd(c)) { + end = true; + } else if (c == 't') { + ++i; + c = s[i]; + chunk.append(&c, 1); + end = true; + } + } + // Treat chunk as a placeholder unless it ends with %. + result += ((c == '%') ? chunk : placeholder(chunk)); + } else if (c == '<' || c == '&') { + // html syntax, no need to pseudolocalize + bool tag_closed = false; + while (!tag_closed && i < I) { + if (c == '&') { + std::u16string escapeText; + escapeText.append(&c, 1); + bool end = false; + size_t htmlCodePos = i; + while (!end && htmlCodePos < I) { + ++htmlCodePos; + c = s[htmlCodePos]; + escapeText.append(&c, 1); + // Valid html code + if (c == ';') { + end = true; + i = htmlCodePos; + } + // Wrong html code + else if (!((c == '#' || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9')))) { + end = true; + } + } + result += escapeText; + if (escapeText != u"<") { + tag_closed = true; + } + continue; + } + if (c == '>') { + tag_closed = true; + result.append(&c, 1); + continue; + } + result.append(&c, 1); + i++; + c = s[i]; + } + } else { + // This is a pure text that should be pseudolocalized + const char16_t* p = pseudolocalizeChar(c); + if (p != nullptr) { + result += p; + } else { + bool space = util::isspace16(c); + if (lastspace && !space) { + mWordCount++; + } + lastspace = space; + result.append(&c, 1); + } + // Count only pseudolocalizable chars and delimiters + mLength++; + } + } + return result; +} + +std::u16string PseudoMethodAccent::placeholder(const StringPiece16& source) { + // Surround a placeholder with brackets + return k_placeholder_open + source.toString() + k_placeholder_close; +} + +std::u16string PseudoMethodBidi::text(const StringPiece16& source) { + const char16_t* s = source.data(); + std::u16string result; + bool lastspace = true; + bool space = true; + for (size_t i = 0; i < source.size(); i++) { + char16_t c = s[i]; + space = util::isspace16(c); + if (lastspace && !space) { + // Word start + result += k_rlm + k_rlo; + } else if (!lastspace && space) { + // Word end + result += k_pdf + k_rlm; + } + lastspace = space; + result.append(&c, 1); + } + if (!lastspace) { + // End of last word + result += k_pdf + k_rlm; + } + return result; +} + +std::u16string PseudoMethodBidi::placeholder(const StringPiece16& source) { + // Surround a placeholder with directionality change sequence + return k_rlm + k_rlo + source.toString() + k_pdf + k_rlm; +} + +} // namespace aapt diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h new file mode 100644 index 000000000000..8818c1725617 --- /dev/null +++ b/tools/aapt2/compile/Pseudolocalizer.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#ifndef AAPT_COMPILE_PSEUDOLOCALIZE_H +#define AAPT_COMPILE_PSEUDOLOCALIZE_H + +#include "ResourceValues.h" +#include "StringPool.h" +#include "util/StringPiece.h" + +#include <android-base/macros.h> +#include <memory> + +namespace aapt { + +class PseudoMethodImpl { +public: + virtual ~PseudoMethodImpl() {} + virtual std::u16string start() { return {}; } + virtual std::u16string end() { return {}; } + virtual std::u16string text(const StringPiece16& text) = 0; + virtual std::u16string placeholder(const StringPiece16& text) = 0; +}; + +class Pseudolocalizer { +public: + enum class Method { + kNone, + kAccent, + kBidi, + }; + + Pseudolocalizer(Method method); + void setMethod(Method method); + std::u16string start() { return mImpl->start(); } + std::u16string end() { return mImpl->end(); } + std::u16string text(const StringPiece16& text); +private: + std::unique_ptr<PseudoMethodImpl> mImpl; + size_t mLastDepth; +}; + +} // namespace aapt + +#endif /* AAPT_COMPILE_PSEUDOLOCALIZE_H */ diff --git a/tools/aapt2/compile/Pseudolocalizer_test.cpp b/tools/aapt2/compile/Pseudolocalizer_test.cpp new file mode 100644 index 000000000000..b0bc2c10fbe0 --- /dev/null +++ b/tools/aapt2/compile/Pseudolocalizer_test.cpp @@ -0,0 +1,227 @@ +/* + * 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. + */ + +#include "compile/Pseudolocalizer.h" +#include "util/Util.h" + +#include <androidfw/ResourceTypes.h> +#include <gtest/gtest.h> + +namespace aapt { + +// In this context, 'Axis' represents a particular field in the configuration, +// such as language or density. + +static ::testing::AssertionResult simpleHelper(const char* input, const char* expected, + Pseudolocalizer::Method method) { + Pseudolocalizer pseudo(method); + std::string result = util::utf16ToUtf8( + pseudo.start() + pseudo.text(util::utf8ToUtf16(input)) + pseudo.end()); + if (StringPiece(expected) != result) { + return ::testing::AssertionFailure() << expected << " != " << result; + } + return ::testing::AssertionSuccess(); +} + +static ::testing::AssertionResult compoundHelper(const char* in1, const char* in2, const char *in3, + const char* expected, + Pseudolocalizer::Method method) { + Pseudolocalizer pseudo(method); + std::string result = util::utf16ToUtf8(pseudo.start() + + pseudo.text(util::utf8ToUtf16(in1)) + + pseudo.text(util::utf8ToUtf16(in2)) + + pseudo.text(util::utf8ToUtf16(in3)) + + pseudo.end()); + if (StringPiece(expected) != result) { + return ::testing::AssertionFailure() << expected << " != " << result; + } + return ::testing::AssertionSuccess(); +} + +TEST(PseudolocalizerTest, NoPseudolocalization) { + EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kNone)); + EXPECT_TRUE(simpleHelper("Hello, world", "Hello, world", Pseudolocalizer::Method::kNone)); + + EXPECT_TRUE(compoundHelper("Hello,", " world", "", + "Hello, world", Pseudolocalizer::Method::kNone)); +} + +TEST(PseudolocalizerTest, PlaintextAccent) { + EXPECT_TRUE(simpleHelper("", "[]", Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(simpleHelper("Hello, world", + "[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE(simpleHelper("Hello, %1d", + "[Ĥéļļö, »%1d« one two]", Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE(simpleHelper("Battery %1d%%", + "[βåţţéŕý »%1d«%% one two]", Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE(compoundHelper("", "", "", "[]", Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(compoundHelper("Hello,", " world", "", + "[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent)); +} + +TEST(PseudolocalizerTest, PlaintextBidi) { + EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(simpleHelper("word", + "\xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(simpleHelper(" word ", + " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(simpleHelper(" word ", + " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(simpleHelper("hello\n world\n", + "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \ + " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(compoundHelper("hello", "\n ", " world\n", + "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \ + " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n", + Pseudolocalizer::Method::kBidi)); +} + +TEST(PseudolocalizerTest, SimpleICU) { + // Single-fragment messages + EXPECT_TRUE(simpleHelper("{placeholder}", "[»{placeholder}«]", + Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(simpleHelper("{USER} is offline", + "[»{USER}« îš öƒƒļîñé one two]", Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(simpleHelper("Copy from {path1} to {path2}", + "[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]", + Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(simpleHelper("Today is {1,date} {1,time}", + "[Ţöðåý îš »{1,date}« »{1,time}« one two]", + Pseudolocalizer::Method::kAccent)); + + // Multi-fragment messages + EXPECT_TRUE(compoundHelper("{USER}", " ", "is offline", + "[»{USER}« îš öƒƒļîñé one two]", + Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(compoundHelper("Copy from ", "{path1}", " to {path2}", + "[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]", + Pseudolocalizer::Method::kAccent)); +} + +TEST(PseudolocalizerTest, ICUBidi) { + // Single-fragment messages + EXPECT_TRUE(simpleHelper("{placeholder}", + "\xe2\x80\x8f\xE2\x80\xae{placeholder}\xE2\x80\xac\xe2\x80\x8f", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(simpleHelper( + "{COUNT, plural, one {one} other {other}}", + "{COUNT, plural, " \ + "one {\xe2\x80\x8f\xE2\x80\xaeone\xE2\x80\xac\xe2\x80\x8f} " \ + "other {\xe2\x80\x8f\xE2\x80\xaeother\xE2\x80\xac\xe2\x80\x8f}}", + Pseudolocalizer::Method::kBidi)); +} + +TEST(PseudolocalizerTest, Escaping) { + // Single-fragment messages + EXPECT_TRUE(simpleHelper("'{USER'} is offline", + "['{ÛŠÉŔ'} îš öƒƒļîñé one two three]", + Pseudolocalizer::Method::kAccent)); + + // Multi-fragment messages + EXPECT_TRUE(compoundHelper("'{USER}", " ", "''is offline", + "['{ÛŠÉŔ} ''îš öƒƒļîñé one two three]", + Pseudolocalizer::Method::kAccent)); +} + +TEST(PseudolocalizerTest, PluralsAndSelects) { + EXPECT_TRUE(simpleHelper( + "{COUNT, plural, one {Delete a file} other {Delete {COUNT} files}}", + "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " \ + "other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]", + Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE(simpleHelper( + "Distance is {COUNT, plural, one {# mile} other {# miles}}", + "[Ðîšţåñçé îš {COUNT, plural, one {# ḿîļé one two} " \ + "other {# ḿîļéš one two}}]", + Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE(simpleHelper( + "{1, select, female {{1} added you} " \ + "male {{1} added you} other {{1} added you}}", + "[{1, select, female {»{1}« åððéð ýöû one two} " \ + "male {»{1}« åððéð ýöû one two} other {»{1}« åððéð ýöû one two}}]", + Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE(compoundHelper( + "{COUNT, plural, one {Delete a file} " \ + "other {Delete ", "{COUNT}", " files}}", + "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " \ + "other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]", + Pseudolocalizer::Method::kAccent)); +} + +TEST(PseudolocalizerTest, NestedICU) { + EXPECT_TRUE(simpleHelper( + "{person, select, " \ + "female {" \ + "{num_circles, plural," \ + "=0{{person} didn't add you to any of her circles.}" \ + "=1{{person} added you to one of her circles.}" \ + "other{{person} added you to her # circles.}}}" \ + "male {" \ + "{num_circles, plural," \ + "=0{{person} didn't add you to any of his circles.}" \ + "=1{{person} added you to one of his circles.}" \ + "other{{person} added you to his # circles.}}}" \ + "other {" \ + "{num_circles, plural," \ + "=0{{person} didn't add you to any of their circles.}" \ + "=1{{person} added you to one of their circles.}" \ + "other{{person} added you to their # circles.}}}}", + "[{person, select, " \ + "female {" \ + "{num_circles, plural," \ + "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥéŕ çîŕçļéš." \ + " one two three four five}" \ + "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥéŕ çîŕçļéš." \ + " one two three four}" \ + "other{»{person}« åððéð ýöû ţö ĥéŕ # çîŕçļéš." \ + " one two three four}}}" \ + "male {" \ + "{num_circles, plural," \ + "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥîš çîŕçļéš." \ + " one two three four five}" \ + "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥîš çîŕçļéš." \ + " one two three four}" \ + "other{»{person}« åððéð ýöû ţö ĥîš # çîŕçļéš." \ + " one two three four}}}" \ + "other {{num_circles, plural," \ + "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ţĥéîŕ çîŕçļéš." \ + " one two three four five}" \ + "=1{»{person}« åððéð ýöû ţö öñé öƒ ţĥéîŕ çîŕçļéš." \ + " one two three four}" \ + "other{»{person}« åððéð ýöû ţö ţĥéîŕ # çîŕçļéš." \ + " one two three four}}}}]", + Pseudolocalizer::Method::kAccent)); +} + +TEST(PseudolocalizerTest, RedefineMethod) { + Pseudolocalizer pseudo(Pseudolocalizer::Method::kAccent); + std::u16string result = pseudo.text(u"Hello, "); + pseudo.setMethod(Pseudolocalizer::Method::kNone); + result += pseudo.text(u"world!"); + ASSERT_EQ(StringPiece("Ĥéļļö, world!"), util::utf16ToUtf8(result)); +} + +} // namespace aapt diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp index a2f53e15df69..26d7c2ca055c 100644 --- a/tools/aapt2/flatten/TableFlattener.cpp +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -113,8 +113,7 @@ struct MapFlattenVisitor : public RawValueVisitor { bool mUseExtendedChunks; size_t mEntryCount = 0; - Maybe<uint32_t> mParentIdent; - Maybe<ResourceNameRef> mParentName; + const Reference* mParent = nullptr; MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer, StringPool* sourcePool, StringPool* commentPool, @@ -227,13 +226,8 @@ struct MapFlattenVisitor : public RawValueVisitor { void visit(Style* style) override { if (style->parent) { - bool privateRef = style->parent.value().privateReference && mUseExtendedChunks; - if (!style->parent.value().id || privateRef) { - assert(style->parent.value().name && "reference must have a name"); - mParentName = style->parent.value().name; - } else { - mParentIdent = style->parent.value().id.value().id; - } + // Parents are treated a bit differently, so record the existence and move on. + mParent = &style->parent.value(); } // Sort the style. @@ -427,11 +421,16 @@ private: mOptions.useExtendedChunks); entry->value->accept(&visitor); outEntry->count = util::hostToDevice32(visitor.mEntryCount); - if (visitor.mParentName) { - mSymbols->addSymbol(visitor.mParentName.value(), - beforeEntry + offsetof(ResTable_entry_ext, parent)); - } else if (visitor.mParentIdent) { - outEntry->parent.ident = util::hostToDevice32(visitor.mParentIdent.value()); + if (visitor.mParent) { + const bool forceSymbol = visitor.mParent->privateReference && + mOptions.useExtendedChunks; + if (!visitor.mParent->id || forceSymbol) { + assert(visitor.mParent->name && "reference must have a name"); + mSymbols->addSymbol(*visitor.mParent, + beforeEntry + offsetof(ResTable_entry_ext, parent)); + } else { + outEntry->parent.ident = util::hostToDevice32(visitor.mParent->id.value().id); + } } } return true; diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index c0968545ad81..c610bb0f2ff2 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -227,14 +227,14 @@ bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) { for (const auto& entry : keepSet.mKeepSet) { for (const Source& source : entry.second) { - *out << "// Referenced at " << source << "\n"; + *out << "# Referenced at " << source << "\n"; } *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl; } for (const auto& entry : keepSet.mKeepMethodSet) { for (const Source& source : entry.second) { - *out << "// Referenced at " << source << "\n"; + *out << "# Referenced at " << source << "\n"; } *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl; } diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index f8e3d031fb67..93a11b9334a8 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -68,6 +68,12 @@ public: return addValue(name, id, util::make_unique<String>(mTable->stringPool.makeRef(str))); } + ResourceTableBuilder& addString(const StringPiece16& name, const ResourceId id, + const ConfigDescription& config, const StringPiece16& str) { + return addValue(name, id, config, + util::make_unique<String>(mTable->stringPool.makeRef(str))); + } + ResourceTableBuilder& addFileReference(const StringPiece16& name, const StringPiece16& path) { return addFileReference(name, {}, path); } diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp index 21e476fc9c29..6b7a63cf7bf2 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.cpp +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -585,6 +585,13 @@ bool BinaryResourceParser::parseType(const ResourceTablePackage* package, source.path = path.toString(); } source.line = util::deviceToHost32(sourceBlock->line); + + if (Style* style = valueCast<Style>(resourceValue.get())) { + // The parent's source is the same as the resource itself, set it here. + if (style->parent) { + style->parent.value().setSource(source); + } + } } StringPiece16 comment = util::getString(mSourcePool, diff --git a/tools/aapt2/util/ImmutableMap.h b/tools/aapt2/util/ImmutableMap.h new file mode 100644 index 000000000000..b1f9e9d2fb57 --- /dev/null +++ b/tools/aapt2/util/ImmutableMap.h @@ -0,0 +1,84 @@ +/* + * 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. + */ + +#ifndef AAPT_UTIL_IMMUTABLEMAP_H +#define AAPT_UTIL_IMMUTABLEMAP_H + +#include "util/TypeTraits.h" + +#include <utility> +#include <vector> + +namespace aapt { + +template <typename TKey, typename TValue> +class ImmutableMap { + static_assert(is_comparable<TKey, TKey>::value, "key is not comparable"); + +private: + std::vector<std::pair<TKey, TValue>> mData; + + explicit ImmutableMap(std::vector<std::pair<TKey, TValue>> data) : mData(std::move(data)) { + } + +public: + using const_iterator = typename decltype(mData)::const_iterator; + + ImmutableMap(ImmutableMap&&) = default; + ImmutableMap& operator=(ImmutableMap&&) = default; + + ImmutableMap(const ImmutableMap&) = delete; + ImmutableMap& operator=(const ImmutableMap&) = delete; + + static ImmutableMap<TKey, TValue> createPreSorted( + std::initializer_list<std::pair<TKey, TValue>> list) { + return ImmutableMap(std::vector<std::pair<TKey, TValue>>(list.begin(), list.end())); + } + + static ImmutableMap<TKey, TValue> createAndSort( + std::initializer_list<std::pair<TKey, TValue>> list) { + std::vector<std::pair<TKey, TValue>> data(list.begin(), list.end()); + std::sort(data.begin(), data.end()); + return ImmutableMap(std::move(data)); + } + + template <typename TKey2, + typename = typename std::enable_if<is_comparable<TKey, TKey2>::value>::type> + const_iterator find(const TKey2& key) const { + auto cmp = [](const std::pair<TKey, TValue>& candidate, const TKey2& target) -> bool { + return candidate.first < target; + }; + + const_iterator endIter = end(); + auto iter = std::lower_bound(mData.begin(), endIter, key, cmp); + if (iter == endIter || iter->first == key) { + return iter; + } + return endIter; + } + + const_iterator begin() const { + return mData.begin(); + } + + const_iterator end() const { + return mData.end(); + } +}; + +} // namespace aapt + +#endif /* AAPT_UTIL_IMMUTABLEMAP_H */ diff --git a/tools/aapt2/util/TypeTraits.h b/tools/aapt2/util/TypeTraits.h new file mode 100644 index 000000000000..76c13d615e41 --- /dev/null +++ b/tools/aapt2/util/TypeTraits.h @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#ifndef AAPT_UTIL_TYPETRAITS_H +#define AAPT_UTIL_TYPETRAITS_H + +#include <type_traits> + +namespace aapt { + +#define DEFINE_HAS_BINARY_OP_TRAIT(name, op) \ + template <typename T, typename U> \ + struct name { \ + template <typename V, typename W> \ + static constexpr decltype(std::declval<V>() op std::declval<W>(), bool()) test(int) { \ + return true; \ + } \ + template <typename V, typename W> \ + static constexpr bool test(...) { \ + return false; \ + } \ + static constexpr bool value = test<T, U>(int()); \ +} + +DEFINE_HAS_BINARY_OP_TRAIT(has_eq_op, ==); +DEFINE_HAS_BINARY_OP_TRAIT(has_lt_op, <); + +/** + * Type trait that checks if two types can be equated (==) and compared (<). + */ +template <typename T, typename U> +struct is_comparable { + static constexpr bool value = has_eq_op<T, U>::value && has_lt_op<T, U>::value; +}; + +} // namespace aapt + +#endif /* AAPT_UTIL_TYPETRAITS_H */ diff --git a/tools/layoutlib/bridge/src/android/os/ServiceManager.java b/tools/layoutlib/bridge/src/android/os/ServiceManager.java index 6a68ee29c9cb..549074d15757 100644 --- a/tools/layoutlib/bridge/src/android/os/ServiceManager.java +++ b/tools/layoutlib/bridge/src/android/os/ServiceManager.java @@ -51,8 +51,10 @@ public final class ServiceManager { /** * Return a list of all currently running services. + * @return an array of all currently running services, or <code>null</code> in + * case of an exception */ - public static String[] listServices() throws RemoteException { + public static String[] listServices() { // actual implementation returns null sometimes, so it's ok // to return null instead of an empty list. return null; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java index 27751eb7d267..4625de25d200 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java @@ -97,11 +97,26 @@ public class BridgePackageManager extends PackageManager { } @Override + public int[] getPackageGids(String packageName, int flags) throws NameNotFoundException { + return new int[0]; + } + + @Override + public int getPackageUid(String packageName, int flags) throws NameNotFoundException { + return 0; + } + + @Override public int getPackageUidAsUser(String packageName, int userHandle) throws NameNotFoundException { return 0; } @Override + public int getPackageUidAsUser(String packageName, int flags, int userHandle) throws NameNotFoundException { + return 0; + } + + @Override public PermissionInfo getPermissionInfo(String name, int flags) throws NameNotFoundException { return null; } |