summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--api/current.txt7
-rw-r--r--api/system-current.txt7
-rw-r--r--api/test-current.txt7
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java11
-rw-r--r--core/java/android/app/ActivityManager.java2
-rw-r--r--core/java/android/app/BackStackRecord.java76
-rw-r--r--core/java/android/app/ContextImpl.java41
-rw-r--r--core/java/android/app/Notification.java579
-rw-r--r--core/java/android/content/Context.java26
-rw-r--r--core/java/android/content/ContextWrapper.java5
-rw-r--r--core/java/android/content/Intent.java8
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java12
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl2
-rw-r--r--core/java/android/content/pm/PackageParser.java16
-rw-r--r--core/java/android/hardware/input/InputManagerInternal.java13
-rw-r--r--core/java/android/os/AsyncTask.java19
-rw-r--r--core/java/android/os/UserManager.java3
-rw-r--r--core/java/android/provider/Settings.java38
-rw-r--r--core/java/android/security/net/config/ApplicationConfig.java14
-rw-r--r--core/java/android/security/net/config/CertificateSource.java1
-rw-r--r--core/java/android/security/net/config/CertificatesEntryRef.java13
-rw-r--r--core/java/android/security/net/config/DirectoryCertificateSource.java139
-rw-r--r--core/java/android/security/net/config/KeyStoreCertificateSource.java25
-rw-r--r--core/java/android/security/net/config/ManifestConfigSource.java100
-rw-r--r--core/java/android/security/net/config/NetworkSecurityConfig.java34
-rw-r--r--core/java/android/security/net/config/NetworkSecurityConfigProvider.java38
-rw-r--r--core/java/android/security/net/config/NetworkSecurityTrustManager.java10
-rw-r--r--core/java/android/security/net/config/ResourceCertificateSource.java35
-rw-r--r--core/java/android/security/net/config/SystemCertificateSource.java70
-rw-r--r--core/java/android/security/net/config/UserCertificateSource.java59
-rw-r--r--core/java/android/util/jar/StrictJarFile.java427
-rw-r--r--core/java/android/util/jar/StrictJarManifest.java315
-rw-r--r--core/java/android/util/jar/StrictJarManifestReader.java184
-rw-r--r--core/java/android/util/jar/StrictJarVerifier.java456
-rw-r--r--core/java/android/view/IDockDividerVisibilityListener.aidl27
-rw-r--r--core/java/android/view/IWindowManager.aidl10
-rw-r--r--core/java/android/view/NotificationHeaderView.java250
-rw-r--r--core/java/android/view/View.java1
-rw-r--r--core/java/android/widget/RemoteViews.java57
-rw-r--r--core/java/android/widget/TextView.java10
-rw-r--r--core/java/com/android/internal/logging/MetricsLogger.java1
-rw-r--r--core/java/com/android/internal/widget/ImageFloatingTextView.java89
-rw-r--r--core/jni/Android.mk1
-rw-r--r--core/jni/AndroidRuntime.cpp4
-rw-r--r--core/jni/android_util_jar_StrictJarFile.cpp172
-rw-r--r--core/res/AndroidManifest.xml5
-rw-r--r--core/res/res/drawable/ic_arrow_drop_down.xml25
-rw-r--r--core/res/res/drawable/ic_arrow_up_14dp.xml24
-rw-r--r--core/res/res/layout/notification_material_action.xml2
-rw-r--r--core/res/res/layout/notification_material_action_list.xml7
-rw-r--r--core/res/res/layout/notification_material_media_action.xml10
-rw-r--r--core/res/res/layout/notification_template_header.xml132
-rw-r--r--core/res/res/layout/notification_template_icon_group.xml45
-rw-r--r--core/res/res/layout/notification_template_material_base.xml24
-rw-r--r--core/res/res/layout/notification_template_material_big_base.xml93
-rw-r--r--core/res/res/layout/notification_template_material_big_media.xml45
-rw-r--r--core/res/res/layout/notification_template_material_big_media_narrow.xml63
-rw-r--r--core/res/res/layout/notification_template_material_big_picture.xml72
-rw-r--r--core/res/res/layout/notification_template_material_big_text.xml42
-rw-r--r--core/res/res/layout/notification_template_material_inbox.xml66
-rw-r--r--core/res/res/layout/notification_template_material_media.xml68
-rw-r--r--core/res/res/layout/notification_template_part_chronometer.xml4
-rw-r--r--core/res/res/layout/notification_template_part_line1.xml25
-rw-r--r--core/res/res/layout/notification_template_part_line2.xml58
-rw-r--r--core/res/res/layout/notification_template_part_line3.xml11
-rw-r--r--core/res/res/layout/notification_template_part_time.xml3
-rw-r--r--core/res/res/layout/notification_template_progress.xml (renamed from core/res/res/drawable/notification_icon_legacy_bg.xml)16
-rw-r--r--core/res/res/layout/notification_template_right_icon.xml27
-rw-r--r--core/res/res/values/colors.xml5
-rw-r--r--core/res/res/values/dimens.xml27
-rw-r--r--core/res/res/values/strings.xml6
-rw-r--r--core/res/res/values/symbols.xml32
-rw-r--r--core/tests/coretests/src/android/widget/TextViewActivityTest.java37
-rw-r--r--graphics/java/android/graphics/drawable/RippleBackground.java1
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java18
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java39
-rw-r--r--packages/Shell/AndroidManifest.xml1
-rw-r--r--packages/Shell/res/values/strings.xml2
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java410
-rw-r--r--packages/Shell/src/com/android/shell/BugreportReceiver.java9
-rw-r--r--packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java53
-rw-r--r--packages/Shell/tests/src/com/android/shell/UiBot.java33
-rw-r--r--packages/SystemUI/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/res/drawable/notification_expand_more.xml2
-rw-r--r--packages/SystemUI/res/layout/notification_header.xml83
-rw-r--r--packages/SystemUI/res/layout/recents_history.xml10
-rw-r--r--packages/SystemUI/res/layout/recents_history_button.xml4
-rw-r--r--packages/SystemUI/res/layout/status_bar_notification_row.xml8
-rw-r--r--packages/SystemUI/res/values/colors.xml1
-rw-r--r--packages/SystemUI/res/values/dimens.xml21
-rw-r--r--packages/SystemUI/res/values/strings.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/ExpandHelper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/DragView.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java143
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java102
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java164
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java130
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigMediaNarrowViewWrapper.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigPictureViewWrapper.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java170
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderView.java209
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java2
-rw-r--r--preloaded-classes11
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java29
-rw-r--r--services/core/java/com/android/server/InputMethodManagerService.java16
-rw-r--r--services/core/java/com/android/server/LockSettingsService.java11
-rw-r--r--services/core/java/com/android/server/MountService.java18
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java6
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java26
-rw-r--r--services/core/java/com/android/server/am/UserController.java26
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java36
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java2
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java3
-rw-r--r--services/core/java/com/android/server/media/MediaSessionStack.java38
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java46
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java6
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java2
-rw-r--r--services/core/java/com/android/server/wm/DockedStackDividerController.java32
-rw-r--r--services/core/java/com/android/server/wm/Task.java45
-rw-r--r--services/core/java/com/android/server/wm/TaskStack.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java54
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java5
-rw-r--r--test-runner/src/android/test/mock/MockContext.java5
-rw-r--r--tests/NetworkSecurityConfigTest/res/xml/override_dedup.xml20
-rw-r--r--tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java14
-rw-r--r--tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java18
-rw-r--r--tools/aapt/Resource.cpp20
-rw-r--r--tools/aapt/ResourceTable.cpp246
-rw-r--r--tools/aapt/ResourceTable.h24
-rw-r--r--tools/aapt/XMLNode.cpp18
-rw-r--r--tools/aapt/XMLNode.h4
-rw-r--r--tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java8
167 files changed, 5333 insertions, 2272 deletions
diff --git a/Android.mk b/Android.mk
index f5d5a113ff0a..d22273c6b75d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -261,6 +261,7 @@ LOCAL_SRC_FILES += \
core/java/android/view/IApplicationToken.aidl \
core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \
core/java/android/view/IAssetAtlas.aidl \
+ core/java/android/view/IDockDividerVisibilityListener.aidl \
core/java/android/view/IGraphicsStats.aidl \
core/java/android/view/IInputFilter.aidl \
core/java/android/view/IInputFilterHost.aidl \
diff --git a/api/current.txt b/api/current.txt
index 5153106eb2a6..59b8fb3c0383 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7809,6 +7809,7 @@ package android.content {
method public abstract java.lang.String getPackageResourcePath();
method public abstract android.content.res.Resources getResources();
method public abstract android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public final java.lang.String getString(int);
method public final java.lang.String getString(int, java.lang.Object...);
method public abstract java.lang.Object getSystemService(java.lang.String);
@@ -7991,6 +7992,7 @@ package android.content {
method public java.lang.String getPackageResourcePath();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
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();
@@ -8344,6 +8346,7 @@ package android.content {
field public static final java.lang.String ACTION_USER_FOREGROUND = "android.intent.action.USER_FOREGROUND";
field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
+ field public static final java.lang.String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
@@ -31403,7 +31406,10 @@ package android.provider {
public static final class Telephony.Sms.Intents {
method public static android.telephony.SmsMessage[] getMessagesFromIntent(android.content.Intent);
field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+ field public static final java.lang.String ACTION_DEFAULT_SMS_PACKAGE_CHANGED = "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+ field public static final java.lang.String ACTION_EXTERNAL_PROVIDER_CHANGE = "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
field public static final java.lang.String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";
+ field public static final java.lang.String EXTRA_IS_DEFAULT_SMS_APP = "android.provider.extra.IS_DEFAULT_SMS_APP";
field public static final java.lang.String EXTRA_PACKAGE_NAME = "package";
field public static final int RESULT_SMS_DUPLICATED = 5; // 0x5
field public static final int RESULT_SMS_GENERIC_ERROR = 2; // 0x2
@@ -36197,6 +36203,7 @@ package android.test.mock {
method public java.lang.String getPackageResourcePath();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
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();
diff --git a/api/system-current.txt b/api/system-current.txt
index 0e80c8f669de..f02956867093 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8052,6 +8052,7 @@ package android.content {
method public abstract java.lang.String getPackageResourcePath();
method public abstract android.content.res.Resources getResources();
method public abstract android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public final java.lang.String getString(int);
method public final java.lang.String getString(int, java.lang.Object...);
method public abstract java.lang.Object getSystemService(java.lang.String);
@@ -8243,6 +8244,7 @@ package android.content {
method public java.lang.String getPackageResourcePath();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
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();
@@ -8602,6 +8604,7 @@ package android.content {
field public static final java.lang.String ACTION_USER_FOREGROUND = "android.intent.action.USER_FOREGROUND";
field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
+ field public static final java.lang.String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
@@ -33519,7 +33522,10 @@ package android.provider {
public static final class Telephony.Sms.Intents {
method public static android.telephony.SmsMessage[] getMessagesFromIntent(android.content.Intent);
field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+ field public static final java.lang.String ACTION_DEFAULT_SMS_PACKAGE_CHANGED = "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+ field public static final java.lang.String ACTION_EXTERNAL_PROVIDER_CHANGE = "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
field public static final java.lang.String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";
+ field public static final java.lang.String EXTRA_IS_DEFAULT_SMS_APP = "android.provider.extra.IS_DEFAULT_SMS_APP";
field public static final java.lang.String EXTRA_PACKAGE_NAME = "package";
field public static final int RESULT_SMS_DUPLICATED = 5; // 0x5
field public static final int RESULT_SMS_GENERIC_ERROR = 2; // 0x2
@@ -38512,6 +38518,7 @@ package android.test.mock {
method public java.lang.String getPackageResourcePath();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
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();
diff --git a/api/test-current.txt b/api/test-current.txt
index 5153106eb2a6..59b8fb3c0383 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -7809,6 +7809,7 @@ package android.content {
method public abstract java.lang.String getPackageResourcePath();
method public abstract android.content.res.Resources getResources();
method public abstract android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public final java.lang.String getString(int);
method public final java.lang.String getString(int, java.lang.Object...);
method public abstract java.lang.Object getSystemService(java.lang.String);
@@ -7991,6 +7992,7 @@ package android.content {
method public java.lang.String getPackageResourcePath();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
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();
@@ -8344,6 +8346,7 @@ package android.content {
field public static final java.lang.String ACTION_USER_FOREGROUND = "android.intent.action.USER_FOREGROUND";
field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
+ field public static final java.lang.String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
@@ -31403,7 +31406,10 @@ package android.provider {
public static final class Telephony.Sms.Intents {
method public static android.telephony.SmsMessage[] getMessagesFromIntent(android.content.Intent);
field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+ field public static final java.lang.String ACTION_DEFAULT_SMS_PACKAGE_CHANGED = "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+ field public static final java.lang.String ACTION_EXTERNAL_PROVIDER_CHANGE = "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
field public static final java.lang.String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";
+ field public static final java.lang.String EXTRA_IS_DEFAULT_SMS_APP = "android.provider.extra.IS_DEFAULT_SMS_APP";
field public static final java.lang.String EXTRA_PACKAGE_NAME = "package";
field public static final int RESULT_SMS_DUPLICATED = 5; // 0x5
field public static final int RESULT_SMS_GENERIC_ERROR = 2; // 0x2
@@ -36197,6 +36203,7 @@ package android.test.mock {
method public java.lang.String getPackageResourcePath();
method public android.content.res.Resources getResources();
method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+ method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
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();
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 273483a6db8a..468c145a698b 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -178,15 +178,8 @@ import java.util.Set;
* </p>
* <h3>Notification strategy</h3>
* <p>
- * For each feedback type only one accessibility service is notified. Services are notified
- * in the order of registration. Hence, if two services are registered for the same
- * feedback type in the same package the first one wins. It is possible however, to
- * register a service as the default one for a given feedback type. In such a case this
- * service is invoked if no other service was interested in the event. In other words, default
- * services do not compete with other services and are notified last regardless of the
- * registration order. This enables "generic" accessibility services that work reasonably
- * well with most applications to coexist with "polished" ones that are targeted for
- * specific applications.
+ * All accessibility services are notified of all events they have requested, regardless of their
+ * feedback type.
* </p>
* <p class="note">
* <strong>Note:</strong> The event notification timeout is useful to avoid propagating
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f7aee759f3b9..f1a7de81d5ac 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3084,7 +3084,7 @@ public class ActivityManager {
/** {@hide} */
public static final int FLAG_OR_STOPPED = 1 << 0;
/** {@hide} */
- public static final int FLAG_WITH_AMNESIA = 1 << 1;
+ public static final int FLAG_AND_LOCKED = 1 << 1;
/**
* Return whether the given user is actively running. This means that
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index 9081ef8a1d1a..b24bce3107d1 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -798,21 +798,33 @@ final class BackStackRecord extends FragmentTransaction implements
}
}
- private static void setFirstOut(SparseArray<Fragment> fragments, Fragment fragment) {
+ private static void setFirstOut(SparseArray<Fragment> firstOutFragments,
+ SparseArray<Fragment> lastInFragments, Fragment fragment) {
if (fragment != null) {
int containerId = fragment.mContainerId;
- if (containerId != 0 && !fragment.isHidden() && fragment.isAdded() &&
- fragment.getView() != null && fragments.get(containerId) == null) {
- fragments.put(containerId, fragment);
+ if (containerId != 0 && !fragment.isHidden()) {
+ if (fragment.isAdded() && fragment.getView() != null
+ && firstOutFragments.get(containerId) == null) {
+ firstOutFragments.put(containerId, fragment);
+ }
+ if (lastInFragments.get(containerId) == fragment) {
+ lastInFragments.remove(containerId);
+ }
}
}
}
- private void setLastIn(SparseArray<Fragment> fragments, Fragment fragment) {
+ private void setLastIn(SparseArray<Fragment> firstOutFragments,
+ SparseArray<Fragment> lastInFragments, Fragment fragment) {
if (fragment != null) {
int containerId = fragment.mContainerId;
if (containerId != 0) {
- fragments.put(containerId, fragment);
+ if (!fragment.isAdded()) {
+ lastInFragments.put(containerId, fragment);
+ }
+ if (firstOutFragments.get(containerId) == fragment) {
+ firstOutFragments.remove(containerId);
+ }
}
}
}
@@ -835,7 +847,7 @@ final class BackStackRecord extends FragmentTransaction implements
while (op != null) {
switch (op.cmd) {
case OP_ADD:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_REPLACE: {
Fragment f = op.fragment;
@@ -845,29 +857,30 @@ final class BackStackRecord extends FragmentTransaction implements
if (f == null || old.mContainerId == f.mContainerId) {
if (old == f) {
f = null;
+ lastInFragments.remove(old.mContainerId);
} else {
- setFirstOut(firstOutFragments, old);
+ setFirstOut(firstOutFragments, lastInFragments, old);
}
}
}
}
- setLastIn(lastInFragments, f);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
}
case OP_REMOVE:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_HIDE:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_SHOW:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_DETACH:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_ATTACH:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
}
@@ -889,38 +902,38 @@ final class BackStackRecord extends FragmentTransaction implements
if (!mManager.mContainer.onHasView()) {
return; // nothing to see, so no transitions
}
- Op op = mHead;
+ Op op = mTail;
while (op != null) {
switch (op.cmd) {
case OP_ADD:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_REPLACE:
if (op.removed != null) {
for (int i = op.removed.size() - 1; i >= 0; i--) {
- setLastIn(lastInFragments, op.removed.get(i));
+ setLastIn(firstOutFragments, lastInFragments, op.removed.get(i));
}
}
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_REMOVE:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_HIDE:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_SHOW:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_DETACH:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_ATTACH:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
}
- op = op.next;
+ op = op.prev;
}
}
@@ -957,6 +970,7 @@ final class BackStackRecord extends FragmentTransaction implements
*/
private TransitionState beginTransition(SparseArray<Fragment> firstOutFragments,
SparseArray<Fragment> lastInFragments, boolean isBack) {
+ ensureFragmentsAreInitialized(lastInFragments);
TransitionState state = new TransitionState();
// Adding a non-existent target view makes sure that the transitions don't target
@@ -982,6 +996,20 @@ final class BackStackRecord extends FragmentTransaction implements
return state;
}
+ /**
+ * Ensure that fragments that are entering are at least at the CREATED state
+ * so that they may load Transitions using TransitionInflater.
+ */
+ private void ensureFragmentsAreInitialized(SparseArray<Fragment> lastInFragments) {
+ final int count = lastInFragments.size();
+ for (int i = 0; i < count; i++) {
+ final Fragment fragment = lastInFragments.valueAt(i);
+ if (fragment.mState < Fragment.CREATED) {
+ mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
+ }
+ }
+ }
+
private static Transition cloneTransition(Transition transition) {
if (transition != null) {
transition = transition.clone();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index bc7c3d0a3211..c661107e3397 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -129,7 +129,7 @@ class ContextImpl extends Context {
/**
* Map from package name, to preference name, to cached preferences.
*/
- private static ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>> sSharedPrefs;
+ private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefs;
final ActivityThread mMainThread;
final LoadedApk mPackageInfo;
@@ -327,34 +327,39 @@ class ContextImpl extends Context {
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
+ // At least one application in the world actually passes in a null
+ // name. This happened to work because when we generated the file name
+ // we would stringify it to "null.xml". Nice.
+ if (mPackageInfo.getApplicationInfo().targetSdkVersion <
+ Build.VERSION_CODES.KITKAT) {
+ if (name == null) {
+ name = "null";
+ }
+ }
+
+ final File file = getSharedPrefsFile(name);
+ return getSharedPreferences(file, mode);
+ }
+
+ @Override
+ public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
if (sSharedPrefs == null) {
- sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
+ sSharedPrefs = new ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>>();
}
final String packageName = getPackageName();
- ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
+ ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
if (packagePrefs == null) {
- packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
+ packagePrefs = new ArrayMap<File, SharedPreferencesImpl>();
sSharedPrefs.put(packageName, packagePrefs);
}
- // At least one application in the world actually passes in a null
- // name. This happened to work because when we generated the file name
- // we would stringify it to "null.xml". Nice.
- if (mPackageInfo.getApplicationInfo().targetSdkVersion <
- Build.VERSION_CODES.KITKAT) {
- if (name == null) {
- name = "null";
- }
- }
-
- sp = packagePrefs.get(name);
+ sp = packagePrefs.get(file);
if (sp == null) {
- File prefsFile = getSharedPrefsFile(name);
- sp = new SharedPreferencesImpl(prefsFile, mode);
- packagePrefs.put(name, sp);
+ sp = new SharedPreferencesImpl(file, mode);
+ packagePrefs.put(file, sp);
return sp;
}
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 74634a9f2b39..6c0c3e864877 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -25,6 +25,7 @@ import android.annotation.SystemApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
@@ -45,7 +46,6 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
-import android.util.MathUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
@@ -58,7 +58,6 @@ import com.android.internal.util.NotificationColorUtil;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
-import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -2969,7 +2968,6 @@ public class Notification implements Parcelable
Bitmap profileBadge = getProfileBadge();
contentView.setViewVisibility(R.id.profile_badge_large_template, View.GONE);
- contentView.setViewVisibility(R.id.profile_badge_line2, View.GONE);
contentView.setViewVisibility(R.id.profile_badge_line3, View.GONE);
if (profileBadge != null) {
@@ -2986,38 +2984,35 @@ public class Notification implements Parcelable
return false;
}
- private void shrinkLine3Text(RemoteViews contentView) {
- float subTextSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_subtext_size);
- contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize);
- }
-
- private void unshrinkLine3Text(RemoteViews contentView) {
- float regularTextSize = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_text_size);
- contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, regularTextSize);
- }
-
private void resetStandardTemplate(RemoteViews contentView) {
- removeLargeIconBackground(contentView);
- contentView.setViewPadding(R.id.icon, 0, 0, 0, 0);
- contentView.setImageViewResource(R.id.icon, 0);
- contentView.setInt(R.id.icon, "setBackgroundResource", 0);
+ resetNotificationHeader(contentView);
+ resetContentMargins(contentView);
contentView.setViewVisibility(R.id.right_icon, View.GONE);
- contentView.setInt(R.id.right_icon, "setBackgroundResource", 0);
- contentView.setImageViewResource(R.id.right_icon, 0);
- contentView.setImageViewResource(R.id.icon, 0);
contentView.setTextViewText(R.id.title, null);
contentView.setTextViewText(R.id.text, null);
- unshrinkLine3Text(contentView);
- contentView.setTextViewText(R.id.text2, null);
- contentView.setViewVisibility(R.id.text2, View.GONE);
- contentView.setViewVisibility(R.id.info, View.GONE);
- contentView.setViewVisibility(R.id.time, View.GONE);
contentView.setViewVisibility(R.id.line3, View.GONE);
- contentView.setViewVisibility(R.id.overflow_divider, View.GONE);
+ contentView.setViewVisibility(R.id.text_line_1, View.GONE);
contentView.setViewVisibility(R.id.progress, View.GONE);
+ }
+
+ /**
+ * Resets the notification header to its original state
+ */
+ private void resetNotificationHeader(RemoteViews contentView) {
+ contentView.setImageViewResource(R.id.icon, 0);
+ contentView.setTextViewText(R.id.app_name_text, null);
contentView.setViewVisibility(R.id.chronometer, View.GONE);
+ contentView.setViewVisibility(R.id.header_sub_text, View.GONE);
+ contentView.setViewVisibility(R.id.header_content_info, View.GONE);
+ contentView.setViewVisibility(R.id.number_of_children, View.GONE);
+ contentView.setViewVisibility(R.id.sub_text_divider, View.GONE);
+ contentView.setViewVisibility(R.id.content_info_divider, View.GONE);
+ contentView.setViewVisibility(R.id.time_divider, View.GONE);
+ }
+
+ private void resetContentMargins(RemoteViews contentView) {
+ contentView.setViewLayoutMarginEnd(R.id.line1, 0);
+ contentView.setViewLayoutMarginEnd(R.id.line3, 0);
}
private RemoteViews applyStandardTemplate(int resId) {
@@ -3033,95 +3028,118 @@ public class Notification implements Parcelable
resetStandardTemplate(contentView);
boolean showLine3 = false;
- boolean showLine2 = false;
- boolean contentTextInLine2 = false;
final Bundle ex = mN.extras;
- if (mN.mLargeIcon != null) {
- contentView.setImageViewIcon(R.id.icon, mN.mLargeIcon);
- processLargeLegacyIcon(mN.mLargeIcon, contentView);
- contentView.setImageViewIcon(R.id.right_icon, mN.mSmallIcon);
- contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
- processSmallRightIcon(mN.mSmallIcon, contentView);
- } else { // small icon at left
- contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
- contentView.setViewVisibility(R.id.icon, View.VISIBLE);
- processSmallIconAsLarge(mN.mSmallIcon, contentView);
- }
+ bindNotificationHeader(contentView);
+ bindLargeIcon(contentView);
if (ex.getCharSequence(EXTRA_TITLE) != null) {
contentView.setTextViewText(R.id.title,
processLegacyText(ex.getCharSequence(EXTRA_TITLE)));
}
+ boolean showProgress = handleProgressBar(hasProgress, contentView, ex);
if (ex.getCharSequence(EXTRA_TEXT) != null) {
- contentView.setTextViewText(R.id.text,
+ contentView.setTextViewText(showProgress ? R.id.text_line_1 : R.id.text,
processLegacyText(ex.getCharSequence(EXTRA_TEXT)));
- showLine3 = true;
+ if (showProgress) {
+ contentView.setViewVisibility(R.id.text_line_1, View.VISIBLE);
+ }
+ showLine3 = !showProgress;
}
- if (ex.getCharSequence(EXTRA_INFO_TEXT) != null) {
- contentView.setTextViewText(R.id.info,
- processLegacyText(ex.getCharSequence(EXTRA_INFO_TEXT)));
- contentView.setViewVisibility(R.id.info, View.VISIBLE);
+ // We want to add badge to first line of text.
+ if (addProfileBadge(contentView, R.id.profile_badge_line3)) {
showLine3 = true;
+ }
+ // Note getStandardView may hide line 3 again.
+ contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
+
+ return contentView;
+ }
+
+ private boolean handleProgressBar(boolean hasProgress, RemoteViews contentView, Bundle ex) {
+ final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
+ final int progress = ex.getInt(EXTRA_PROGRESS, 0);
+ final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
+ if (hasProgress && (max != 0 || ind)) {
+ contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
+ contentView.setProgressBar(
+ R.id.progress, max, progress, ind);
+ contentView.setProgressBackgroundTintList(
+ R.id.progress, ColorStateList.valueOf(mContext.getColor(
+ R.color.notification_progress_background_color)));
+ if (mN.color != COLOR_DEFAULT) {
+ ColorStateList colorStateList = ColorStateList.valueOf(mN.color);
+ contentView.setProgressTintList(R.id.progress, colorStateList);
+ contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
+ }
+ return true;
+ } else {
+ contentView.setViewVisibility(R.id.progress, View.GONE);
+ return false;
+ }
+ }
+
+ private void bindLargeIcon(RemoteViews contentView) {
+ if (mN.mLargeIcon != null) {
+ contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
+ contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
+ processLargeLegacyIcon(mN.mLargeIcon, contentView);
+ int endMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.notification_content_picture_margin);
+ contentView.setViewLayoutMarginEnd(R.id.line1, endMargin);
+ contentView.setViewLayoutMarginEnd(R.id.line3, endMargin);
+ contentView.setViewLayoutMarginEnd(R.id.progress, endMargin);
+ }
+ }
+
+ private void bindNotificationHeader(RemoteViews contentView) {
+ bindSmallIcon(contentView);
+ bindChildCountColor(contentView);
+ bindHeaderAppName(contentView);
+ bindHeaderSubText(contentView);
+ bindContentInfo(contentView);
+ bindHeaderChronometerAndTime(contentView);
+ bindExpandButton(contentView);
+ }
+
+ private void bindChildCountColor(RemoteViews contentView) {
+ contentView.setTextColor(R.id.number_of_children, resolveColor());
+ }
+
+ private void bindContentInfo(RemoteViews contentView) {
+ boolean visible = false;
+ if (mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
+ contentView.setTextViewText(R.id.header_content_info,
+ processLegacyText(mN.extras.getCharSequence(EXTRA_INFO_TEXT)));
+ contentView.setViewVisibility(R.id.header_content_info, View.VISIBLE);
+ visible = true;
} else if (mN.number > 0) {
final int tooBig = mContext.getResources().getInteger(
R.integer.status_bar_notification_info_maxnum);
if (mN.number > tooBig) {
- contentView.setTextViewText(R.id.info, processLegacyText(
+ contentView.setTextViewText(R.id.header_content_info, processLegacyText(
mContext.getResources().getString(
R.string.status_bar_notification_info_overflow)));
} else {
- NumberFormat f = NumberFormat.getIntegerInstance();
- contentView.setTextViewText(R.id.info, processLegacyText(f.format(mN.number)));
- }
- contentView.setViewVisibility(R.id.info, View.VISIBLE);
- showLine3 = true;
- } else {
- contentView.setViewVisibility(R.id.info, View.GONE);
- }
-
- // Need to show three lines?
- if (ex.getCharSequence(EXTRA_SUB_TEXT) != null) {
- contentView.setTextViewText(R.id.text,
- processLegacyText(ex.getCharSequence(EXTRA_SUB_TEXT)));
- if (ex.getCharSequence(EXTRA_TEXT) != null) {
- contentView.setTextViewText(R.id.text2,
- processLegacyText(ex.getCharSequence(EXTRA_TEXT)));
- contentView.setViewVisibility(R.id.text2, View.VISIBLE);
- showLine2 = true;
- contentTextInLine2 = true;
- } else {
- contentView.setViewVisibility(R.id.text2, View.GONE);
- }
- } else {
- contentView.setViewVisibility(R.id.text2, View.GONE);
- final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
- final int progress = ex.getInt(EXTRA_PROGRESS, 0);
- final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
- if (hasProgress && (max != 0 || ind)) {
- contentView.setViewVisibility(R.id.progress, View.VISIBLE);
- contentView.setProgressBar(
- R.id.progress, max, progress, ind);
- contentView.setProgressBackgroundTintList(
- R.id.progress, ColorStateList.valueOf(mContext.getColor(
- R.color.notification_progress_background_color)));
- if (mN.color != COLOR_DEFAULT) {
- ColorStateList colorStateList = ColorStateList.valueOf(mN.color);
- contentView.setProgressTintList(R.id.progress, colorStateList);
- contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
- }
- showLine2 = true;
- } else {
- contentView.setViewVisibility(R.id.progress, View.GONE);
+ contentView.setTextViewText(R.id.header_content_info,
+ processLegacyText(String.valueOf(mN.number)));
}
+ contentView.setViewVisibility(R.id.header_content_info, View.VISIBLE);
+ visible = true;
}
- if (showLine2) {
-
- // need to shrink all the type to make sure everything fits
- shrinkLine3Text(contentView);
+ if (visible) {
+ contentView.setViewVisibility(R.id.content_info_divider, View.VISIBLE);
}
+ }
+
+ private void bindExpandButton(RemoteViews contentView) {
+ contentView.setDrawableParameters(R.id.expand_button, false, -1, resolveColor(),
+ PorterDuff.Mode.SRC_ATOP, -1);
+ }
+ private void bindHeaderChronometerAndTime(RemoteViews contentView) {
if (showsTimeOrChronometer()) {
- if (ex.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
+ contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
+ if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
contentView.setLong(R.id.chronometer, "setBase",
mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
@@ -3131,26 +3149,42 @@ public class Notification implements Parcelable
contentView.setLong(R.id.time, "setTime", mN.when);
}
}
+ }
- // Adjust padding depending on line count and font size.
- contentView.setViewPadding(R.id.line1, 0,
- calculateTopPadding(mContext, hasThreeLines(),
- mContext.getResources().getConfiguration().fontScale),
- 0, 0);
+ private void bindHeaderSubText(RemoteViews contentView) {
+ CharSequence subText = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
+ if (subText == null && mStyle != null && mStyle.mSummaryTextSet
+ && mStyle.hasSummaryInHeader()) {
+ subText = mStyle.mSummaryText;
+ }
+ if (subText != null) {
+ // TODO: Remove the span entirely to only have the string with propper formating.
+ contentView.setTextViewText(R.id.header_sub_text, processLegacyText(subText));
+ contentView.setViewVisibility(R.id.header_sub_text, View.VISIBLE);
+ contentView.setViewVisibility(R.id.sub_text_divider, View.VISIBLE);
+ }
+ }
- // We want to add badge to first line of text.
- boolean addedBadge = addProfileBadge(contentView,
- contentTextInLine2 ? R.id.profile_badge_line2 : R.id.profile_badge_line3);
- // If we added the badge to line 3 then we should show line 3.
- if (addedBadge && !contentTextInLine2) {
- showLine3 = true;
+ private void bindHeaderAppName(RemoteViews contentView) {
+ PackageManager packageManager = mContext.getPackageManager();
+ ApplicationInfo info = null;
+ try {
+ info = packageManager.getApplicationInfo(mContext.getApplicationInfo().packageName,
+ 0);
+ } catch (final NameNotFoundException e) {
+ return;
}
+ CharSequence appName = info != null ? packageManager.getApplicationLabel(info)
+ : null;
+ if (TextUtils.isEmpty(appName)) {
+ return;
+ }
+ contentView.setTextViewText(R.id.app_name_text, appName);
+ }
- // Note getStandardView may hide line 3 again.
- contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
- contentView.setViewVisibility(R.id.overflow_divider,
- showLine3 ? View.VISIBLE : View.GONE);
- return contentView;
+ private void bindSmallIcon(RemoteViews contentView) {
+ contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
+ processSmallIconColor(mN.mSmallIcon, contentView);
}
/**
@@ -3161,49 +3195,6 @@ public class Notification implements Parcelable
return mN.when != 0 && mN.extras.getBoolean(EXTRA_SHOW_WHEN);
}
- /**
- * Logic to find out whether the notification is going to have three lines in the contracted
- * layout. This is used to adjust the top padding.
- *
- * @return true if the notification is going to have three lines; false if the notification
- * is going to have one or two lines
- */
- private boolean hasThreeLines() {
- final CharSequence subText = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
- final CharSequence text = mN.extras.getCharSequence(EXTRA_TEXT);
- boolean contentTextInLine2 = subText != null && text != null;
- boolean hasProgress = mStyle == null || mStyle.hasProgress();
- // If we have content text in line 2, badge goes into line 2, or line 3 otherwise
- boolean badgeInLine3 = getProfileBadgeDrawable() != null && !contentTextInLine2;
- boolean hasLine3 = text != null || mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null
- || mN.number > 0 || badgeInLine3;
- final Bundle ex = mN.extras;
- final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
- final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
- boolean hasLine2 = (subText != null && text != null) ||
- (hasProgress && subText == null && (max != 0 || ind));
- return hasLine2 && hasLine3;
- }
-
- /**
- * @hide
- */
- public static int calculateTopPadding(Context ctx, boolean hasThreeLines,
- float fontScale) {
- int padding = ctx.getResources().getDimensionPixelSize(hasThreeLines
- ? R.dimen.notification_top_pad_narrow
- : R.dimen.notification_top_pad);
- int largePadding = ctx.getResources().getDimensionPixelSize(hasThreeLines
- ? R.dimen.notification_top_pad_large_text_narrow
- : R.dimen.notification_top_pad_large_text);
- float largeFactor = (MathUtils.constrain(fontScale, 1.0f, LARGE_TEXT_SCALE) - 1f)
- / (LARGE_TEXT_SCALE - 1f);
-
- // Linearly interpolate the padding between large and normal with the font scale ranging
- // from 1f to LARGE_TEXT_SCALE
- return Math.round((1 - largeFactor) * padding + largeFactor * largePadding);
- }
-
private void resetStandardTemplateWithActions(RemoteViews big) {
big.setViewVisibility(R.id.actions, View.GONE);
big.setViewVisibility(R.id.action_divider, View.GONE);
@@ -3250,18 +3241,46 @@ public class Notification implements Parcelable
* Construct a RemoteViews for the final big notification layout.
*/
public RemoteViews makeBigContentView() {
+ RemoteViews result = null;
if (mN.bigContentView != null) {
return mN.bigContentView;
} else if (mStyle != null) {
- final RemoteViews styleView = mStyle.makeBigContentView();
- if (styleView != null) {
- return styleView;
- }
+ result = mStyle.makeBigContentView();
} else if (mActions.size() == 0) {
return null;
}
+ if (result == null) {
+ result = applyStandardTemplateWithActions(getBigBaseLayoutResource());
+ } else {
+ hideLine1Text(result);
+ }
+ adaptNotificationHeaderForBigContentView(result);
+ return result;
+ }
- return applyStandardTemplateWithActions(getBigBaseLayoutResource());
+ /**
+ * Construct a RemoteViews for the final notification header only
+ *
+ * @hide
+ */
+ public RemoteViews makeNotificationHeader() {
+ RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
+ R.layout.notification_template_header);
+ resetNotificationHeader(header);
+ bindNotificationHeader(header);
+ return header;
+ }
+
+ private void hideLine1Text(RemoteViews result) {
+ result.setViewVisibility(R.id.text_line_1, View.GONE);
+ }
+
+ private void adaptNotificationHeaderForBigContentView(RemoteViews result) {
+ // We have to set the collapse button instead
+ result.setImageViewResource(R.id.expand_button, R.drawable.ic_arrow_up_14dp);
+ // Apply the color again
+ result.setDrawableParameters(R.id.expand_button, false, -1, resolveColor(),
+ PorterDuff.Mode.SRC_ATOP, -1);
}
/**
@@ -3330,84 +3349,27 @@ public class Notification implements Parcelable
}
/**
- * Apply any necessary background to smallIcons being used in the largeIcon spot.
+ * Apply any necessariy colors to the small icon
*/
- private void processSmallIconAsLarge(Icon largeIcon, RemoteViews contentView) {
- if (!isLegacy()) {
- contentView.setDrawableParameters(R.id.icon, false, -1,
- 0xFFFFFFFF,
+ private void processSmallIconColor(Icon smallIcon, RemoteViews contentView) {
+ if (!isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon)) {
+ contentView.setDrawableParameters(R.id.icon, false, -1, resolveColor(),
PorterDuff.Mode.SRC_ATOP, -1);
- applyLargeIconBackground(contentView);
- } else {
- if (getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
- applyLargeIconBackground(contentView);
- }
}
}
/**
- * Apply any necessary background to a largeIcon if it's a fake smallIcon (that is,
+ * Make the largeIcon dark if it's a fake smallIcon (that is,
* if it's grayscale).
*/
// TODO: also check bounds, transparency, that sort of thing.
private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView) {
if (largeIcon != null && isLegacy()
&& getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
- applyLargeIconBackground(contentView);
- } else {
- removeLargeIconBackground(contentView);
- }
- }
-
- /**
- * Add a colored circle behind the largeIcon slot.
- */
- private void applyLargeIconBackground(RemoteViews contentView) {
- contentView.setInt(R.id.icon, "setBackgroundResource",
- R.drawable.notification_icon_legacy_bg);
-
- contentView.setDrawableParameters(
- R.id.icon,
- true,
- -1,
- resolveColor(),
- PorterDuff.Mode.SRC_ATOP,
- -1);
-
- int padding = mContext.getResources().getDimensionPixelSize(
- R.dimen.notification_large_icon_circle_padding);
- contentView.setViewPadding(R.id.icon, padding, padding, padding, padding);
- }
-
- private void removeLargeIconBackground(RemoteViews contentView) {
- contentView.setInt(R.id.icon, "setBackgroundResource", 0);
- }
-
- /**
- * Recolor small icons when used in the R.id.right_icon slot.
- */
- private void processSmallRightIcon(Icon smallIcon, RemoteViews contentView) {
- if (!isLegacy()) {
- contentView.setDrawableParameters(R.id.right_icon, false, -1,
- 0xFFFFFFFF,
+ // resolve color will fall back to the default when legacy
+ contentView.setDrawableParameters(R.id.icon, false, -1, resolveColor(),
PorterDuff.Mode.SRC_ATOP, -1);
}
- final boolean gray = isLegacy()
- && smallIcon.getType() == Icon.TYPE_RESOURCE
- && getColorUtil().isGrayscaleIcon(mContext, smallIcon.getResId());
- if (!isLegacy() || gray) {
- contentView.setInt(R.id.right_icon,
- "setBackgroundResource",
- R.drawable.notification_icon_legacy_bg);
-
- contentView.setDrawableParameters(
- R.id.right_icon,
- true,
- -1,
- resolveColor(),
- PorterDuff.Mode.SRC_ATOP,
- -1);
- }
}
private void sanitizeColor() {
@@ -3416,9 +3378,9 @@ public class Notification implements Parcelable
}
}
- private int resolveColor() {
+ int resolveColor() {
if (mN.color == COLOR_DEFAULT) {
- return mContext.getColor(R.color.notification_icon_bg_color);
+ return mContext.getColor(R.color.notification_icon_default_color);
}
return mN.color;
}
@@ -3622,20 +3584,9 @@ public class Notification implements Parcelable
contentView.setViewVisibility(R.id.line1, View.VISIBLE);
}
- // The last line defaults to the subtext, but can be replaced by mSummaryText
- final CharSequence overflowText =
- mSummaryTextSet ? mSummaryText
- : mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT);
- if (overflowText != null) {
- contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(overflowText));
- contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE);
- contentView.setViewVisibility(R.id.line3, View.VISIBLE);
- } else {
- // Clear text in case we use the line to show the profile badge.
- contentView.setTextViewText(R.id.text, "");
- contentView.setViewVisibility(R.id.overflow_divider, View.GONE);
- contentView.setViewVisibility(R.id.line3, View.GONE);
- }
+ // Clear text in case we use the line to show the profile badge.
+ contentView.setTextViewText(com.android.internal.R.id.text, "");
+ contentView.setViewVisibility(com.android.internal.R.id.line3, View.GONE);
return contentView;
}
@@ -3666,19 +3617,6 @@ public class Notification implements Parcelable
}
/**
- * Changes the padding of the first line such that the big and small content view have the
- * same top padding.
- *
- * @hide
- */
- protected void applyTopPadding(RemoteViews contentView) {
- int topPadding = Builder.calculateTopPadding(mBuilder.mContext,
- mBuilder.hasThreeLines(),
- mBuilder.mContext.getResources().getConfiguration().fontScale);
- contentView.setViewPadding(R.id.line1, 0, topPadding, 0, 0);
- }
-
- /**
* Apply any style-specific extras to this notification before shipping it out.
* @hide
*/
@@ -3739,6 +3677,14 @@ public class Notification implements Parcelable
protected boolean hasProgress() {
return true;
}
+
+ /**
+ * @hide
+ * @return Whether we should put the summary be put into the notification header
+ */
+ public boolean hasSummaryInHeader() {
+ return true;
+ }
}
/**
@@ -3846,6 +3792,16 @@ public class Notification implements Parcelable
}
RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
+ if (mSummaryTextSet) {
+ contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(mSummaryText));
+ contentView.setViewVisibility(R.id.line3, View.VISIBLE);
+ }
+ int imageMinHeight = mBuilder.mContext.getResources().getDimensionPixelSize(
+ R.dimen.notification_big_picture_content_min_height_with_picture);
+ // We need to make space for the right image, so we're enforcing a minheight if there
+ // is a picture.
+ int minHeight = (mBuilder.mN.mLargeIcon == null) ? 0 : imageMinHeight;
+ contentView.setInt(R.id.notification_main_column, "setMinimumHeight", minHeight);
if (mBigLargeIconSet) {
mBuilder.mN.mLargeIcon = oldLargeIcon;
@@ -3853,12 +3809,7 @@ public class Notification implements Parcelable
contentView.setImageViewBitmap(R.id.big_picture, mPicture);
- applyTopPadding(contentView);
-
- boolean twoTextLines = mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT) != null
- && mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT) != null;
- mBuilder.addProfileBadge(contentView,
- twoTextLines ? R.id.profile_badge_line2 : R.id.profile_badge_line3);
+ mBuilder.addProfileBadge(contentView, R.id.profile_badge_line3);
return contentView;
}
@@ -3887,6 +3838,14 @@ public class Notification implements Parcelable
}
mPicture = extras.getParcelable(EXTRA_PICTURE);
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean hasSummaryInHeader() {
+ return false;
+ }
}
/**
@@ -3983,14 +3942,11 @@ public class Notification implements Parcelable
contentView.setTextViewText(R.id.big_text, mBuilder.processLegacyText(mBigText));
contentView.setViewVisibility(R.id.big_text, View.VISIBLE);
contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines());
- contentView.setViewVisibility(R.id.text2, View.GONE);
-
- applyTopPadding(contentView);
-
- mBuilder.shrinkLine3Text(contentView);
mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
+ contentView.setBoolean(R.id.big_text, "setHasImage", mBuilder.mN.mLargeIcon != null);
+
return contentView;
}
@@ -4005,11 +3961,6 @@ public class Notification implements Parcelable
if (hasSummary) {
lineCount -= LINES_CONSUMED_BY_SUMMARY;
}
-
- // If we have less top padding at the top, we can fit less lines.
- if (!mBuilder.hasThreeLines()) {
- lineCount--;
- }
return lineCount;
}
}
@@ -4105,8 +4056,6 @@ public class Notification implements Parcelable
mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText);
- contentView.setViewVisibility(R.id.text2, View.GONE);
-
int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
@@ -4120,6 +4069,9 @@ public class Notification implements Parcelable
final float subTextSize = mBuilder.mContext.getResources().getDimensionPixelSize(
R.dimen.notification_subtext_size);
int i=0;
+ final float density = mBuilder.mContext.getResources().getDisplayMetrics().density;
+ int topPadding = (int) (5 * density);
+ int bottomPadding = (int) (13 * density);
while (i < mTexts.size() && i < rowIds.length) {
CharSequence str = mTexts.get(i);
if (str != null && !str.equals("")) {
@@ -4129,24 +4081,29 @@ public class Notification implements Parcelable
contentView.setTextViewTextSize(rowIds[i], TypedValue.COMPLEX_UNIT_PX,
subTextSize);
}
+ contentView.setViewPadding(rowIds[i], 0, topPadding, 0,
+ i == rowIds.length - 1 || i == mTexts.size() - 1 ? bottomPadding : 0);
}
i++;
}
-
- contentView.setViewVisibility(R.id.inbox_end_pad,
- mTexts.size() > 0 ? View.VISIBLE : View.GONE);
-
- contentView.setViewVisibility(R.id.inbox_more,
- mTexts.size() > rowIds.length ? View.VISIBLE : View.GONE);
-
- applyTopPadding(contentView);
-
- mBuilder.shrinkLine3Text(contentView);
-
mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
+ handleInboxImageMargin(contentView, rowIds[0]);
+
return contentView;
}
+
+ private void handleInboxImageMargin(RemoteViews contentView, int id) {
+ final int max = mBuilder.mN.extras.getInt(EXTRA_PROGRESS_MAX, 0);
+ final boolean ind = mBuilder.mN.extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
+ boolean hasProgress = max != 0 || ind;
+ int endMargin = 0;
+ if (mTexts.size() > 0 && mBuilder.mN.mLargeIcon != null && !hasProgress) {
+ endMargin = mBuilder.mContext.getResources().getDimensionPixelSize(
+ R.dimen.notification_content_picture_margin);
+ }
+ contentView.setViewLayoutMarginEnd(id, endMargin);
+ }
}
/**
@@ -4278,14 +4235,13 @@ public class Notification implements Parcelable
}
}
- private RemoteViews generateMediaActionButton(Action action) {
+ private RemoteViews generateMediaActionButton(Action action, int color) {
final boolean tombstone = (action.actionIntent == null);
RemoteViews button = new BuilderRemoteViews(mBuilder.mContext.getApplicationInfo(),
R.layout.notification_material_media_action);
button.setImageViewIcon(R.id.action0, action.getIcon());
- button.setDrawableParameters(R.id.action0, false, -1,
- 0xFFFFFFFF,
- PorterDuff.Mode.SRC_ATOP, -1);
+ button.setDrawableParameters(R.id.action0, false, -1, color, PorterDuff.Mode.SRC_ATOP,
+ -1);
if (!tombstone) {
button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
}
@@ -4311,65 +4267,38 @@ public class Notification implements Parcelable
}
final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
- final RemoteViews button = generateMediaActionButton(action);
+ final RemoteViews button = generateMediaActionButton(action,
+ mBuilder.resolveColor());
view.addView(com.android.internal.R.id.media_actions, button);
}
}
- styleText(view);
- hideRightIcon(view);
+ handleImage(view /* addPaddingToMainColumn */);
return view;
}
private RemoteViews makeMediaBigContentView() {
final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
- RemoteViews big = mBuilder.applyStandardTemplate(getBigLayoutResource(actionCount),
- false /* hasProgress */);
+ RemoteViews big = mBuilder.applyStandardTemplate(
+ R.layout.notification_template_material_big_media,
+ false);
if (actionCount > 0) {
big.removeAllViews(com.android.internal.R.id.media_actions);
for (int i = 0; i < actionCount; i++) {
- final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i));
+ final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i),
+ mBuilder.resolveColor());
big.addView(com.android.internal.R.id.media_actions, button);
}
}
- styleText(big);
- hideRightIcon(big);
- applyTopPadding(big);
- big.setViewVisibility(android.R.id.progress, View.GONE);
+ handleImage(big);
return big;
}
- private int getBigLayoutResource(int actionCount) {
- if (actionCount <= 3) {
- return R.layout.notification_template_material_big_media_narrow;
- } else {
- return R.layout.notification_template_material_big_media;
- }
- }
-
- private void hideRightIcon(RemoteViews contentView) {
- contentView.setViewVisibility(R.id.right_icon, View.GONE);
- }
-
- /**
- * Applies the special text colors for media notifications to all text views.
- */
- private void styleText(RemoteViews contentView) {
- int primaryColor = mBuilder.mContext.getColor(
- R.color.notification_media_primary_color);
- int secondaryColor = mBuilder.mContext.getColor(
- R.color.notification_media_secondary_color);
- contentView.setTextColor(R.id.title, primaryColor);
- if (mBuilder.showsTimeOrChronometer()) {
- if (mBuilder.getAllExtras().getBoolean(EXTRA_SHOW_CHRONOMETER)) {
- contentView.setTextColor(R.id.chronometer, secondaryColor);
- } else {
- contentView.setTextColor(R.id.time, secondaryColor);
- }
+ private void handleImage(RemoteViews contentView) {
+ if (mBuilder.mN.mLargeIcon != null) {
+ contentView.setViewLayoutMarginEnd(R.id.line1, 0);
+ contentView.setViewLayoutMarginEnd(R.id.line3, 0);
}
- contentView.setTextColor(R.id.text2, secondaryColor);
- contentView.setTextColor(R.id.text, secondaryColor);
- contentView.setTextColor(R.id.info, secondaryColor);
}
/**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a0102b630b3b..b73fa50dcf15 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -625,8 +625,30 @@ public abstract class Context {
* @see #MODE_WORLD_READABLE
* @see #MODE_WORLD_WRITEABLE
*/
- public abstract SharedPreferences getSharedPreferences(String name,
- int mode);
+ public abstract SharedPreferences getSharedPreferences(String name, int mode);
+
+ /**
+ * Retrieve and hold the contents of the preferences file, returning
+ * a SharedPreferences through which you can retrieve and modify its
+ * values. Only one instance of the SharedPreferences object is returned
+ * to any callers for the same name, meaning they will see each other's
+ * edits as soon as they are made.
+ *
+ * @param file Desired preferences file. If a preferences file by this name
+ * does not exist, it will be created when you retrieve an
+ * editor (SharedPreferences.edit()) and then commit changes (Editor.commit()).
+ * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the
+ * default operation, {@link #MODE_WORLD_READABLE}
+ * and {@link #MODE_WORLD_WRITEABLE} to control permissions.
+ *
+ * @return The single {@link SharedPreferences} instance that can be used
+ * to retrieve and modify the preference values.
+ *
+ * @see #MODE_PRIVATE
+ * @see #MODE_WORLD_READABLE
+ * @see #MODE_WORLD_WRITEABLE
+ */
+ public abstract SharedPreferences getSharedPreferences(File file, int mode);
/**
* Open a private file associated with this Context's application package
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index bec1b374ab25..a345aae27205 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -172,6 +172,11 @@ public class ContextWrapper extends Context {
}
@Override
+ public SharedPreferences getSharedPreferences(File file, int mode) {
+ return mBase.getSharedPreferences(file, mode);
+ }
+
+ @Override
public FileInputStream openFileInput(String name)
throws FileNotFoundException {
return mBase.openFileInput(name);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 4a7cbc7b0517..e25f1d7804ce 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2877,6 +2877,14 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.USER_SWITCHED";
/**
+ * Broadcast Action: Sent when the credential-encrypted private storage has
+ * become unlocked for the target user. This is only sent to registered
+ * receivers, not manifest receivers.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
+
+ /**
* Broadcast sent to the system when a user's information changes. Carries an extra
* {@link #EXTRA_USER_HANDLE} to indicate which user's information changed.
* This is only sent to registered receivers, not manifest receivers. It is sent to all users.
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 1996e0feb401..65e5945a9421 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -487,6 +487,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public static final int PRIVATE_FLAG_AUTOPLAY = 1 << 7;
/**
+ * When set, at least one component inside this application is encryption aware.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE = 1 << 8;
+
+ /**
* Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
* {@hide}
*/
@@ -1054,6 +1061,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ENCRYPTION_AWARE) != 0;
}
+ /** @hide */
+ public boolean isPartiallyEncryptionAware() {
+ return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE) != 0;
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 6fe1efd72ba3..b9a42eb21eee 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -59,7 +59,7 @@ import android.content.IntentSender;
* {@hide}
*/
interface IPackageManager {
- boolean isPackageFrozen(String packageName);
+ void checkPackageStartable(String packageName, int userId);
boolean isPackageAvailable(String packageName, int userId);
PackageInfo getPackageInfo(String packageName, int flags, int userId);
int getPackageUid(String packageName, int userId);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 838da37c365d..ecb1850a94ca 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -84,9 +84,10 @@ import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.jar.StrictJarFile;
import java.util.zip.ZipEntry;
+import android.util.jar.StrictJarFile;
+
/**
* Parser for package files (APKs) on disk. This supports apps packaged either
* as a single "monolithic" APK, or apps packaged as a "cluster" of multiple
@@ -3262,6 +3263,11 @@ public class PackageParser {
owner.applicationInfo.isEncryptionAware());
}
+ if (a.info.encryptionAware) {
+ owner.applicationInfo.privateFlags |=
+ ApplicationInfo.PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE;
+ }
+
sa.recycle();
if (receiver && (owner.applicationInfo.privateFlags
@@ -3663,6 +3669,10 @@ public class PackageParser {
p.info.encryptionAware = sa.getBoolean(
R.styleable.AndroidManifestProvider_encryptionAware,
owner.applicationInfo.isEncryptionAware());
+ if (p.info.encryptionAware) {
+ owner.applicationInfo.privateFlags |=
+ ApplicationInfo.PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE;
+ }
sa.recycle();
@@ -3947,6 +3957,10 @@ public class PackageParser {
s.info.encryptionAware = sa.getBoolean(
R.styleable.AndroidManifestService_encryptionAware,
owner.applicationInfo.isEncryptionAware());
+ if (s.info.encryptionAware) {
+ owner.applicationInfo.privateFlags |=
+ ApplicationInfo.PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE;
+ }
sa.recycle();
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index 6a392ddfa379..014e73fd8aeb 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -16,8 +16,11 @@
package android.hardware.input;
+import android.annotation.Nullable;
import android.hardware.display.DisplayViewport;
import android.view.InputEvent;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
/**
* Input manager local system service interface.
@@ -39,4 +42,14 @@ public abstract class InputManagerInternal {
* watching for wake events.
*/
public abstract void setInteractive(boolean interactive);
+
+ /**
+ * Notifies that InputMethodManagerService switched the current input method subtype.
+ *
+ * @param userId user id that indicates who is using the specified input method and subtype.
+ * @param inputMethodInfo {@code null} when no input method is selected.
+ * @param subtype {@code null} when {@code inputMethodInfo} does has no subtype.
+ */
+ public abstract void onInputMethodSubtypeChanged(int userId,
+ @Nullable InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype subtype);
}
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 521df280c2c4..63f39c59b1b7 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -181,9 +181,12 @@ public abstract class AsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask";
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
- private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
+ // We want at least 2 threads and at most 4 threads in the core pool,
+ // preferring to have 1 less than the CPU count to avoid saturating
+ // the CPU with background work
+ private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
- private static final int KEEP_ALIVE = 1;
+ private static final int KEEP_ALIVE_SECONDS = 30;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@@ -199,9 +202,15 @@ public abstract class AsyncTask<Params, Progress, Result> {
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
- public static final Executor THREAD_POOL_EXECUTOR
- = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
- TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
+ public static final Executor THREAD_POOL_EXECUTOR;
+
+ static {
+ ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
+ CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
+ sPoolWorkQueue, sThreadFactory);
+ threadPoolExecutor.allowCoreThreadTimeOut(true);
+ THREAD_POOL_EXECUTOR = threadPoolExecutor;
+ }
/**
* An {@link Executor} that executes tasks one at a time in serial
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 0a149bbac713..f26693c853fb 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -929,9 +929,6 @@ public class UserManager {
if (guest != null) {
Settings.Secure.putStringForUser(context.getContentResolver(),
Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
-
- mService.setUserRestriction(DISALLOW_SMS, true, guest.id);
- mService.setUserRestriction(DISALLOW_INSTALL_UNKNOWN_SOURCES, true, guest.id);
}
} catch (RemoteException re) {
Log.w(TAG, "Could not create a user", re);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f560f8ad7b15..c92382a7d661 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4420,8 +4420,19 @@ public final class Settings {
* to receive changes in this value.
*/
public static final String LOCATION_MODE = "location_mode";
+ /**
+ * Stores the previous location mode when {@link #LOCATION_MODE} is set to
+ * {@link #LOCATION_MODE_OFF}
+ * @hide
+ */
+ public static final String LOCATION_PREVIOUS_MODE = "location_previous_mode";
/**
+ * Sets all location providers to the previous states before location was turned off.
+ * @hide
+ */
+ public static final int LOCATION_MODE_PREVIOUS = -1;
+ /**
* Location access disabled.
*/
public static final int LOCATION_MODE_OFF = 0;
@@ -5795,6 +5806,7 @@ public final class Settings {
CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES);
CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
+ CLONE_TO_MANAGED_PROFILE.add(LOCATION_PREVIOUS_MODE);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
CLONE_TO_MANAGED_PROFILE.add(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE);
@@ -5882,6 +5894,28 @@ public final class Settings {
}
/**
+ * Saves the current location mode into {@link #LOCATION_PREVIOUS_MODE}.
+ */
+ private static final boolean saveLocationModeForUser(ContentResolver cr, int userId) {
+ final int mode = getLocationModeForUser(cr, userId);
+ return putIntForUser(cr, Settings.Secure.LOCATION_PREVIOUS_MODE, mode, userId);
+ }
+
+ /**
+ * Restores the current location mode from {@link #LOCATION_PREVIOUS_MODE}.
+ */
+ private static final boolean restoreLocationModeForUser(ContentResolver cr, int userId) {
+ int mode = getIntForUser(cr, Settings.Secure.LOCATION_PREVIOUS_MODE,
+ LOCATION_MODE_HIGH_ACCURACY, userId);
+ // Make sure that the previous mode is never "off". Otherwise the user won't be able to
+ // turn on location any longer.
+ if (mode == LOCATION_MODE_OFF) {
+ mode = LOCATION_MODE_HIGH_ACCURACY;
+ }
+ return setLocationModeForUser(cr, mode, userId);
+ }
+
+ /**
* Thread-safe method for setting the location mode to one of
* {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
* {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
@@ -5899,7 +5933,11 @@ public final class Settings {
boolean gps = false;
boolean network = false;
switch (mode) {
+ case LOCATION_MODE_PREVIOUS:
+ // Retrieve the actual mode and set to that mode.
+ return restoreLocationModeForUser(cr, userId);
case LOCATION_MODE_OFF:
+ saveLocationModeForUser(cr, userId);
break;
case LOCATION_MODE_SENSORS_ONLY:
gps = true;
diff --git a/core/java/android/security/net/config/ApplicationConfig.java b/core/java/android/security/net/config/ApplicationConfig.java
index 48359d47f091..b6276418c49d 100644
--- a/core/java/android/security/net/config/ApplicationConfig.java
+++ b/core/java/android/security/net/config/ApplicationConfig.java
@@ -144,18 +144,4 @@ public final class ApplicationConfig {
return sInstance;
}
}
-
- /** @hide */
- public static ApplicationConfig getPlatformDefault() {
- return new ApplicationConfig(new ConfigSource() {
- @Override
- public NetworkSecurityConfig getDefaultConfig() {
- return NetworkSecurityConfig.DEFAULT;
- }
- @Override
- public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
- return null;
- }
- });
- }
}
diff --git a/core/java/android/security/net/config/CertificateSource.java b/core/java/android/security/net/config/CertificateSource.java
index 386354dc4d57..2b7829eb6a31 100644
--- a/core/java/android/security/net/config/CertificateSource.java
+++ b/core/java/android/security/net/config/CertificateSource.java
@@ -22,4 +22,5 @@ import java.security.cert.X509Certificate;
/** @hide */
public interface CertificateSource {
Set<X509Certificate> getCertificates();
+ X509Certificate findBySubjectAndPublicKey(X509Certificate cert);
}
diff --git a/core/java/android/security/net/config/CertificatesEntryRef.java b/core/java/android/security/net/config/CertificatesEntryRef.java
index 2ba38c21c330..1d15e19a99f2 100644
--- a/core/java/android/security/net/config/CertificatesEntryRef.java
+++ b/core/java/android/security/net/config/CertificatesEntryRef.java
@@ -30,6 +30,10 @@ public final class CertificatesEntryRef {
mOverridesPins = overridesPins;
}
+ boolean overridesPins() {
+ return mOverridesPins;
+ }
+
public Set<TrustAnchor> getTrustAnchors() {
// TODO: cache this [but handle mutable sources]
Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>();
@@ -38,4 +42,13 @@ public final class CertificatesEntryRef {
}
return anchors;
}
+
+ public TrustAnchor findBySubjectAndPublicKey(X509Certificate cert) {
+ X509Certificate foundCert = mSource.findBySubjectAndPublicKey(cert);
+ if (foundCert == null) {
+ return null;
+ }
+
+ return new TrustAnchor(foundCert, mOverridesPins);
+ }
}
diff --git a/core/java/android/security/net/config/DirectoryCertificateSource.java b/core/java/android/security/net/config/DirectoryCertificateSource.java
new file mode 100644
index 000000000000..a261e0611e45
--- /dev/null
+++ b/core/java/android/security/net/config/DirectoryCertificateSource.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.net.config;
+
+import android.os.Environment;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Pair;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Set;
+import libcore.io.IoUtils;
+
+import com.android.org.conscrypt.Hex;
+import com.android.org.conscrypt.NativeCrypto;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * {@link CertificateSource} based on a directory where certificates are stored as individual files
+ * named after a hash of their SubjectName for more efficient lookups.
+ * @hide
+ */
+abstract class DirectoryCertificateSource implements CertificateSource {
+ private final File mDir;
+ private final Object mLock = new Object();
+ private final CertificateFactory mCertFactory;
+
+ private Set<X509Certificate> mCertificates;
+
+ protected DirectoryCertificateSource(File caDir) {
+ mDir = caDir;
+ try {
+ mCertFactory = CertificateFactory.getInstance("X.509");
+ } catch (CertificateException e) {
+ throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+ }
+ }
+
+ protected abstract boolean isCertMarkedAsRemoved(String caFile);
+
+ @Override
+ public Set<X509Certificate> getCertificates() {
+ // TODO: loading all of these is wasteful, we should instead use a keystore style API.
+ synchronized (mLock) {
+ if (mCertificates != null) {
+ return mCertificates;
+ }
+
+ Set<X509Certificate> certs = new ArraySet<X509Certificate>();
+ if (mDir.isDirectory()) {
+ for (String caFile : mDir.list()) {
+ if (isCertMarkedAsRemoved(caFile)) {
+ continue;
+ }
+ X509Certificate cert = readCertificate(caFile);
+ if (cert != null) {
+ certs.add(cert);
+ }
+ }
+ }
+ mCertificates = certs;
+ return mCertificates;
+ }
+ }
+
+ @Override
+ public X509Certificate findBySubjectAndPublicKey(final X509Certificate cert) {
+ return findCert(cert.getSubjectX500Principal(), new CertSelector() {
+ @Override
+ public boolean match(X509Certificate ca) {
+ return ca.getPublicKey().equals(cert.getPublicKey());
+ }
+ });
+ }
+
+ private static interface CertSelector {
+ boolean match(X509Certificate cert);
+ }
+
+ private X509Certificate findCert(X500Principal subj, CertSelector selector) {
+ String hash = getHash(subj);
+ for (int index = 0; index >= 0; index++) {
+ String fileName = hash + "." + index;
+ if (!new File(mDir, fileName).exists()) {
+ break;
+ }
+ if (isCertMarkedAsRemoved(fileName)) {
+ continue;
+ }
+ X509Certificate cert = readCertificate(fileName);
+ if (!subj.equals(cert.getSubjectX500Principal())) {
+ continue;
+ }
+ if (selector.match(cert)) {
+ return cert;
+ }
+ }
+ return null;
+ }
+
+ private String getHash(X500Principal name) {
+ int hash = NativeCrypto.X509_NAME_hash_old(name);
+ return Hex.intToHexString(hash, 8);
+ }
+
+ private X509Certificate readCertificate(String file) {
+ InputStream is = null;
+ try {
+ is = new BufferedInputStream(new FileInputStream(new File(mDir, file)));
+ return (X509Certificate) mCertFactory.generateCertificate(is);
+ } catch (CertificateException | IOException e) {
+ return null;
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+}
diff --git a/core/java/android/security/net/config/KeyStoreCertificateSource.java b/core/java/android/security/net/config/KeyStoreCertificateSource.java
index 9167a9007325..7a01a6488a04 100644
--- a/core/java/android/security/net/config/KeyStoreCertificateSource.java
+++ b/core/java/android/security/net/config/KeyStoreCertificateSource.java
@@ -24,6 +24,8 @@ import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.Set;
+import com.android.org.conscrypt.TrustedCertificateIndex;
+
/**
* {@link CertificateSource} which provides certificates from trusted certificate entries of a
* {@link KeyStore}.
@@ -31,6 +33,7 @@ import java.util.Set;
class KeyStoreCertificateSource implements CertificateSource {
private final Object mLock = new Object();
private final KeyStore mKeyStore;
+ private TrustedCertificateIndex mIndex;
private Set<X509Certificate> mCertificates;
public KeyStoreCertificateSource(KeyStore ks) {
@@ -39,24 +42,42 @@ class KeyStoreCertificateSource implements CertificateSource {
@Override
public Set<X509Certificate> getCertificates() {
+ ensureInitialized();
+ return mCertificates;
+ }
+
+ private void ensureInitialized() {
synchronized (mLock) {
if (mCertificates != null) {
- return mCertificates;
+ return;
}
+
try {
+ TrustedCertificateIndex localIndex = new TrustedCertificateIndex();
Set<X509Certificate> certificates = new ArraySet<>(mKeyStore.size());
for (Enumeration<String> en = mKeyStore.aliases(); en.hasMoreElements();) {
String alias = en.nextElement();
X509Certificate cert = (X509Certificate) mKeyStore.getCertificate(alias);
if (cert != null) {
certificates.add(cert);
+ localIndex.index(cert);
}
}
+ mIndex = localIndex;
mCertificates = certificates;
- return mCertificates;
} catch (KeyStoreException e) {
throw new RuntimeException("Failed to load certificates from KeyStore", e);
}
}
}
+
+ @Override
+ public X509Certificate findBySubjectAndPublicKey(X509Certificate cert) {
+ ensureInitialized();
+ java.security.cert.TrustAnchor anchor = mIndex.findBySubjectAndPublicKey(cert);
+ if (anchor == null) {
+ return null;
+ }
+ return anchor.getTrustedCert();
+ }
}
diff --git a/core/java/android/security/net/config/ManifestConfigSource.java b/core/java/android/security/net/config/ManifestConfigSource.java
new file mode 100644
index 000000000000..bf1fb8a5a721
--- /dev/null
+++ b/core/java/android/security/net/config/ManifestConfigSource.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.net.config;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+import android.util.Pair;
+import java.util.Set;
+
+/** @hide */
+public class ManifestConfigSource implements ConfigSource {
+ public static final String META_DATA_NETWORK_SECURITY_CONFIG =
+ "android.security.net.config";
+ private static final boolean DBG = true;
+ private static final String LOG_TAG = "NetworkSecurityConfig";
+
+ private final Object mLock = new Object();
+ private final Context mContext;
+
+ private ConfigSource mConfigSource;
+
+ public ManifestConfigSource(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
+ return getConfigSource().getPerDomainConfigs();
+ }
+
+ @Override
+ public NetworkSecurityConfig getDefaultConfig() {
+ return getConfigSource().getDefaultConfig();
+ }
+
+ private ConfigSource getConfigSource() {
+ synchronized (mLock) {
+ if (mConfigSource != null) {
+ return mConfigSource;
+ }
+ ApplicationInfo info;
+ try {
+ info = mContext.getPackageManager().getApplicationInfo(mContext.getPackageName(),
+ PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException("Failed to look up ApplicationInfo", e);
+ }
+ int configResourceId = 0;
+ if (info != null && info.metaData != null) {
+ configResourceId = info.metaData.getInt(META_DATA_NETWORK_SECURITY_CONFIG);
+ }
+
+ ConfigSource source;
+ if (configResourceId != 0) {
+ boolean debugBuild = (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ if (DBG) {
+ Log.d(LOG_TAG, "Using Network Security Config from resource "
+ + mContext.getResources().getResourceEntryName(configResourceId)
+ + " debugBuild: " + debugBuild);
+ }
+ source = new XmlConfigSource(mContext, configResourceId, debugBuild);
+ } else {
+ if (DBG) {
+ Log.d(LOG_TAG, "No Network Security Config specified, using platform default");
+ }
+ source = new DefaultConfigSource();
+ }
+ mConfigSource = source;
+ return mConfigSource;
+ }
+ }
+
+ private static final class DefaultConfigSource implements ConfigSource {
+ @Override
+ public NetworkSecurityConfig getDefaultConfig() {
+ return NetworkSecurityConfig.DEFAULT;
+ }
+
+ @Override
+ public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
+ return null;
+ }
+ }
+}
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 9eab80ca0771..2ab07b5abf18 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -22,6 +22,7 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -53,6 +54,19 @@ public final class NetworkSecurityConfig {
mHstsEnforced = hstsEnforced;
mPins = pins;
mCertificatesEntryRefs = certificatesEntryRefs;
+ // Sort the certificates entry refs so that all entries that override pins come before
+ // non-override pin entries. This allows us to handle the case where a certificate is in
+ // multiple entry refs by returning the certificate from the first entry ref.
+ Collections.sort(mCertificatesEntryRefs, new Comparator<CertificatesEntryRef>() {
+ @Override
+ public int compare(CertificatesEntryRef lhs, CertificatesEntryRef rhs) {
+ if (lhs.overridesPins()) {
+ return rhs.overridesPins() ? 0 : -1;
+ } else {
+ return rhs.overridesPins() ? 1 : 0;
+ }
+ }
+ });
}
public Set<TrustAnchor> getTrustAnchors() {
@@ -63,14 +77,15 @@ public final class NetworkSecurityConfig {
// Merge trust anchors based on the X509Certificate.
// If we see the same certificate in two TrustAnchors, one with overridesPins and one
// without, the one with overridesPins wins.
+ // Because mCertificatesEntryRefs is sorted with all overridesPins anchors coming first
+ // this can be simplified to just using the first occurrence of a certificate.
Map<X509Certificate, TrustAnchor> anchorMap = new ArrayMap<>();
for (CertificatesEntryRef ref : mCertificatesEntryRefs) {
Set<TrustAnchor> anchors = ref.getTrustAnchors();
for (TrustAnchor anchor : anchors) {
- if (anchor.overridesPins) {
- anchorMap.put(anchor.certificate, anchor);
- } else if (!anchorMap.containsKey(anchor.certificate)) {
- anchorMap.put(anchor.certificate, anchor);
+ X509Certificate cert = anchor.certificate;
+ if (!anchorMap.containsKey(cert)) {
+ anchorMap.put(cert, anchor);
}
}
}
@@ -108,6 +123,17 @@ public final class NetworkSecurityConfig {
}
}
+ /** @hide */
+ public TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) {
+ for (CertificatesEntryRef ref : mCertificatesEntryRefs) {
+ TrustAnchor anchor = ref.findBySubjectAndPublicKey(cert);
+ if (anchor != null) {
+ return anchor;
+ }
+ }
+ return null;
+ }
+
/**
* Return a {@link Builder} for the default {@code NetworkSecurityConfig}.
*
diff --git a/core/java/android/security/net/config/NetworkSecurityConfigProvider.java b/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
index ac762efe85d2..5ebc7ac5f242 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
@@ -17,20 +17,13 @@
package android.security.net.config;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.util.Log;
import java.security.Security;
import java.security.Provider;
/** @hide */
public final class NetworkSecurityConfigProvider extends Provider {
- private static final String LOG_TAG = "NetworkSecurityConfig";
private static final String PREFIX =
NetworkSecurityConfigProvider.class.getPackage().getName() + ".";
- public static final String META_DATA_NETWORK_SECURITY_CONFIG =
- "android.security.net.config";
- private static final boolean DBG = true;
public NetworkSecurityConfigProvider() {
// TODO: More clever name than this
@@ -40,36 +33,7 @@ public final class NetworkSecurityConfigProvider extends Provider {
}
public static void install(Context context) {
- ApplicationInfo info = null;
- // TODO: This lookup shouldn't be done in the app startup path, it should be done lazily.
- try {
- info = context.getPackageManager().getApplicationInfo(context.getPackageName(),
- PackageManager.GET_META_DATA);
- } catch (PackageManager.NameNotFoundException e) {
- throw new RuntimeException("Failed to look up ApplicationInfo", e);
- }
- int configResourceId = 0;
- if (info != null && info.metaData != null) {
- configResourceId = info.metaData.getInt(META_DATA_NETWORK_SECURITY_CONFIG);
- }
-
- ApplicationConfig config;
- if (configResourceId != 0) {
- boolean debugBuild = (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
- if (DBG) {
- Log.d(LOG_TAG, "Using Network Security Config from resource "
- + context.getResources().getResourceEntryName(configResourceId)
- + " debugBuild: " + debugBuild);
- }
- ConfigSource source = new XmlConfigSource(context, configResourceId, debugBuild);
- config = new ApplicationConfig(source);
- } else {
- if (DBG) {
- Log.d(LOG_TAG, "No Network Security Config specified, using platform default");
- }
- config = ApplicationConfig.getPlatformDefault();
- }
-
+ ApplicationConfig config = new ApplicationConfig(new ManifestConfigSource(context));
ApplicationConfig.setDefaultInstance(config);
int pos = Security.insertProviderAt(new NetworkSecurityConfigProvider(), 1);
if (pos != 1) {
diff --git a/core/java/android/security/net/config/NetworkSecurityTrustManager.java b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
index 2b860fac45c1..6013c1e4023e 100644
--- a/core/java/android/security/net/config/NetworkSecurityTrustManager.java
+++ b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
@@ -133,14 +133,8 @@ public class NetworkSecurityTrustManager implements X509TrustManager {
return false;
}
X509Certificate anchorCert = chain.get(chain.size() - 1);
- TrustAnchor chainAnchor = null;
- // TODO: faster lookup
- for (TrustAnchor anchor : mNetworkSecurityConfig.getTrustAnchors()) {
- if (anchor.certificate.equals(anchorCert)) {
- chainAnchor = anchor;
- break;
- }
- }
+ TrustAnchor chainAnchor =
+ mNetworkSecurityConfig.findTrustAnchorBySubjectAndPublicKey(anchorCert);
if (chainAnchor == null) {
throw new CertificateException("Trusted chain does not end in a TrustAnchor");
}
diff --git a/core/java/android/security/net/config/ResourceCertificateSource.java b/core/java/android/security/net/config/ResourceCertificateSource.java
index 06dd9d42e364..b007f8f00a55 100644
--- a/core/java/android/security/net/config/ResourceCertificateSource.java
+++ b/core/java/android/security/net/config/ResourceCertificateSource.java
@@ -27,26 +27,29 @@ import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Set;
+import com.android.org.conscrypt.TrustedCertificateIndex;
+
/**
* {@link CertificateSource} based on certificates contained in an application resource file.
* @hide
*/
public class ResourceCertificateSource implements CertificateSource {
- private Set<X509Certificate> mCertificates;
+ private final Object mLock = new Object();
private final int mResourceId;
+
+ private Set<X509Certificate> mCertificates;
private Context mContext;
- private final Object mLock = new Object();
+ private TrustedCertificateIndex mIndex;
public ResourceCertificateSource(int resourceId, Context context) {
mResourceId = resourceId;
mContext = context.getApplicationContext();
}
- @Override
- public Set<X509Certificate> getCertificates() {
+ private void ensureInitialized() {
synchronized (mLock) {
if (mCertificates != null) {
- return mCertificates;
+ return;
}
Set<X509Certificate> certificates = new ArraySet<X509Certificate>();
Collection<? extends Certificate> certs;
@@ -61,12 +64,30 @@ public class ResourceCertificateSource implements CertificateSource {
} finally {
IoUtils.closeQuietly(in);
}
+ TrustedCertificateIndex indexLocal = new TrustedCertificateIndex();
for (Certificate cert : certs) {
- certificates.add((X509Certificate) cert);
+ certificates.add((X509Certificate) cert);
+ indexLocal.index((X509Certificate) cert);
}
mCertificates = certificates;
+ mIndex = indexLocal;
mContext = null;
- return mCertificates;
}
}
+
+ @Override
+ public Set<X509Certificate> getCertificates() {
+ ensureInitialized();
+ return mCertificates;
+ }
+
+ @Override
+ public X509Certificate findBySubjectAndPublicKey(X509Certificate cert) {
+ ensureInitialized();
+ java.security.cert.TrustAnchor anchor = mIndex.findBySubjectAndPublicKey(cert);
+ if (anchor == null) {
+ return null;
+ }
+ return anchor.getTrustedCert();
+ }
}
diff --git a/core/java/android/security/net/config/SystemCertificateSource.java b/core/java/android/security/net/config/SystemCertificateSource.java
index 7649a977ff5c..abef7b453c79 100644
--- a/core/java/android/security/net/config/SystemCertificateSource.java
+++ b/core/java/android/security/net/config/SystemCertificateSource.java
@@ -18,29 +18,20 @@ package android.security.net.config;
import android.os.Environment;
import android.os.UserHandle;
-import android.util.ArraySet;
-import java.io.BufferedInputStream;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.IOException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Set;
-import libcore.io.IoUtils;
/**
* {@link CertificateSource} based on the system trusted CA store.
* @hide
*/
-public class SystemCertificateSource implements CertificateSource {
+public final class SystemCertificateSource extends DirectoryCertificateSource {
private static final SystemCertificateSource INSTANCE = new SystemCertificateSource();
- private Set<X509Certificate> mSystemCerts = null;
- private final Object mLock = new Object();
+ private final File mUserRemovedCaDir;
private SystemCertificateSource() {
+ super(new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts"));
+ File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
+ mUserRemovedCaDir = new File(configDir, "cacerts-removed");
}
public static SystemCertificateSource getInstance() {
@@ -48,54 +39,7 @@ public class SystemCertificateSource implements CertificateSource {
}
@Override
- public Set<X509Certificate> getCertificates() {
- // TODO: loading all of these is wasteful, we should instead use a keystore style API.
- synchronized (mLock) {
- if (mSystemCerts != null) {
- return mSystemCerts;
- }
- CertificateFactory certFactory;
- try {
- certFactory = CertificateFactory.getInstance("X.509");
- } catch (CertificateException e) {
- throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
- }
-
- final String ANDROID_ROOT = System.getenv("ANDROID_ROOT");
- final File systemCaDir = new File(ANDROID_ROOT + "/etc/security/cacerts");
- final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
- final File userRemovedCaDir = new File(configDir, "cacerts-removed");
- // Sanity check
- if (!systemCaDir.isDirectory()) {
- throw new AssertionError(systemCaDir + " is not a directory");
- }
-
- Set<X509Certificate> systemCerts = new ArraySet<X509Certificate>();
- for (String caFile : systemCaDir.list()) {
- // Skip any CAs in the user's deleted directory.
- if (new File(userRemovedCaDir, caFile).exists()) {
- continue;
- }
- InputStream is = null;
- try {
- is = new BufferedInputStream(
- new FileInputStream(new File(systemCaDir, caFile)));
- systemCerts.add((X509Certificate) certFactory.generateCertificate(is));
- } catch (CertificateException | IOException e) {
- // Don't rethrow to be consistent with conscrypt's cert loading code.
- continue;
- } finally {
- IoUtils.closeQuietly(is);
- }
- }
- mSystemCerts = systemCerts;
- return mSystemCerts;
- }
- }
-
- public void onCertificateStorageChange() {
- synchronized (mLock) {
- mSystemCerts = null;
- }
+ protected boolean isCertMarkedAsRemoved(String caFile) {
+ return new File(mUserRemovedCaDir, caFile).exists();
}
}
diff --git a/core/java/android/security/net/config/UserCertificateSource.java b/core/java/android/security/net/config/UserCertificateSource.java
index e9d5aa1e2f34..1a7d92456a3d 100644
--- a/core/java/android/security/net/config/UserCertificateSource.java
+++ b/core/java/android/security/net/config/UserCertificateSource.java
@@ -18,29 +18,18 @@ package android.security.net.config;
import android.os.Environment;
import android.os.UserHandle;
-import android.util.ArraySet;
-import java.io.BufferedInputStream;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.IOException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Set;
-import libcore.io.IoUtils;
/**
* {@link CertificateSource} based on the user-installed trusted CA store.
* @hide
*/
-public class UserCertificateSource implements CertificateSource {
+public final class UserCertificateSource extends DirectoryCertificateSource {
private static final UserCertificateSource INSTANCE = new UserCertificateSource();
- private Set<X509Certificate> mUserCerts = null;
- private final Object mLock = new Object();
private UserCertificateSource() {
+ super(new File(
+ Environment.getUserConfigDirectory(UserHandle.myUserId()), "cacerts-added"));
}
public static UserCertificateSource getInstance() {
@@ -48,45 +37,7 @@ public class UserCertificateSource implements CertificateSource {
}
@Override
- public Set<X509Certificate> getCertificates() {
- // TODO: loading all of these is wasteful, we should instead use a keystore style API.
- synchronized (mLock) {
- if (mUserCerts != null) {
- return mUserCerts;
- }
- CertificateFactory certFactory;
- try {
- certFactory = CertificateFactory.getInstance("X.509");
- } catch (CertificateException e) {
- throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
- }
- final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
- final File userCaDir = new File(configDir, "cacerts-added");
- Set<X509Certificate> userCerts = new ArraySet<X509Certificate>();
- // If the user hasn't added any certificates the directory may not exist.
- if (userCaDir.isDirectory()) {
- for (String caFile : userCaDir.list()) {
- InputStream is = null;
- try {
- is = new BufferedInputStream(
- new FileInputStream(new File(userCaDir, caFile)));
- userCerts.add((X509Certificate) certFactory.generateCertificate(is));
- } catch (CertificateException | IOException e) {
- // Don't rethrow to be consistent with conscrypt's cert loading code.
- continue;
- } finally {
- IoUtils.closeQuietly(is);
- }
- }
- }
- mUserCerts = userCerts;
- return mUserCerts;
- }
- }
-
- public void onCertificateStorageChange() {
- synchronized (mLock) {
- mUserCerts = null;
- }
+ protected boolean isCertMarkedAsRemoved(String caFile) {
+ return false;
}
}
diff --git a/core/java/android/util/jar/StrictJarFile.java b/core/java/android/util/jar/StrictJarFile.java
new file mode 100644
index 000000000000..fd5780612649
--- /dev/null
+++ b/core/java/android/util/jar/StrictJarFile.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2013 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.util.jar;
+
+import dalvik.system.CloseGuard;
+import java.io.ByteArrayInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.security.cert.Certificate;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * A subset of the JarFile API implemented as a thin wrapper over
+ * system/core/libziparchive.
+ *
+ * @hide for internal use only. Not API compatible (or as forgiving) as
+ * {@link java.util.jar.JarFile}
+ */
+public final class StrictJarFile {
+
+ private final long nativeHandle;
+
+ // NOTE: It's possible to share a file descriptor with the native
+ // code, at the cost of some additional complexity.
+ private final RandomAccessFile raf;
+
+ private final StrictJarManifest manifest;
+ private final StrictJarVerifier verifier;
+
+ private final boolean isSigned;
+
+ private final CloseGuard guard = CloseGuard.get();
+ private boolean closed;
+
+ public StrictJarFile(String fileName) throws IOException, SecurityException {
+ this.nativeHandle = nativeOpenJarFile(fileName);
+ this.raf = new RandomAccessFile(fileName, "r");
+
+ try {
+ // Read the MANIFEST and signature files up front and try to
+ // parse them. We never want to accept a JAR File with broken signatures
+ // or manifests, so it's best to throw as early as possible.
+ HashMap<String, byte[]> metaEntries = getMetaEntries();
+ this.manifest = new StrictJarManifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
+ this.verifier = new StrictJarVerifier(fileName, manifest, metaEntries);
+ Set<String> files = manifest.getEntries().keySet();
+ for (String file : files) {
+ if (findEntry(file) == null) {
+ throw new SecurityException(fileName + ": File " + file + " in manifest does not exist");
+ }
+ }
+
+ isSigned = verifier.readCertificates() && verifier.isSignedJar();
+ } catch (IOException | SecurityException e) {
+ nativeClose(this.nativeHandle);
+ IoUtils.closeQuietly(this.raf);
+ throw e;
+ }
+
+ guard.open("close");
+ }
+
+ public StrictJarManifest getManifest() {
+ return manifest;
+ }
+
+ public Iterator<ZipEntry> iterator() throws IOException {
+ return new EntryIterator(nativeHandle, "");
+ }
+
+ public ZipEntry findEntry(String name) {
+ return nativeFindEntry(nativeHandle, name);
+ }
+
+ /**
+ * Return all certificate chains for a given {@link ZipEntry} belonging to this jar.
+ * This method MUST be called only after fully exhausting the InputStream belonging
+ * to this entry.
+ *
+ * Returns {@code null} if this jar file isn't signed or if this method is
+ * called before the stream is processed.
+ */
+ public Certificate[][] getCertificateChains(ZipEntry ze) {
+ if (isSigned) {
+ return verifier.getCertificateChains(ze.getName());
+ }
+
+ return null;
+ }
+
+ /**
+ * Return all certificates for a given {@link ZipEntry} belonging to this jar.
+ * This method MUST be called only after fully exhausting the InputStream belonging
+ * to this entry.
+ *
+ * Returns {@code null} if this jar file isn't signed or if this method is
+ * called before the stream is processed.
+ *
+ * @deprecated Switch callers to use getCertificateChains instead
+ */
+ @Deprecated
+ public Certificate[] getCertificates(ZipEntry ze) {
+ if (isSigned) {
+ Certificate[][] certChains = verifier.getCertificateChains(ze.getName());
+
+ // Measure number of certs.
+ int count = 0;
+ for (Certificate[] chain : certChains) {
+ count += chain.length;
+ }
+
+ // Create new array and copy all the certs into it.
+ Certificate[] certs = new Certificate[count];
+ int i = 0;
+ for (Certificate[] chain : certChains) {
+ System.arraycopy(chain, 0, certs, i, chain.length);
+ i += chain.length;
+ }
+
+ return certs;
+ }
+
+ return null;
+ }
+
+ public InputStream getInputStream(ZipEntry ze) {
+ final InputStream is = getZipInputStream(ze);
+
+ if (isSigned) {
+ StrictJarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());
+ if (entry == null) {
+ return is;
+ }
+
+ return new JarFileInputStream(is, ze.getSize(), entry);
+ }
+
+ return is;
+ }
+
+ public void close() throws IOException {
+ if (!closed) {
+ guard.close();
+
+ nativeClose(nativeHandle);
+ IoUtils.closeQuietly(raf);
+ closed = true;
+ }
+ }
+
+ private InputStream getZipInputStream(ZipEntry ze) {
+ if (ze.getMethod() == ZipEntry.STORED) {
+ return new RAFStream(raf, ze.getDataOffset(),
+ ze.getDataOffset() + ze.getSize());
+ } else {
+ final RAFStream wrapped = new RAFStream(
+ raf, ze.getDataOffset(), ze.getDataOffset() + ze.getCompressedSize());
+
+ int bufSize = Math.max(1024, (int) Math.min(ze.getSize(), 65535L));
+ return new ZipInflaterInputStream(wrapped, new Inflater(true), bufSize, ze);
+ }
+ }
+
+ static final class EntryIterator implements Iterator<ZipEntry> {
+ private final long iterationHandle;
+ private ZipEntry nextEntry;
+
+ EntryIterator(long nativeHandle, String prefix) throws IOException {
+ iterationHandle = nativeStartIteration(nativeHandle, prefix);
+ }
+
+ public ZipEntry next() {
+ if (nextEntry != null) {
+ final ZipEntry ze = nextEntry;
+ nextEntry = null;
+ return ze;
+ }
+
+ return nativeNextEntry(iterationHandle);
+ }
+
+ public boolean hasNext() {
+ if (nextEntry != null) {
+ return true;
+ }
+
+ final ZipEntry ze = nativeNextEntry(iterationHandle);
+ if (ze == null) {
+ return false;
+ }
+
+ nextEntry = ze;
+ return true;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private HashMap<String, byte[]> getMetaEntries() throws IOException {
+ HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>();
+
+ Iterator<ZipEntry> entryIterator = new EntryIterator(nativeHandle, "META-INF/");
+ while (entryIterator.hasNext()) {
+ final ZipEntry entry = entryIterator.next();
+ metaEntries.put(entry.getName(), Streams.readFully(getInputStream(entry)));
+ }
+
+ return metaEntries;
+ }
+
+ static final class JarFileInputStream extends FilterInputStream {
+ private final StrictJarVerifier.VerifierEntry entry;
+
+ private long count;
+ private boolean done = false;
+
+ JarFileInputStream(InputStream is, long size, StrictJarVerifier.VerifierEntry e) {
+ super(is);
+ entry = e;
+
+ count = size;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (done) {
+ return -1;
+ }
+ if (count > 0) {
+ int r = super.read();
+ if (r != -1) {
+ entry.write(r);
+ count--;
+ } else {
+ count = 0;
+ }
+ if (count == 0) {
+ done = true;
+ entry.verify();
+ }
+ return r;
+ } else {
+ done = true;
+ entry.verify();
+ return -1;
+ }
+ }
+
+ @Override
+ public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ if (done) {
+ return -1;
+ }
+ if (count > 0) {
+ int r = super.read(buffer, byteOffset, byteCount);
+ if (r != -1) {
+ int size = r;
+ if (count < size) {
+ size = (int) count;
+ }
+ entry.write(buffer, byteOffset, size);
+ count -= size;
+ } else {
+ count = 0;
+ }
+ if (count == 0) {
+ done = true;
+ entry.verify();
+ }
+ return r;
+ } else {
+ done = true;
+ entry.verify();
+ return -1;
+ }
+ }
+
+ @Override
+ public int available() throws IOException {
+ if (done) {
+ return 0;
+ }
+ return super.available();
+ }
+
+ @Override
+ public long skip(long byteCount) throws IOException {
+ return Streams.skipByReading(this, byteCount);
+ }
+ }
+
+ /** @hide */
+ public static class ZipInflaterInputStream extends InflaterInputStream {
+ private final ZipEntry entry;
+ private long bytesRead = 0;
+
+ public ZipInflaterInputStream(InputStream is, Inflater inf, int bsize, ZipEntry entry) {
+ super(is, inf, bsize);
+ this.entry = entry;
+ }
+
+ @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ final int i;
+ try {
+ i = super.read(buffer, byteOffset, byteCount);
+ } catch (IOException e) {
+ throw new IOException("Error reading data for " + entry.getName() + " near offset "
+ + bytesRead, e);
+ }
+ if (i == -1) {
+ if (entry.getSize() != bytesRead) {
+ throw new IOException("Size mismatch on inflated file: " + bytesRead + " vs "
+ + entry.getSize());
+ }
+ } else {
+ bytesRead += i;
+ }
+ return i;
+ }
+
+ @Override public int available() throws IOException {
+ if (closed) {
+ // Our superclass will throw an exception, but there's a jtreg test that
+ // explicitly checks that the InputStream returned from ZipFile.getInputStream
+ // returns 0 even when closed.
+ return 0;
+ }
+ return super.available() == 0 ? 0 : (int) (entry.getSize() - bytesRead);
+ }
+ }
+
+ /**
+ * Wrap a stream around a RandomAccessFile. The RandomAccessFile is shared
+ * among all streams returned by getInputStream(), so we have to synchronize
+ * access to it. (We can optimize this by adding buffering here to reduce
+ * collisions.)
+ *
+ * <p>We could support mark/reset, but we don't currently need them.
+ *
+ * @hide
+ */
+ public static class RAFStream extends InputStream {
+ private final RandomAccessFile sharedRaf;
+ private long endOffset;
+ private long offset;
+
+
+ public RAFStream(RandomAccessFile raf, long initialOffset, long endOffset) {
+ sharedRaf = raf;
+ offset = initialOffset;
+ this.endOffset = endOffset;
+ }
+
+ public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException {
+ this(raf, initialOffset, raf.length());
+ }
+
+ @Override public int available() throws IOException {
+ return (offset < endOffset ? 1 : 0);
+ }
+
+ @Override public int read() throws IOException {
+ return Streams.readSingleByte(this);
+ }
+
+ @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ synchronized (sharedRaf) {
+ final long length = endOffset - offset;
+ if (byteCount > length) {
+ byteCount = (int) length;
+ }
+ sharedRaf.seek(offset);
+ int count = sharedRaf.read(buffer, byteOffset, byteCount);
+ if (count > 0) {
+ offset += count;
+ return count;
+ } else {
+ return -1;
+ }
+ }
+ }
+
+ @Override public long skip(long byteCount) throws IOException {
+ if (byteCount > endOffset - offset) {
+ byteCount = endOffset - offset;
+ }
+ offset += byteCount;
+ return byteCount;
+ }
+ }
+
+
+ private static native long nativeOpenJarFile(String fileName) throws IOException;
+ private static native long nativeStartIteration(long nativeHandle, String prefix);
+ private static native ZipEntry nativeNextEntry(long iterationHandle);
+ private static native ZipEntry nativeFindEntry(long nativeHandle, String entryName);
+ private static native void nativeClose(long nativeHandle);
+}
diff --git a/core/java/android/util/jar/StrictJarManifest.java b/core/java/android/util/jar/StrictJarManifest.java
new file mode 100644
index 000000000000..dbb466cd1760
--- /dev/null
+++ b/core/java/android/util/jar/StrictJarManifest.java
@@ -0,0 +1,315 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.util.jar;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.jar.Attributes;
+import libcore.io.Streams;
+
+/**
+ * The {@code StrictJarManifest} class is used to obtain attribute information for a
+ * {@code StrictJarFile} and its entries.
+ *
+ * @hide
+ */
+public class StrictJarManifest implements Cloneable {
+ static final int LINE_LENGTH_LIMIT = 72;
+
+ private static final byte[] LINE_SEPARATOR = new byte[] { '\r', '\n' };
+
+ private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' };
+
+ private final Attributes mainAttributes;
+ private final HashMap<String, Attributes> entries;
+
+ static final class Chunk {
+ final int start;
+ final int end;
+
+ Chunk(int start, int end) {
+ this.start = start;
+ this.end = end;
+ }
+ }
+
+ private HashMap<String, Chunk> chunks;
+
+ /**
+ * The end of the main attributes section in the manifest is needed in
+ * verification.
+ */
+ private int mainEnd;
+
+ /**
+ * Creates a new {@code StrictJarManifest} instance.
+ */
+ public StrictJarManifest() {
+ entries = new HashMap<String, Attributes>();
+ mainAttributes = new Attributes();
+ }
+
+ /**
+ * Creates a new {@code StrictJarManifest} instance using the attributes obtained
+ * from the input stream.
+ *
+ * @param is
+ * {@code InputStream} to parse for attributes.
+ * @throws IOException
+ * if an IO error occurs while creating this {@code StrictJarManifest}
+ */
+ public StrictJarManifest(InputStream is) throws IOException {
+ this();
+ read(Streams.readFully(is));
+ }
+
+ /**
+ * Creates a new {@code StrictJarManifest} instance. The new instance will have the
+ * same attributes as those found in the parameter {@code StrictJarManifest}.
+ *
+ * @param man
+ * {@code StrictJarManifest} instance to obtain attributes from.
+ */
+ @SuppressWarnings("unchecked")
+ public StrictJarManifest(StrictJarManifest man) {
+ mainAttributes = (Attributes) man.mainAttributes.clone();
+ entries = (HashMap<String, Attributes>) ((HashMap<String, Attributes>) man
+ .getEntries()).clone();
+ }
+
+ StrictJarManifest(byte[] manifestBytes, boolean readChunks) throws IOException {
+ this();
+ if (readChunks) {
+ chunks = new HashMap<String, Chunk>();
+ }
+ read(manifestBytes);
+ }
+
+ /**
+ * Resets the both the main attributes as well as the entry attributes
+ * associated with this {@code StrictJarManifest}.
+ */
+ public void clear() {
+ entries.clear();
+ mainAttributes.clear();
+ }
+
+ /**
+ * Returns the {@code Attributes} associated with the parameter entry
+ * {@code name}.
+ *
+ * @param name
+ * the name of the entry to obtain {@code Attributes} from.
+ * @return the Attributes for the entry or {@code null} if the entry does
+ * not exist.
+ */
+ public Attributes getAttributes(String name) {
+ return getEntries().get(name);
+ }
+
+ /**
+ * Returns a map containing the {@code Attributes} for each entry in the
+ * {@code StrictJarManifest}.
+ *
+ * @return the map of entry attributes.
+ */
+ public Map<String, Attributes> getEntries() {
+ return entries;
+ }
+
+ /**
+ * Returns the main {@code Attributes} of the {@code JarFile}.
+ *
+ * @return main {@code Attributes} associated with the source {@code
+ * JarFile}.
+ */
+ public Attributes getMainAttributes() {
+ return mainAttributes;
+ }
+
+ /**
+ * Creates a copy of this {@code StrictJarManifest}. The returned {@code StrictJarManifest}
+ * will equal the {@code StrictJarManifest} from which it was cloned.
+ *
+ * @return a copy of this instance.
+ */
+ @Override
+ public Object clone() {
+ return new StrictJarManifest(this);
+ }
+
+ /**
+ * Writes this {@code StrictJarManifest}'s name/attributes pairs to the given {@code OutputStream}.
+ * The {@code MANIFEST_VERSION} or {@code SIGNATURE_VERSION} attribute must be set before
+ * calling this method, or no attributes will be written.
+ *
+ * @throws IOException
+ * If an error occurs writing the {@code StrictJarManifest}.
+ */
+ public void write(OutputStream os) throws IOException {
+ write(this, os);
+ }
+
+ /**
+ * Merges name/attribute pairs read from the input stream {@code is} into this manifest.
+ *
+ * @param is
+ * The {@code InputStream} to read from.
+ * @throws IOException
+ * If an error occurs reading the manifest.
+ */
+ public void read(InputStream is) throws IOException {
+ read(Streams.readFullyNoClose(is));
+ }
+
+ private void read(byte[] buf) throws IOException {
+ if (buf.length == 0) {
+ return;
+ }
+
+ StrictJarManifestReader im = new StrictJarManifestReader(buf, mainAttributes);
+ mainEnd = im.getEndOfMainSection();
+ im.readEntries(entries, chunks);
+ }
+
+ /**
+ * Returns the hash code for this instance.
+ *
+ * @return this {@code StrictJarManifest}'s hashCode.
+ */
+ @Override
+ public int hashCode() {
+ return mainAttributes.hashCode() ^ getEntries().hashCode();
+ }
+
+ /**
+ * Determines if the receiver is equal to the parameter object. Two {@code
+ * StrictJarManifest}s are equal if they have identical main attributes as well as
+ * identical entry attributes.
+ *
+ * @param o
+ * the object to compare against.
+ * @return {@code true} if the manifests are equal, {@code false} otherwise
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) {
+ return false;
+ }
+ if (o.getClass() != this.getClass()) {
+ return false;
+ }
+ if (!mainAttributes.equals(((StrictJarManifest) o).mainAttributes)) {
+ return false;
+ }
+ return getEntries().equals(((StrictJarManifest) o).getEntries());
+ }
+
+ Chunk getChunk(String name) {
+ return chunks.get(name);
+ }
+
+ void removeChunks() {
+ chunks = null;
+ }
+
+ int getMainAttributesEnd() {
+ return mainEnd;
+ }
+
+ /**
+ * Writes out the attribute information of the specified manifest to the
+ * specified {@code OutputStream}
+ *
+ * @param manifest
+ * the manifest to write out.
+ * @param out
+ * The {@code OutputStream} to write to.
+ * @throws IOException
+ * If an error occurs writing the {@code StrictJarManifest}.
+ */
+ static void write(StrictJarManifest manifest, OutputStream out) throws IOException {
+ CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
+ ByteBuffer buffer = ByteBuffer.allocate(LINE_LENGTH_LIMIT);
+
+ Attributes.Name versionName = Attributes.Name.MANIFEST_VERSION;
+ String version = manifest.mainAttributes.getValue(versionName);
+ if (version == null) {
+ versionName = Attributes.Name.SIGNATURE_VERSION;
+ version = manifest.mainAttributes.getValue(versionName);
+ }
+ if (version != null) {
+ writeEntry(out, versionName, version, encoder, buffer);
+ Iterator<?> entries = manifest.mainAttributes.keySet().iterator();
+ while (entries.hasNext()) {
+ Attributes.Name name = (Attributes.Name) entries.next();
+ if (!name.equals(versionName)) {
+ writeEntry(out, name, manifest.mainAttributes.getValue(name), encoder, buffer);
+ }
+ }
+ }
+ out.write(LINE_SEPARATOR);
+ Iterator<String> i = manifest.getEntries().keySet().iterator();
+ while (i.hasNext()) {
+ String key = i.next();
+ writeEntry(out, Attributes.Name.NAME, key, encoder, buffer);
+ Attributes attributes = manifest.entries.get(key);
+ Iterator<?> entries = attributes.keySet().iterator();
+ while (entries.hasNext()) {
+ Attributes.Name name = (Attributes.Name) entries.next();
+ writeEntry(out, name, attributes.getValue(name), encoder, buffer);
+ }
+ out.write(LINE_SEPARATOR);
+ }
+ }
+
+ private static void writeEntry(OutputStream os, Attributes.Name name,
+ String value, CharsetEncoder encoder, ByteBuffer bBuf) throws IOException {
+ String nameString = name.toString();
+ os.write(nameString.getBytes(StandardCharsets.US_ASCII));
+ os.write(VALUE_SEPARATOR);
+
+ encoder.reset();
+ bBuf.clear().limit(LINE_LENGTH_LIMIT - nameString.length() - 2);
+
+ CharBuffer cBuf = CharBuffer.wrap(value);
+
+ while (true) {
+ CoderResult r = encoder.encode(cBuf, bBuf, true);
+ if (CoderResult.UNDERFLOW == r) {
+ r = encoder.flush(bBuf);
+ }
+ os.write(bBuf.array(), bBuf.arrayOffset(), bBuf.position());
+ os.write(LINE_SEPARATOR);
+ if (CoderResult.UNDERFLOW == r) {
+ break;
+ }
+ os.write(' ');
+ bBuf.clear().limit(LINE_LENGTH_LIMIT - 1);
+ }
+ }
+}
diff --git a/core/java/android/util/jar/StrictJarManifestReader.java b/core/java/android/util/jar/StrictJarManifestReader.java
new file mode 100644
index 000000000000..9881bb003d03
--- /dev/null
+++ b/core/java/android/util/jar/StrictJarManifestReader.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.util.jar;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.Attributes;
+
+/**
+ * Reads a JAR file manifest. The specification is here:
+ * http://java.sun.com/javase/6/docs/technotes/guides/jar/jar.html
+ */
+class StrictJarManifestReader {
+ // There are relatively few unique attribute names,
+ // but a manifest might have thousands of entries.
+ private final HashMap<String, Attributes.Name> attributeNameCache = new HashMap<String, Attributes.Name>();
+
+ private final ByteArrayOutputStream valueBuffer = new ByteArrayOutputStream(80);
+
+ private final byte[] buf;
+
+ private final int endOfMainSection;
+
+ private int pos;
+
+ private Attributes.Name name;
+
+ private String value;
+
+ private int consecutiveLineBreaks = 0;
+
+ public StrictJarManifestReader(byte[] buf, Attributes main) throws IOException {
+ this.buf = buf;
+ while (readHeader()) {
+ main.put(name, value);
+ }
+ this.endOfMainSection = pos;
+ }
+
+ public void readEntries(Map<String, Attributes> entries, Map<String, StrictJarManifest.Chunk> chunks) throws IOException {
+ int mark = pos;
+ while (readHeader()) {
+ if (!Attributes.Name.NAME.equals(name)) {
+ throw new IOException("Entry is not named");
+ }
+ String entryNameValue = value;
+
+ Attributes entry = entries.get(entryNameValue);
+ if (entry == null) {
+ entry = new Attributes(12);
+ }
+
+ while (readHeader()) {
+ entry.put(name, value);
+ }
+
+ if (chunks != null) {
+ if (chunks.get(entryNameValue) != null) {
+ // TODO A bug: there might be several verification chunks for
+ // the same name. I believe they should be used to update
+ // signature in order of appearance; there are two ways to fix
+ // this: either use a list of chunks, or decide on used
+ // signature algorithm in advance and reread the chunks while
+ // updating the signature; for now a defensive error is thrown
+ throw new IOException("A jar verifier does not support more than one entry with the same name");
+ }
+ chunks.put(entryNameValue, new StrictJarManifest.Chunk(mark, pos));
+ mark = pos;
+ }
+
+ entries.put(entryNameValue, entry);
+ }
+ }
+
+ public int getEndOfMainSection() {
+ return endOfMainSection;
+ }
+
+ /**
+ * Read a single line from the manifest buffer.
+ */
+ private boolean readHeader() throws IOException {
+ if (consecutiveLineBreaks > 1) {
+ // break a section on an empty line
+ consecutiveLineBreaks = 0;
+ return false;
+ }
+ readName();
+ consecutiveLineBreaks = 0;
+ readValue();
+ // if the last line break is missed, the line
+ // is ignored by the reference implementation
+ return consecutiveLineBreaks > 0;
+ }
+
+ private void readName() throws IOException {
+ int mark = pos;
+
+ while (pos < buf.length) {
+ if (buf[pos++] != ':') {
+ continue;
+ }
+
+ String nameString = new String(buf, mark, pos - mark - 1, StandardCharsets.US_ASCII);
+
+ if (buf[pos++] != ' ') {
+ throw new IOException(String.format("Invalid value for attribute '%s'", nameString));
+ }
+
+ try {
+ name = attributeNameCache.get(nameString);
+ if (name == null) {
+ name = new Attributes.Name(nameString);
+ attributeNameCache.put(nameString, name);
+ }
+ } catch (IllegalArgumentException e) {
+ // new Attributes.Name() throws IllegalArgumentException but we declare IOException
+ throw new IOException(e.getMessage());
+ }
+ return;
+ }
+ }
+
+ private void readValue() throws IOException {
+ boolean lastCr = false;
+ int mark = pos;
+ int last = pos;
+ valueBuffer.reset();
+ while (pos < buf.length) {
+ byte next = buf[pos++];
+ switch (next) {
+ case 0:
+ throw new IOException("NUL character in a manifest");
+ case '\n':
+ if (lastCr) {
+ lastCr = false;
+ } else {
+ consecutiveLineBreaks++;
+ }
+ continue;
+ case '\r':
+ lastCr = true;
+ consecutiveLineBreaks++;
+ continue;
+ case ' ':
+ if (consecutiveLineBreaks == 1) {
+ valueBuffer.write(buf, mark, last - mark);
+ mark = pos;
+ consecutiveLineBreaks = 0;
+ continue;
+ }
+ }
+
+ if (consecutiveLineBreaks >= 1) {
+ pos--;
+ break;
+ }
+ last = pos;
+ }
+
+ valueBuffer.write(buf, mark, last - mark);
+ // A bit frustrating that that Charset.forName will be called
+ // again.
+ value = valueBuffer.toString(StandardCharsets.UTF_8.name());
+ }
+}
diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java
new file mode 100644
index 000000000000..ca2aec105bcf
--- /dev/null
+++ b/core/java/android/util/jar/StrictJarVerifier.java
@@ -0,0 +1,456 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.util.jar;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import libcore.io.Base64;
+import sun.security.jca.Providers;
+import sun.security.pkcs.PKCS7;
+
+/**
+ * Non-public class used by {@link JarFile} and {@link JarInputStream} to manage
+ * the verification of signed JARs. {@code JarFile} and {@code JarInputStream}
+ * objects are expected to have a {@code JarVerifier} instance member which
+ * can be used to carry out the tasks associated with verifying a signed JAR.
+ * These tasks would typically include:
+ * <ul>
+ * <li>verification of all signed signature files
+ * <li>confirmation that all signed data was signed only by the party or parties
+ * specified in the signature block data
+ * <li>verification that the contents of all signature files (i.e. {@code .SF}
+ * files) agree with the JAR entries information found in the JAR manifest.
+ * </ul>
+ */
+class StrictJarVerifier {
+ /**
+ * List of accepted digest algorithms. This list is in order from most
+ * preferred to least preferred.
+ */
+ private static final String[] DIGEST_ALGORITHMS = new String[] {
+ "SHA-512",
+ "SHA-384",
+ "SHA-256",
+ "SHA1",
+ };
+
+ private final String jarName;
+ private final StrictJarManifest manifest;
+ private final HashMap<String, byte[]> metaEntries;
+ private final int mainAttributesEnd;
+
+ private final Hashtable<String, HashMap<String, Attributes>> signatures =
+ new Hashtable<String, HashMap<String, Attributes>>(5);
+
+ private final Hashtable<String, Certificate[]> certificates =
+ new Hashtable<String, Certificate[]>(5);
+
+ private final Hashtable<String, Certificate[][]> verifiedEntries =
+ new Hashtable<String, Certificate[][]>();
+
+ /**
+ * Stores and a hash and a message digest and verifies that massage digest
+ * matches the hash.
+ */
+ static class VerifierEntry extends OutputStream {
+
+ private final String name;
+
+ private final MessageDigest digest;
+
+ private final byte[] hash;
+
+ private final Certificate[][] certChains;
+
+ private final Hashtable<String, Certificate[][]> verifiedEntries;
+
+ VerifierEntry(String name, MessageDigest digest, byte[] hash,
+ Certificate[][] certChains, Hashtable<String, Certificate[][]> verifedEntries) {
+ this.name = name;
+ this.digest = digest;
+ this.hash = hash;
+ this.certChains = certChains;
+ this.verifiedEntries = verifedEntries;
+ }
+
+ /**
+ * Updates a digest with one byte.
+ */
+ @Override
+ public void write(int value) {
+ digest.update((byte) value);
+ }
+
+ /**
+ * Updates a digest with byte array.
+ */
+ @Override
+ public void write(byte[] buf, int off, int nbytes) {
+ digest.update(buf, off, nbytes);
+ }
+
+ /**
+ * Verifies that the digests stored in the manifest match the decrypted
+ * digests from the .SF file. This indicates the validity of the
+ * signing, not the integrity of the file, as its digest must be
+ * calculated and verified when its contents are read.
+ *
+ * @throws SecurityException
+ * if the digest value stored in the manifest does <i>not</i>
+ * agree with the decrypted digest as recovered from the
+ * <code>.SF</code> file.
+ */
+ void verify() {
+ byte[] d = digest.digest();
+ if (!MessageDigest.isEqual(d, Base64.decode(hash))) {
+ throw invalidDigest(JarFile.MANIFEST_NAME, name, name);
+ }
+ verifiedEntries.put(name, certChains);
+ }
+ }
+
+ private static SecurityException invalidDigest(String signatureFile, String name,
+ String jarName) {
+ throw new SecurityException(signatureFile + " has invalid digest for " + name +
+ " in " + jarName);
+ }
+
+ private static SecurityException failedVerification(String jarName, String signatureFile) {
+ throw new SecurityException(jarName + " failed verification of " + signatureFile);
+ }
+
+ private static SecurityException failedVerification(String jarName, String signatureFile,
+ Throwable e) {
+ throw new SecurityException(jarName + " failed verification of " + signatureFile, e);
+ }
+
+
+ /**
+ * Constructs and returns a new instance of {@code JarVerifier}.
+ *
+ * @param name
+ * the name of the JAR file being verified.
+ */
+ StrictJarVerifier(String name, StrictJarManifest manifest,
+ HashMap<String, byte[]> metaEntries) {
+ jarName = name;
+ this.manifest = manifest;
+ this.metaEntries = metaEntries;
+ this.mainAttributesEnd = manifest.getMainAttributesEnd();
+ }
+
+ /**
+ * Invoked for each new JAR entry read operation from the input
+ * stream. This method constructs and returns a new {@link VerifierEntry}
+ * which contains the certificates used to sign the entry and its hash value
+ * as specified in the JAR MANIFEST format.
+ *
+ * @param name
+ * the name of an entry in a JAR file which is <b>not</b> in the
+ * {@code META-INF} directory.
+ * @return a new instance of {@link VerifierEntry} which can be used by
+ * callers as an {@link OutputStream}.
+ */
+ VerifierEntry initEntry(String name) {
+ // If no manifest is present by the time an entry is found,
+ // verification cannot occur. If no signature files have
+ // been found, do not verify.
+ if (manifest == null || signatures.isEmpty()) {
+ return null;
+ }
+
+ Attributes attributes = manifest.getAttributes(name);
+ // entry has no digest
+ if (attributes == null) {
+ return null;
+ }
+
+ ArrayList<Certificate[]> certChains = new ArrayList<Certificate[]>();
+ Iterator<Map.Entry<String, HashMap<String, Attributes>>> it = signatures.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, HashMap<String, Attributes>> entry = it.next();
+ HashMap<String, Attributes> hm = entry.getValue();
+ if (hm.get(name) != null) {
+ // Found an entry for entry name in .SF file
+ String signatureFile = entry.getKey();
+ Certificate[] certChain = certificates.get(signatureFile);
+ if (certChain != null) {
+ certChains.add(certChain);
+ }
+ }
+ }
+
+ // entry is not signed
+ if (certChains.isEmpty()) {
+ return null;
+ }
+ Certificate[][] certChainsArray = certChains.toArray(new Certificate[certChains.size()][]);
+
+ for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
+ final String algorithm = DIGEST_ALGORITHMS[i];
+ final String hash = attributes.getValue(algorithm + "-Digest");
+ if (hash == null) {
+ continue;
+ }
+ byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);
+
+ try {
+ return new VerifierEntry(name, MessageDigest.getInstance(algorithm), hashBytes,
+ certChainsArray, verifiedEntries);
+ } catch (NoSuchAlgorithmException ignored) {
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Add a new meta entry to the internal collection of data held on each JAR
+ * entry in the {@code META-INF} directory including the manifest
+ * file itself. Files associated with the signing of a JAR would also be
+ * added to this collection.
+ *
+ * @param name
+ * the name of the file located in the {@code META-INF}
+ * directory.
+ * @param buf
+ * the file bytes for the file called {@code name}.
+ * @see #removeMetaEntries()
+ */
+ void addMetaEntry(String name, byte[] buf) {
+ metaEntries.put(name.toUpperCase(Locale.US), buf);
+ }
+
+ /**
+ * If the associated JAR file is signed, check on the validity of all of the
+ * known signatures.
+ *
+ * @return {@code true} if the associated JAR is signed and an internal
+ * check verifies the validity of the signature(s). {@code false} if
+ * the associated JAR file has no entries at all in its {@code
+ * META-INF} directory. This situation is indicative of an invalid
+ * JAR file.
+ * <p>
+ * Will also return {@code true} if the JAR file is <i>not</i>
+ * signed.
+ * @throws SecurityException
+ * if the JAR file is signed and it is determined that a
+ * signature block file contains an invalid signature for the
+ * corresponding signature file.
+ */
+ synchronized boolean readCertificates() {
+ if (metaEntries.isEmpty()) {
+ return false;
+ }
+
+ Iterator<String> it = metaEntries.keySet().iterator();
+ while (it.hasNext()) {
+ String key = it.next();
+ if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {
+ verifyCertificate(key);
+ it.remove();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Verifies that the signature computed from {@code sfBytes} matches
+ * that specified in {@code blockBytes} (which is a PKCS7 block). Returns
+ * certificates listed in the PKCS7 block. Throws a {@code GeneralSecurityException}
+ * if something goes wrong during verification.
+ */
+ static Certificate[] verifyBytes(byte[] blockBytes, byte[] sfBytes)
+ throws GeneralSecurityException {
+
+ Object obj = null;
+ try {
+
+ obj = Providers.startJarVerification();
+ PKCS7 block = new PKCS7(blockBytes);
+ if (block.verify(sfBytes) == null) {
+ throw new GeneralSecurityException("Failed to verify signature");
+ }
+ X509Certificate[] blockCerts = block.getCertificates();
+ Certificate[] signerCertChain = null;
+ if (blockCerts != null) {
+ signerCertChain = new Certificate[blockCerts.length];
+ for (int i = 0; i < blockCerts.length; ++i) {
+ signerCertChain[i] = blockCerts[i];
+ }
+ }
+ return signerCertChain;
+ } catch (IOException e) {
+ throw new GeneralSecurityException("IO exception verifying jar cert", e);
+ } finally {
+ Providers.stopJarVerification(obj);
+ }
+ }
+
+ /**
+ * @param certFile
+ */
+ private void verifyCertificate(String certFile) {
+ // Found Digital Sig, .SF should already have been read
+ String signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF";
+ byte[] sfBytes = metaEntries.get(signatureFile);
+ if (sfBytes == null) {
+ return;
+ }
+
+ byte[] manifestBytes = metaEntries.get(JarFile.MANIFEST_NAME);
+ // Manifest entry is required for any verifications.
+ if (manifestBytes == null) {
+ return;
+ }
+
+ byte[] sBlockBytes = metaEntries.get(certFile);
+ try {
+ Certificate[] signerCertChain = verifyBytes(sBlockBytes, sfBytes);
+ if (signerCertChain != null) {
+ certificates.put(signatureFile, signerCertChain);
+ }
+ } catch (GeneralSecurityException e) {
+ throw failedVerification(jarName, signatureFile, e);
+ }
+
+ // Verify manifest hash in .sf file
+ Attributes attributes = new Attributes();
+ HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
+ try {
+ StrictJarManifestReader im = new StrictJarManifestReader(sfBytes, attributes);
+ im.readEntries(entries, null);
+ } catch (IOException e) {
+ return;
+ }
+
+ // Do we actually have any signatures to look at?
+ if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) {
+ return;
+ }
+
+ boolean createdBySigntool = false;
+ String createdBy = attributes.getValue("Created-By");
+ if (createdBy != null) {
+ createdBySigntool = createdBy.indexOf("signtool") != -1;
+ }
+
+ // Use .SF to verify the mainAttributes of the manifest
+ // If there is no -Digest-Manifest-Main-Attributes entry in .SF
+ // file, such as those created before java 1.5, then we ignore
+ // such verification.
+ if (mainAttributesEnd > 0 && !createdBySigntool) {
+ String digestAttribute = "-Digest-Manifest-Main-Attributes";
+ if (!verify(attributes, digestAttribute, manifestBytes, 0, mainAttributesEnd, false, true)) {
+ throw failedVerification(jarName, signatureFile);
+ }
+ }
+
+ // Use .SF to verify the whole manifest.
+ String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest";
+ if (!verify(attributes, digestAttribute, manifestBytes, 0, manifestBytes.length, false, false)) {
+ Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, Attributes> entry = it.next();
+ StrictJarManifest.Chunk chunk = manifest.getChunk(entry.getKey());
+ if (chunk == null) {
+ return;
+ }
+ if (!verify(entry.getValue(), "-Digest", manifestBytes,
+ chunk.start, chunk.end, createdBySigntool, false)) {
+ throw invalidDigest(signatureFile, entry.getKey(), jarName);
+ }
+ }
+ }
+ metaEntries.put(signatureFile, null);
+ signatures.put(signatureFile, entries);
+ }
+
+ /**
+ * Returns a <code>boolean</code> indication of whether or not the
+ * associated jar file is signed.
+ *
+ * @return {@code true} if the JAR is signed, {@code false}
+ * otherwise.
+ */
+ boolean isSignedJar() {
+ return certificates.size() > 0;
+ }
+
+ private boolean verify(Attributes attributes, String entry, byte[] data,
+ int start, int end, boolean ignoreSecondEndline, boolean ignorable) {
+ for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
+ String algorithm = DIGEST_ALGORITHMS[i];
+ String hash = attributes.getValue(algorithm + entry);
+ if (hash == null) {
+ continue;
+ }
+
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException e) {
+ continue;
+ }
+ if (ignoreSecondEndline && data[end - 1] == '\n' && data[end - 2] == '\n') {
+ md.update(data, start, end - 1 - start);
+ } else {
+ md.update(data, start, end - start);
+ }
+ byte[] b = md.digest();
+ byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);
+ return MessageDigest.isEqual(b, Base64.decode(hashBytes));
+ }
+ return ignorable;
+ }
+
+ /**
+ * Returns all of the {@link java.security.cert.Certificate} chains that
+ * were used to verify the signature on the JAR entry called
+ * {@code name}. Callers must not modify the returned arrays.
+ *
+ * @param name
+ * the name of a JAR entry.
+ * @return an array of {@link java.security.cert.Certificate} chains.
+ */
+ Certificate[][] getCertificateChains(String name) {
+ return verifiedEntries.get(name);
+ }
+
+ /**
+ * Remove all entries from the internal collection of data held about each
+ * JAR entry in the {@code META-INF} directory.
+ */
+ void removeMetaEntries() {
+ metaEntries.clear();
+ }
+}
diff --git a/core/java/android/view/IDockDividerVisibilityListener.aidl b/core/java/android/view/IDockDividerVisibilityListener.aidl
new file mode 100644
index 000000000000..a7d5cda9b44f
--- /dev/null
+++ b/core/java/android/view/IDockDividerVisibilityListener.aidl
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+ * Listener for showing/hiding of the dock divider. Will fire when an app is shown in side by side
+ * mode and a divider should be shown.
+ *
+ * @hide
+ */
+oneway interface IDockDividerVisibilityListener {
+ void onDockDividerVisibilityChanged(boolean visible);
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 64a046ec7c06..bd6553297929 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -29,6 +29,7 @@ import android.os.Bundle;
import android.os.IRemoteCallback;
import android.view.IApplicationToken;
import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.IDockDividerVisibilityListener;
import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
import android.view.IWindowSession;
@@ -290,10 +291,10 @@ interface IWindowManager
/**
* Create a screenshot of the applications currently displayed.
*
- * @param frameScale the scale to apply to the frame, only used when width = -1 and
+ * @param frameScale the scale to apply to the frame, only used when width = -1 and
* height = -1
*/
- Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight,
+ Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight,
float frameScale);
/**
@@ -348,4 +349,9 @@ interface IWindowManager
* stack size.
*/
void setDockedStackResizing(boolean resizing);
+
+ /**
+ * Registers a listener that will be called when the dock divider changes its visibility.
+ */
+ void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener);
}
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
new file mode 100644
index 000000000000..82f6c7f8ce44
--- /dev/null
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view;
+
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+/**
+ * A header of a notification view
+ *
+ * @hide
+ */
+@RemoteViews.RemoteView
+public class NotificationHeaderView extends LinearLayout {
+ private final int mHeaderMinWidth;
+ private View mAppName;
+ private View mSubTextView;
+ private OnClickListener mExpandClickListener;
+ private HeaderTouchListener mTouchListener = new HeaderTouchListener();
+ private View mExpandButton;
+ private View mIcon;
+ private TextView mChildCount;
+
+ public NotificationHeaderView(Context context) {
+ this(context, null);
+ }
+
+ public NotificationHeaderView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public NotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mHeaderMinWidth = getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.notification_header_shrink_min_width);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mAppName = findViewById(com.android.internal.R.id.app_name_text);
+ mSubTextView = findViewById(com.android.internal.R.id.header_sub_text);
+ mExpandButton = findViewById(com.android.internal.R.id.expand_button);
+ mIcon = findViewById(com.android.internal.R.id.icon);
+ mChildCount = (TextView) findViewById(com.android.internal.R.id.number_of_children);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int givenWidth = MeasureSpec.getSize(widthMeasureSpec);
+ final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
+ int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth,
+ MeasureSpec.AT_MOST);
+ int wrapContentHeightSpec = MeasureSpec.makeMeasureSpec(givenHeight,
+ MeasureSpec.AT_MOST);
+ int totalWidth = getPaddingStart() + getPaddingEnd();
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ // We'll give it the rest of the space in the end
+ continue;
+ }
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+ int childWidthSpec = getChildMeasureSpec(wrapContentWidthSpec,
+ lp.leftMargin + lp.rightMargin, lp.width);
+ int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec,
+ lp.topMargin + lp.bottomMargin, lp.height);
+ child.measure(childWidthSpec, childHeightSpec);
+ totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
+ }
+ if (totalWidth > givenWidth) {
+ int overFlow = totalWidth - givenWidth;
+ // We are overflowing, lets shrink
+ final int appWidth = mAppName.getMeasuredWidth();
+ if (appWidth > mHeaderMinWidth) {
+ int newSize = appWidth - Math.min(appWidth - mHeaderMinWidth, overFlow);
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+ mAppName.measure(childWidthSpec, wrapContentHeightSpec);
+ overFlow -= appWidth - newSize;
+ }
+ if (overFlow > 0 && mSubTextView.getVisibility() != GONE) {
+ // we're still too big
+ final int subTextWidth = mSubTextView.getMeasuredWidth();
+ int newSize = Math.max(0, subTextWidth - overFlow);
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+ mSubTextView.measure(childWidthSpec, wrapContentHeightSpec);
+ }
+ totalWidth = givenWidth;
+ }
+ setMeasuredDimension(totalWidth, givenHeight);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ updateTouchListener();
+ }
+
+ private void updateTouchListener() {
+ if (mExpandClickListener != null) {
+ mTouchListener.bindTouchRects();
+ }
+ }
+
+ @Override
+ public void setOnClickListener(@Nullable OnClickListener l) {
+ mExpandClickListener = l;
+ setOnTouchListener(mExpandClickListener != null ? mTouchListener : null);
+ updateTouchListener();
+ }
+
+ public void setChildCount(int childCount) {
+ if (childCount > 0) {
+ mChildCount.setText(getContext().getString(
+ com.android.internal.R.string.notification_children_count_bracketed,
+ childCount));
+ mChildCount.setVisibility(VISIBLE);
+ } else {
+ mChildCount.setVisibility(GONE);
+ }
+ }
+
+ public class HeaderTouchListener implements View.OnTouchListener {
+
+ private final ArrayList<Rect> mTouchRects = new ArrayList<>();
+ private int mTouchSlop;
+ private boolean mTrackGesture;
+ private float mDownX;
+ private float mDownY;
+
+ public HeaderTouchListener() {
+ }
+
+ public void bindTouchRects() {
+ mTouchRects.clear();
+ addRectAroundViewView(mIcon);
+ addRectAroundViewView(mExpandButton);
+ addInBetweenRect();
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ private void addInBetweenRect() {
+ final Rect r = new Rect();
+ r.top = 0;
+ r.bottom = (int) (32 * getResources().getDisplayMetrics().density);
+ Rect leftRect = mTouchRects.get(0);
+ r.left = leftRect.right;
+ Rect rightRect = mTouchRects.get(1);
+ r.right = rightRect.left;
+ mTouchRects.add(r);
+ }
+
+ private void addRectAroundViewView(View view) {
+ final Rect r = getRectAroundView(view);
+ mTouchRects.add(r);
+ }
+
+ private Rect getRectAroundView(View view) {
+ float size = 48 * getResources().getDisplayMetrics().density;
+ final Rect r = new Rect();
+ if (view.getVisibility() == GONE) {
+ view = getFirstChildNotGone();
+ r.left = (int) (view.getLeft() - size / 2.0f);
+ } else {
+ r.left = (int) ((view.getLeft() + view.getRight()) / 2.0f - size / 2.0f);
+ }
+ r.top = (int) ((view.getTop() + view.getBottom()) / 2.0f - size / 2.0f);
+ r.bottom = (int) (r.top + size);
+ r.right = (int) (r.left + size);
+ return r;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ switch (event.getActionMasked() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ mTrackGesture = false;
+ if (isInside(x, y)) {
+ mTrackGesture = true;
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mTrackGesture) {
+ if (Math.abs(mDownX - x) > mTouchSlop
+ || Math.abs(mDownY - y) > mTouchSlop) {
+ mTrackGesture = false;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mTrackGesture) {
+ mExpandClickListener.onClick(NotificationHeaderView.this);
+ }
+ break;
+ }
+ return mTrackGesture;
+ }
+
+ private boolean isInside(float x, float y) {
+ for (int i = 0; i < mTouchRects.size(); i++) {
+ Rect r = mTouchRects.get(i);
+ if (r.contains((int) x, (int) y)) {
+ mDownX = x;
+ mDownY = y;
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private View getFirstChildNotGone() {
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ return child;
+ }
+ }
+ return this;
+ }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 692fa6607063..da66f97c8e81 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -19429,6 +19429,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @attr ref android.R.styleable#View_minHeight
*/
+ @RemotableViewMethod
public void setMinimumHeight(int minHeight) {
mMinHeight = minHeight;
requestLayout();
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index ce1c10863acd..0ed72e4df02b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1638,6 +1638,47 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * Helper action to set layout margin on a View.
+ */
+ private class ViewMarginEndAction extends Action {
+ public ViewMarginEndAction(int viewId, int end) {
+ this.viewId = viewId;
+ this.end = end;
+ }
+
+ public ViewMarginEndAction(Parcel parcel) {
+ viewId = parcel.readInt();
+ end = parcel.readInt();
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(TAG);
+ dest.writeInt(viewId);
+ dest.writeInt(end);
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+ final View target = root.findViewById(viewId);
+ if (target == null) {
+ return;
+ }
+ ViewGroup.LayoutParams layoutParams = target.getLayoutParams();
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
+ ((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(end);
+ }
+ }
+
+ public String getActionName() {
+ return "ViewMarginEndAction";
+ }
+
+ int end;
+
+ public final static int TAG = 19;
+ }
+
+ /**
* Helper action to set a color filter on a compound drawable on a TextView. Supports relative
* (s/t/e/b) or cardinal (l/t/r/b) arrangement.
*/
@@ -1942,6 +1983,9 @@ public class RemoteViews implements Parcelable, Filter {
case SetRemoteInputsAction.TAG:
mActions.add(new SetRemoteInputsAction(parcel));
break;
+ case ViewMarginEndAction.TAG:
+ mActions.add(new ViewMarginEndAction(parcel));
+ break;
default:
throw new ActionException("Tag " + tag + " not found");
}
@@ -2549,6 +2593,19 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * @hide
+ * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}.
+ * Only works if the {@link View#getLayoutParams()} supports margins.
+ * Hidden for now since we don't want to support this for all different layout margins yet.
+ *
+ * @param viewId The id of the view to change
+ * @param endMargin the left padding in pixels
+ */
+ public void setViewLayoutMarginEnd(int viewId, int endMargin) {
+ addAction(new ViewMarginEndAction(viewId, endMargin));
+ }
+
+ /**
* Call a method taking one boolean on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 2f385c1f15ff..d666939ba5a0 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6828,7 +6828,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mEditor != null) mEditor.prepareCursorControllers();
}
- private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
+ /**
+ * @hide
+ */
+ protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
boolean useSaved) {
Layout result = null;
@@ -9708,7 +9711,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- TextDirectionHeuristic getTextDirectionHeuristic() {
+ /**
+ * @hide
+ */
+ protected TextDirectionHeuristic getTextDirectionHeuristic() {
if (hasPasswordTransformationMethod()) {
// passwords fields should be LTR
return TextDirectionHeuristics.LTR;
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index c992c70f73e4..cc677cc60acb 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -34,6 +34,7 @@ public class MetricsLogger implements MetricsConstants {
public static final int ACTION_ZEN_ALLOW_PEEK = 261;
public static final int ACTION_ZEN_ALLOW_LIGHTS = 262;
public static final int NOTIFICATION_TOPIC_NOTIFICATION = 263;
+ public static final int ACTION_DEFAULT_SMS_APP_CHANGED = 264;
public static void visible(Context context, int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
new file mode 100644
index 000000000000..c4ed2e10cffe
--- /dev/null
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -0,0 +1,89 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.text.BoringLayout;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+/**
+ * A TextView that can float around an image on the end.
+ *
+ * @hide
+ */
+@RemoteViews.RemoteView
+public class ImageFloatingTextView extends TextView {
+
+ private boolean mHasImage;
+
+ public ImageFloatingTextView(Context context) {
+ this(context, null);
+ }
+
+ public ImageFloatingTextView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ImageFloatingTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ImageFloatingTextView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
+ Layout.Alignment alignment, boolean shouldEllipsize,
+ TextUtils.TruncateAt effectiveEllipsize, boolean useSaved) {
+ CharSequence text = getText() == null ? "" : getText();
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(),
+ getPaint(), wantWidth)
+ .setAlignment(alignment)
+ .setTextDirection(getTextDirectionHeuristic())
+ .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
+ .setIncludePad(getIncludeFontPadding())
+ .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
+ // we set the endmargin on the first 2 lines. this works just in our case but that's
+ // sufficient for now.
+ int endMargin = (int) (getResources().getDisplayMetrics().density * 52);
+ int[] margins = mHasImage ? new int[] {endMargin, endMargin, 0} : null;
+ if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ builder.setIndents(margins, null);
+ } else {
+ builder.setIndents(null, margins);
+ }
+
+ return builder.build();
+ }
+
+ @RemotableViewMethod
+ public void setHasImage(boolean hasImage) {
+ mHasImage = hasImage;
+ // The new layout will be automatically created when the text is
+ // set again by the notification.
+ }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 4d648cea4675..0473016fd3ef 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -93,6 +93,7 @@ LOCAL_SRC_FILES:= \
android_util_Process.cpp \
android_util_StringBlock.cpp \
android_util_XmlBlock.cpp \
+ android_util_jar_StrictJarFile.cpp \
android_graphics_Canvas.cpp \
android_graphics_Picture.cpp \
android/graphics/AutoDecodeCancel.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index bd41c5d37336..f6f45b5a4efa 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -176,6 +176,7 @@ extern int register_android_app_backup_FullBackup(JNIEnv *env);
extern int register_android_app_ActivityThread(JNIEnv *env);
extern int register_android_app_NativeActivity(JNIEnv *env);
extern int register_android_media_RemoteDisplay(JNIEnv *env);
+extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
extern int register_android_view_InputChannel(JNIEnv* env);
extern int register_android_view_InputDevice(JNIEnv* env);
extern int register_android_view_InputEventReceiver(JNIEnv* env);
@@ -1359,6 +1360,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_app_backup_FullBackup),
REG_JNI(register_android_app_ActivityThread),
REG_JNI(register_android_app_NativeActivity),
+ REG_JNI(register_android_util_jar_StrictJarFile),
REG_JNI(register_android_view_InputChannel),
REG_JNI(register_android_view_InputEventReceiver),
REG_JNI(register_android_view_InputEventSender),
@@ -1374,6 +1376,8 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_animation_PropertyValuesHolder),
REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
REG_JNI(register_com_android_internal_net_NetworkStatsFactory),
+
+
};
/*
diff --git a/core/jni/android_util_jar_StrictJarFile.cpp b/core/jni/android_util_jar_StrictJarFile.cpp
new file mode 100644
index 000000000000..7f8f70832ab7
--- /dev/null
+++ b/core/jni/android_util_jar_StrictJarFile.cpp
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#define LOG_TAG "StrictJarFile"
+
+#include <memory>
+#include <string>
+
+#include "JNIHelp.h"
+#include "JniConstants.h"
+#include "ScopedLocalRef.h"
+#include "ScopedUtfChars.h"
+#include "jni.h"
+#include "ziparchive/zip_archive.h"
+#include "cutils/log.h"
+
+namespace android {
+
+// The method ID for ZipEntry.<init>(String,String,JJJIII[BJJ)
+static jmethodID zipEntryCtor;
+
+static void throwIoException(JNIEnv* env, const int32_t errorCode) {
+ jniThrowException(env, "java/io/IOException", ErrorCodeString(errorCode));
+}
+
+static jobject newZipEntry(JNIEnv* env, const ZipEntry& entry, jstring entryName) {
+ return env->NewObject(JniConstants::zipEntryClass,
+ zipEntryCtor,
+ entryName,
+ NULL, // comment
+ static_cast<jlong>(entry.crc32),
+ static_cast<jlong>(entry.compressed_length),
+ static_cast<jlong>(entry.uncompressed_length),
+ static_cast<jint>(entry.method),
+ static_cast<jint>(0), // time
+ NULL, // byte[] extra
+ static_cast<jlong>(entry.offset));
+}
+
+static jlong StrictJarFile_nativeOpenJarFile(JNIEnv* env, jobject, jstring fileName) {
+ ScopedUtfChars fileChars(env, fileName);
+ if (fileChars.c_str() == NULL) {
+ return static_cast<jlong>(-1);
+ }
+
+ ZipArchiveHandle handle;
+ int32_t error = OpenArchive(fileChars.c_str(), &handle);
+ if (error) {
+ CloseArchive(handle);
+ throwIoException(env, error);
+ return static_cast<jlong>(-1);
+ }
+
+ return reinterpret_cast<jlong>(handle);
+}
+
+class IterationHandle {
+ public:
+ IterationHandle() :
+ cookie_(NULL) {
+ }
+
+ void** CookieAddress() {
+ return &cookie_;
+ }
+
+ ~IterationHandle() {
+ EndIteration(cookie_);
+ }
+
+ private:
+ void* cookie_;
+};
+
+
+static jlong StrictJarFile_nativeStartIteration(JNIEnv* env, jobject, jlong nativeHandle,
+ jstring prefix) {
+ ScopedUtfChars prefixChars(env, prefix);
+ if (prefixChars.c_str() == NULL) {
+ return static_cast<jlong>(-1);
+ }
+
+ IterationHandle* handle = new IterationHandle();
+ int32_t error = 0;
+ if (prefixChars.size() == 0) {
+ error = StartIteration(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
+ handle->CookieAddress(), NULL, NULL);
+ } else {
+ ZipString entry_name(prefixChars.c_str());
+ error = StartIteration(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
+ handle->CookieAddress(), &entry_name, NULL);
+ }
+
+ if (error) {
+ throwIoException(env, error);
+ return static_cast<jlong>(-1);
+ }
+
+ return reinterpret_cast<jlong>(handle);
+}
+
+static jobject StrictJarFile_nativeNextEntry(JNIEnv* env, jobject, jlong iterationHandle) {
+ ZipEntry data;
+ ZipString entryName;
+
+ IterationHandle* handle = reinterpret_cast<IterationHandle*>(iterationHandle);
+ const int32_t error = Next(*handle->CookieAddress(), &data, &entryName);
+ if (error) {
+ delete handle;
+ return NULL;
+ }
+
+ std::unique_ptr<char[]> entryNameCString(new char[entryName.name_length + 1]);
+ memcpy(entryNameCString.get(), entryName.name, entryName.name_length);
+ entryNameCString[entryName.name_length] = '\0';
+ ScopedLocalRef<jstring> entryNameString(env, env->NewStringUTF(entryNameCString.get()));
+
+ return newZipEntry(env, data, entryNameString.get());
+}
+
+static jobject StrictJarFile_nativeFindEntry(JNIEnv* env, jobject, jlong nativeHandle,
+ jstring entryName) {
+ ScopedUtfChars entryNameChars(env, entryName);
+ if (entryNameChars.c_str() == NULL) {
+ return NULL;
+ }
+
+ ZipEntry data;
+ const int32_t error = FindEntry(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
+ ZipString(entryNameChars.c_str()), &data);
+ if (error) {
+ return NULL;
+ }
+
+ return newZipEntry(env, data, entryName);
+}
+
+static void StrictJarFile_nativeClose(JNIEnv*, jobject, jlong nativeHandle) {
+ CloseArchive(reinterpret_cast<ZipArchiveHandle>(nativeHandle));
+}
+
+static JNINativeMethod gMethods[] = {
+ NATIVE_METHOD(StrictJarFile, nativeOpenJarFile, "(Ljava/lang/String;)J"),
+ NATIVE_METHOD(StrictJarFile, nativeStartIteration, "(JLjava/lang/String;)J"),
+ NATIVE_METHOD(StrictJarFile, nativeNextEntry, "(J)Ljava/util/zip/ZipEntry;"),
+ NATIVE_METHOD(StrictJarFile, nativeFindEntry, "(JLjava/lang/String;)Ljava/util/zip/ZipEntry;"),
+ NATIVE_METHOD(StrictJarFile, nativeClose, "(J)V"),
+};
+
+void register_android_util_jar_StrictJarFile(JNIEnv* env) {
+ jniRegisterNativeMethods(env, "android/util/jar/StrictJarFile", gMethods, NELEM(gMethods));
+
+ zipEntryCtor = env->GetMethodID(JniConstants::zipEntryClass, "<init>",
+ "(Ljava/lang/String;Ljava/lang/String;JJJII[BJ)V");
+ LOG_ALWAYS_FATAL_IF(zipEntryCtor == NULL, "Unable to find ZipEntry.<init>");
+}
+
+}; // namespace android
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 57338bee6366..568b60162875 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1877,6 +1877,11 @@
<permission android:name="android.permission.MANAGE_APP_TOKENS"
android:protectionLevel="signature" />
+ <!-- Allows System UI to register listeners for events from Window Manager.
+ @hide -->
+ <permission android:name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS"
+ android:protectionLevel="signature" />
+
<!-- @hide Allows the application to temporarily freeze the screen for a
full-screen transition. -->
<permission android:name="android.permission.FREEZE_SCREEN"
diff --git a/core/res/res/drawable/ic_arrow_drop_down.xml b/core/res/res/drawable/ic_arrow_drop_down.xml
new file mode 100644
index 000000000000..c8bb411a1f60
--- /dev/null
+++ b/core/res/res/drawable/ic_arrow_drop_down.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="14.0dp"
+ android:height="14.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M16.600000,8.600000l-4.600000,4.599999 -4.600000,-4.599999 -1.400000,1.400000 6.000000,6.000000 6.000000,-6.000000z"
+ android:fillColor="#FF000000"/>
+</vector>
diff --git a/core/res/res/drawable/ic_arrow_up_14dp.xml b/core/res/res/drawable/ic_arrow_up_14dp.xml
new file mode 100644
index 000000000000..c4cc0d1599b4
--- /dev/null
+++ b/core/res/res/drawable/ic_arrow_up_14dp.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="14.0dp"
+ android:height="14.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M12.000000,8.000000l-6.000000,6.000000 1.400000,1.400000 4.600000,-4.599999 4.600000,4.599999 1.400000,-1.400000z"
+ android:fillColor="#FF000000"/>
+</vector>
diff --git a/core/res/res/layout/notification_material_action.xml b/core/res/res/layout/notification_material_action.xml
index f4bc918e3d4c..62602d8016b3 100644
--- a/core/res/res/layout/notification_material_action.xml
+++ b/core/res/res/layout/notification_material_action.xml
@@ -21,7 +21,7 @@
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_gravity="center"
- android:layout_marginStart="8dp"
+ android:layout_marginStart="4dp"
android:textColor="@color/secondary_text_material_light"
android:singleLine="true"
android:ellipsize="end"
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index edaf020f6b7e..2a89faa2abf2 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -17,13 +17,14 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/actions_container"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
+ android:layout_height="wrap_content"
+ android:layout_marginStart="-16dp"
+ android:layout_marginEnd="-16dp">
<LinearLayout
android:id="@+id/actions"
android:layout_width="match_parent"
android:layout_height="56dp"
- android:paddingEnd="8dp"
+ android:paddingEnd="4dp"
android:orientation="horizontal"
android:visibility="gone"
android:background="#ffeeeeee"
diff --git a/core/res/res/layout/notification_material_media_action.xml b/core/res/res/layout/notification_material_media_action.xml
index 1d52e546ce94..19a6f8473b0c 100644
--- a/core/res/res/layout/notification_material_media_action.xml
+++ b/core/res/res/layout/notification_material_media_action.xml
@@ -19,10 +19,12 @@
style="@android:style/Widget.Material.Button.Borderless.Small"
android:id="@+id/action0"
android:layout_width="48dp"
- android:layout_height="match_parent"
- android:layout_marginLeft="2dp"
- android:layout_marginRight="2dp"
- android:layout_weight="1"
+ android:layout_height="48dp"
+ android:paddingBottom="8dp"
+ android:paddingTop="8dp"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:layout_marginEnd="2dp"
android:gravity="center"
android:background="@drawable/notification_material_media_action_background"
/>
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
new file mode 100644
index 000000000000..aceae9f41a71
--- /dev/null
+++ b/core/res/res/layout/notification_template_header.xml
@@ -0,0 +1,132 @@
+<?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
+ -->
+
+<NotificationHeaderView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/notification_header"
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:clipChildren="false"
+ android:layout_gravity="start|top"
+ android:gravity="center_vertical"
+ android:paddingTop="5dp"
+ android:paddingBottom="16dp"
+ android:paddingStart="@dimen/notification_content_margin_start"
+ android:paddingEnd="16dp">
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="18dp"
+ android:layout_height="18dp"
+ android:layout_marginEnd="3dp"
+ />
+ <TextView
+ android:id="@+id/number_of_children"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.Notification"
+ android:layout_marginEnd="3dp"
+ android:layout_marginStart="2dp"
+ android:visibility="gone"
+ android:singleLine="true"
+ />
+ <TextView
+ android:id="@+id/app_name_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:layout_marginStart="3dp"
+ android:layout_marginEnd="2dp"
+ android:singleLine="true"
+ />
+ <TextView
+ android:id="@+id/sub_text_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@string/notification_header_divider_symbol"
+ android:visibility="gone"/>
+ <TextView
+ android:id="@+id/header_sub_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:visibility="gone"
+ android:singleLine="true"/>
+ <TextView
+ android:id="@+id/content_info_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@string/notification_header_divider_symbol"
+ android:singleLine="true"
+ android:visibility="gone"/>
+ <TextView
+ android:id="@+id/header_content_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:visibility="gone"
+ android:maxWidth="72dp"
+ android:singleLine="true"/>
+ <TextView
+ android:id="@+id/time_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@string/notification_header_divider_symbol"
+ android:singleLine="true"
+ android:visibility="gone"/>
+ <ViewStub
+ android:id="@+id/time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:layout="@layout/notification_template_part_time"
+ android:visibility="gone"
+ />
+ <ViewStub
+ android:id="@+id/chronometer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:layout="@layout/notification_template_part_chronometer"
+ android:visibility="gone"
+ />
+ <ImageView
+ android:id="@+id/expand_button"
+ android:background="@null"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="1dp"
+ android:src="@drawable/ic_arrow_drop_down"
+ android:visibility="gone"
+ />
+</NotificationHeaderView>
+
diff --git a/core/res/res/layout/notification_template_icon_group.xml b/core/res/res/layout/notification_template_icon_group.xml
deleted file mode 100644
index fa6616327cc0..000000000000
--- a/core/res/res/layout/notification_template_icon_group.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2014 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:internal="http://schemas.android.com/apk/prv/res/android"
- android:layout_width="@dimen/notification_large_icon_width"
- android:layout_height="@dimen/notification_large_icon_height"
- android:id="@+id/icon_group"
- >
- <ImageView android:id="@+id/icon"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="12dp"
- android:layout_marginStart="12dp"
- android:layout_marginEnd="12dp"
- android:scaleType="centerInside"
- />
- <ImageView android:id="@+id/right_icon"
- android:layout_width="16dp"
- android:layout_height="16dp"
- android:padding="3dp"
- android:layout_gravity="end|bottom"
- android:scaleType="centerInside"
- android:visibility="gone"
- android:layout_marginEnd="8dp"
- android:layout_marginBottom="8dp"
- />
-</FrameLayout>
-
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 94bbec805520..b69eb24eac16 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -18,24 +18,32 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
- android:layout_height="64dp"
+ android:layout_height="wrap_content"
android:tag="base"
>
- <include layout="@layout/notification_template_icon_group"
- android:layout_width="@dimen/notification_large_icon_width"
- android:layout_height="@dimen/notification_large_icon_height"
- />
+ <include layout="@layout/notification_template_header" />
<LinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top"
- android:layout_marginStart="@dimen/notification_large_icon_width"
- android:minHeight="@dimen/notification_large_icon_height"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:minHeight="@dimen/notification_min_content_height"
android:orientation="vertical"
>
<include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line2" />
<include layout="@layout/notification_template_part_line3" />
</LinearLayout>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginBottom="11dp"
+ android:layout_marginEnd="@dimen/notification_content_margin_end">
+ <include layout="@layout/notification_template_progress" />
+ </FrameLayout>
+ <include layout="@layout/notification_template_right_icon" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index 97df978ed94d..8c78b8d2d671 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -14,71 +14,56 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:orientation="vertical"
android:tag="big"
>
- <include layout="@layout/notification_template_icon_group"
- android:layout_width="@dimen/notification_large_icon_width"
- android:layout_height="@dimen/notification_large_icon_height"
- />
- <LinearLayout
- android:id="@+id/notification_main_column"
+ <FrameLayout
+ android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/notification_min_height"
android:layout_gravity="top"
- android:layout_marginStart="@dimen/notification_large_icon_width"
- android:minHeight="@dimen/notification_large_icon_height"
- android:orientation="vertical"
+ android:tag="base"
>
- <include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line2" />
+ <include layout="@layout/notification_template_header" />
<LinearLayout
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:orientation="horizontal"
- android:gravity="top"
+ android:layout_height="match_parent"
+ android:layout_gravity="top"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:minHeight="@dimen/notification_min_content_height"
+ android:orientation="vertical"
>
- <TextView android:id="@+id/big_text"
- android:textAppearance="@style/TextAppearance.Material.Notification"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:singleLine="false"
- android:visibility="gone"
- />
- <ImageView android:id="@+id/profile_badge_large_template"
- android:layout_width="@dimen/notification_badge_size"
- android:layout_height="@dimen/notification_badge_size"
- android:layout_weight="0"
- android:layout_marginStart="4dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
+ <include layout="@layout/notification_template_part_line1" />
+ <include layout="@layout/notification_template_part_line3" />
</LinearLayout>
- <include
- layout="@layout/notification_template_part_line3"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- />
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_marginTop="10dp"
- android:id="@+id/action_divider"
- android:visibility="gone"
- android:background="@drawable/notification_template_divider" />
- <include
- layout="@layout/notification_material_action_list"
- android:layout_marginStart="-8dp"
+ <FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- />
- </LinearLayout>
-</FrameLayout>
+ android:layout_gravity="bottom"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginBottom="11dp"
+ android:layout_marginEnd="@dimen/notification_content_margin_end">
+ <include layout="@layout/notification_template_progress" />
+ </FrameLayout>
+ <include layout="@layout/notification_template_right_icon" />
+ </FrameLayout>
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:id="@+id/action_divider"
+ android:visibility="gone"
+ android:background="@drawable/notification_template_divider" />
+ <include
+ layout="@layout/notification_material_action_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+</LinearLayout>
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index 7fd93dec6998..0427c8a00db3 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -15,44 +15,53 @@
~ limitations under the License
-->
+<!-- Layout for the expanded media notification -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="128dp"
android:background="#00000000"
- android:tag="bigMedia"
+ android:tag="bigMediaNarrow"
>
- <include layout="@layout/notification_template_icon_group"
- android:layout_width="@dimen/notification_large_icon_width"
- android:layout_height="@dimen/notification_large_icon_height"
- />
+ <include layout="@layout/notification_template_header"
+ android:layout_width="fill_parent"
+ android:layout_height="48dp"
+ android:layout_marginEnd="106dp"/>
<LinearLayout
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_large_icon_width"
- android:minHeight="@dimen/notification_large_icon_height"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginEnd="24dp"
+ android:layout_toStartOf="@id/right_icon"
+ android:minHeight="@dimen/notification_min_content_height"
android:orientation="vertical"
>
<include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line2" />
<include layout="@layout/notification_template_part_line3" />
</LinearLayout>
<LinearLayout
android:id="@+id/media_actions"
- android:layout_width="match_parent"
- android:layout_height="48dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
- android:layout_marginStart="12dp"
- android:layout_marginEnd="12dp"
+ android:layout_alignParentStart="true"
+ android:paddingStart="8dp"
+ android:paddingBottom="8dp"
android:orientation="horizontal"
android:layoutDirection="ltr"
>
<!-- media buttons will be added here -->
</LinearLayout>
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_above="@id/media_actions"
- android:id="@+id/action_divider"
- android:background="@drawable/notification_template_divider_media" />
+
+ <ImageView android:id="@+id/right_icon"
+ android:layout_width="96dp"
+ android:layout_height="96dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginTop="16dp"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentTop="true"
+ android:scaleType="centerCrop"
+ />
</RelativeLayout>
diff --git a/core/res/res/layout/notification_template_material_big_media_narrow.xml b/core/res/res/layout/notification_template_material_big_media_narrow.xml
deleted file mode 100644
index 807cfaf6c963..000000000000
--- a/core/res/res/layout/notification_template_material_big_media_narrow.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2014 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<!-- Layout to be used with only max 3 actions. It has a much larger picture at the left side-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/status_bar_latest_event_content"
- android:layout_width="match_parent"
- android:layout_height="128dp"
- android:background="#00000000"
- android:tag="bigMediaNarrow"
- >
- <ImageView android:id="@+id/icon"
- android:layout_width="128dp"
- android:layout_height="128dp"
- android:scaleType="centerCrop"
- />
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="12dp"
- android:layout_toEndOf="@id/icon"
- android:minHeight="@dimen/notification_large_icon_height"
- android:orientation="vertical"
- >
- <include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line2" />
- <include layout="@layout/notification_template_part_line3" />
- </LinearLayout>
- <LinearLayout
- android:id="@+id/media_actions"
- android:layout_width="match_parent"
- android:layout_height="48dp"
- android:layout_toEndOf="@id/icon"
- android:layout_alignParentBottom="true"
- android:layout_marginStart="12dp"
- android:layout_marginEnd="12dp"
- android:orientation="horizontal"
- android:layoutDirection="ltr"
- >
- <!-- media buttons will be added here -->
- </LinearLayout>
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:layout_toEndOf="@id/icon"
- android:layout_above="@id/media_actions"
- android:id="@+id/action_divider"
- android:background="@drawable/notification_template_divider_media" />
-</RelativeLayout>
diff --git a/core/res/res/layout/notification_template_material_big_picture.xml b/core/res/res/layout/notification_template_material_big_picture.xml
index f3768b549b1f..74e7775d62a8 100644
--- a/core/res/res/layout/notification_template_material_big_picture.xml
+++ b/core/res/res/layout/notification_template_material_big_picture.xml
@@ -21,39 +21,45 @@
android:layout_height="match_parent"
android:tag="bigPicture"
>
- <ImageView
- android:id="@+id/big_picture"
- android:layout_width="match_parent"
- android:layout_height="192dp"
- android:layout_marginTop="64dp"
- android:layout_gravity="bottom"
- android:scaleType="centerCrop"
- />
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="6dp"
- android:layout_marginTop="64dp"
- android:scaleType="fitXY"
- android:src="@drawable/title_bar_shadow"
- />
- <include layout="@layout/notification_template_material_base"
- android:layout_width="match_parent"
- android:layout_height="64dp"
- />
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="208dp"
- android:paddingStart="@dimen/notification_large_icon_width"
- android:layout_gravity="bottom"
- android:background="#CCEEEEEE"
- >
- <include
- layout="@layout/notification_material_action_list"
- android:id="@+id/actions"
- android:layout_gravity="bottom"
+ <include layout="@layout/notification_template_header" />
+ <include layout="@layout/notification_template_right_icon" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="top"
+ android:paddingStart="@dimen/notification_content_margin_start"
+ android:paddingEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:clipToPadding="false"
+ android:orientation="vertical"
+ >
+ <LinearLayout
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- />
- </FrameLayout>
+ android:orientation="vertical">
+ <include layout="@layout/notification_template_part_line1"/>
+ <include layout="@layout/notification_template_progress"/>
+ <include layout="@layout/notification_template_part_line3"/>
+ </LinearLayout>
+ <ImageView
+ android:id="@+id/big_picture"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:adjustViewBounds="true"
+ android:layout_weight="1"
+ android:layout_marginTop="13dp"
+ android:layout_marginBottom="16dp"
+ android:scaleType="centerCrop"
+ />
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_marginStart="-16dp"
+ android:layout_marginEnd="-16dp"
+ android:id="@+id/action_divider"
+ android:visibility="gone"
+ android:background="@drawable/notification_template_divider" />
+ <include layout="@layout/notification_material_action_list" />
+ </LinearLayout>
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml
index 7ae29fb3467c..354c0fb88234 100644
--- a/core/res/res/layout/notification_template_material_big_text.xml
+++ b/core/res/res/layout/notification_template_material_big_text.xml
@@ -21,31 +21,30 @@
android:layout_height="wrap_content"
android:tag="bigText"
>
- <include layout="@layout/notification_template_icon_group"
- android:layout_width="@dimen/notification_large_icon_width"
- android:layout_height="@dimen/notification_large_icon_height"
- />
+ <include layout="@layout/notification_template_header" />
<LinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
- android:layout_marginStart="@dimen/notification_large_icon_width"
- android:minHeight="@dimen/notification_large_icon_height"
+ android:paddingStart="@dimen/notification_content_margin_start"
+ android:paddingEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:clipToPadding="false"
+ android:minHeight="@dimen/notification_min_content_height"
android:orientation="vertical"
>
<include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line2" />
+ <include layout="@layout/notification_template_progress" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
- android:layout_marginBottom="10dp"
+ android:paddingBottom="13dp"
android:orientation="horizontal"
android:gravity="top"
android:layout_weight="1"
>
- <TextView android:id="@+id/big_text"
+ <com.android.internal.widget.ImageFloatingTextView android:id="@+id/big_text"
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -66,27 +65,12 @@
<ImageView
android:layout_width="match_parent"
android:layout_height="1dp"
+ android:layout_marginStart="-16dp"
+ android:layout_marginEnd="-16dp"
android:id="@+id/action_divider"
android:visibility="gone"
android:background="@drawable/notification_template_divider" />
- <include
- layout="@layout/notification_material_action_list"
- android:layout_marginStart="-8dp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="1dip"
- android:id="@+id/overflow_divider"
- android:visibility="visible"
- android:background="@drawable/notification_template_divider" />
- <include
- layout="@layout/notification_template_part_line3"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:layout_marginTop="8dp"
- android:layout_marginBottom="10dp" />
+ <include layout="@layout/notification_material_action_list" />
</LinearLayout>
+ <include layout="@layout/notification_template_right_icon" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_inbox.xml b/core/res/res/layout/notification_template_material_inbox.xml
index 950ae4034564..4f12d766e638 100644
--- a/core/res/res/layout/notification_template_material_inbox.xml
+++ b/core/res/res/layout/notification_template_material_inbox.xml
@@ -21,21 +21,26 @@
android:layout_height="wrap_content"
android:tag="inbox"
>
- <include layout="@layout/notification_template_icon_group"
- android:layout_width="@dimen/notification_large_icon_width"
- android:layout_height="@dimen/notification_large_icon_height"
- />
+ <include layout="@layout/notification_template_header" />
<LinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
- android:layout_marginStart="@dimen/notification_large_icon_width"
- android:minHeight="@dimen/notification_large_icon_height"
+ android:paddingStart="@dimen/notification_content_margin_start"
+ android:paddingEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:minHeight="@dimen/notification_min_content_height"
+ android:clipToPadding="false"
android:orientation="vertical"
>
- <include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line2" />
+ <include layout="@layout/notification_template_part_line1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ <include layout="@layout/notification_template_progress"
+ android:layout_width="match_parent"
+ android:layout_height="15dp"
+ android:layout_marginTop="4dp"/>
<!-- We can't have another vertical linear layout here with weight != 0 so this forces us to
put the badge on the first line. -->
@@ -43,7 +48,6 @@
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
android:orientation="horizontal"
>
<TextView android:id="@+id/inbox_text0"
@@ -69,7 +73,6 @@
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
android:singleLine="true"
android:ellipsize="end"
android:visibility="gone"
@@ -79,7 +82,6 @@
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
android:singleLine="true"
android:ellipsize="end"
android:visibility="gone"
@@ -89,7 +91,6 @@
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
android:singleLine="true"
android:ellipsize="end"
android:visibility="gone"
@@ -99,7 +100,6 @@
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
android:singleLine="true"
android:ellipsize="end"
android:visibility="gone"
@@ -109,7 +109,6 @@
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
android:singleLine="true"
android:ellipsize="end"
android:visibility="gone"
@@ -119,27 +118,15 @@
android:textAppearance="@style/TextAppearance.Material.Notification"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_marginEnd="8dp"
android:singleLine="true"
android:ellipsize="end"
android:visibility="gone"
android:layout_weight="1"
/>
- <TextView android:id="@+id/inbox_more"
- android:textAppearance="@style/TextAppearance.Material.Notification"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_marginEnd="8dp"
- android:singleLine="true"
- android:ellipsize="end"
- android:visibility="gone"
- android:layout_weight="1"
- android:text="@android:string/notification_inbox_ellipsis"
- />
<FrameLayout
android:id="@+id/inbox_end_pad"
android:layout_width="match_parent"
- android:layout_height="10dp"
+ android:layout_height="13dp"
android:visibility="gone"
android:layout_weight="0"
/>
@@ -148,27 +135,10 @@
android:layout_height="1dip"
android:id="@+id/action_divider"
android:visibility="gone"
+ android:layout_marginStart="-16dp"
+ android:layout_marginEnd="-16dp"
android:background="@drawable/notification_template_divider" />
- <include
- layout="@layout/notification_material_action_list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginLeft="-8dp"
- android:layout_marginRight="-8dp"
- android:layout_weight="0"
- />
- <ImageView
- android:layout_width="match_parent"
- android:layout_height="1dip"
- android:id="@+id/overflow_divider"
- android:visibility="visible"
- android:background="@drawable/notification_template_divider" />
- <include
- layout="@layout/notification_template_part_line3"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:layout_marginTop="8dp"
- android:layout_marginBottom="10dp" />
+ <include layout="@layout/notification_material_action_list" />
</LinearLayout>
+ <include layout="@layout/notification_template_right_icon" />
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index f6c22c87d622..dc4afb8c9481 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -15,40 +15,52 @@
~ limitations under the License
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout
android:id="@+id/status_bar_latest_event_content"
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="64dp"
- android:orientation="horizontal"
- android:background="#00000000"
+ android:layout_height="wrap_content"
android:tag="media"
>
- <include layout="@layout/notification_template_icon_group"
- android:layout_width="@dimen/notification_large_icon_width"
- android:layout_height="@dimen/notification_large_icon_height"
- android:layout_weight="0"
- />
+ <include layout="@layout/notification_template_header"
+ android:layout_width="fill_parent"
+ android:layout_height="48dp"
+ android:layout_marginEnd="106dp"/>
<LinearLayout
+ android:id="@+id/notification_main_column"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="fill_vertical"
- android:minHeight="@dimen/notification_large_icon_height"
- android:orientation="vertical"
- >
- <include layout="@layout/notification_template_part_line1" />
- <include layout="@layout/notification_template_part_line2" />
- <include layout="@layout/notification_template_part_line3" />
- </LinearLayout>
- <LinearLayout
- android:id="@+id/media_actions"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginEnd="6dp"
- android:layout_gravity="center_vertical|end"
+ android:layout_height="@dimen/notification_min_content_height"
+ android:background="#00000000"
android:orientation="horizontal"
- android:layoutDirection="ltr"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:layout_marginTop="@dimen/notification_content_margin_top"
+ android:layout_marginEnd="72dp"
+ android:tag="media"
>
- <!-- media buttons will be added here -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="fill_vertical"
+ android:layout_weight="1"
+ android:minHeight="@dimen/notification_min_content_height"
+ android:orientation="vertical"
+ >
+ <include layout="@layout/notification_template_part_line1"/>
+ <include layout="@layout/notification_template_progress"/>
+ <include layout="@layout/notification_template_part_line3"/>
+ </LinearLayout>
+ <LinearLayout
+ android:id="@+id/media_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="bottom|end"
+ android:layout_marginStart="10dp"
+ android:layout_marginBottom="8dp"
+ android:layoutDirection="ltr"
+ android:orientation="horizontal"
+ >
+ <!-- media buttons will be added here -->
+ </LinearLayout>
</LinearLayout>
-</LinearLayout>
+ <include layout="@layout/notification_template_right_icon" />
+</FrameLayout>
diff --git a/core/res/res/layout/notification_template_part_chronometer.xml b/core/res/res/layout/notification_template_part_chronometer.xml
index 1f0430ebdd89..c5ffbeadc525 100644
--- a/core/res/res/layout/notification_template_part_chronometer.xml
+++ b/core/res/res/layout/notification_template_part_chronometer.xml
@@ -18,9 +18,7 @@
android:textAppearance="@style/TextAppearance.Material.Notification.Time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center"
+ android:layout_marginEnd="4dp"
android:layout_weight="0"
android:singleLine="true"
- android:gravity="center"
- android:paddingStart="8dp"
/>
diff --git a/core/res/res/layout/notification_template_part_line1.xml b/core/res/res/layout/notification_template_part_line1.xml
index 78bc1ed2f09c..e7ac408553d5 100644
--- a/core/res/res/layout/notification_template_part_line1.xml
+++ b/core/res/res/layout/notification_template_part_line1.xml
@@ -19,30 +19,25 @@
android:id="@+id/line1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
android:orientation="horizontal"
+ android:layout_marginBottom="1dp"
>
<TextView android:id="@+id/title"
android:textAppearance="@style/TextAppearance.Material.Notification.Title"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
- android:layout_weight="1"
- />
- <ViewStub android:id="@+id/time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="0"
- android:visibility="gone"
- android:layout="@layout/notification_template_part_time"
/>
- <ViewStub android:id="@+id/chronometer"
- android:layout_width="wrap_content"
+ <TextView android:id="@+id/text_line_1"
+ android:textAppearance="@style/TextAppearance.Material.Notification"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="0"
- android:visibility="gone"
- android:layout="@layout/notification_template_part_chronometer"
+ android:gravity="end|bottom"
+ android:layout_marginStart="16dp"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
/>
</LinearLayout>
diff --git a/core/res/res/layout/notification_template_part_line2.xml b/core/res/res/layout/notification_template_part_line2.xml
deleted file mode 100644
index db43271a5680..000000000000
--- a/core/res/res/layout/notification_template_part_line2.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2014 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:orientation="horizontal"
- android:gravity="center_vertical"
- >
- <TextView
- android:id="@+id/text2"
- android:textAppearance="@style/TextAppearance.Material.Notification.Line2"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginTop="-1dp"
- android:layout_marginBottom="-1dp"
- android:singleLine="true"
- android:fadingEdge="horizontal"
- android:ellipsize="marquee"
- android:visibility="gone"
- android:layout_weight="1"
- />
- <ImageView android:id="@+id/profile_badge_line2"
- android:layout_width="@dimen/notification_badge_size"
- android:layout_height="@dimen/notification_badge_size"
- android:layout_weight="0"
- android:layout_marginStart="4dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
- </LinearLayout>
- <ViewStub
- android:id="@android:id/progress"
- android:layout="@layout/notification_template_progressbar"
- android:layout_width="match_parent"
- android:layout_height="15dp"
- android:layout_marginEnd="8dp"
- android:visibility="gone"
- android:layout_weight="0"
- />
-</merge>
diff --git a/core/res/res/layout/notification_template_part_line3.xml b/core/res/res/layout/notification_template_part_line3.xml
index da3c5c52a804..76337ac1156d 100644
--- a/core/res/res/layout/notification_template_part_line3.xml
+++ b/core/res/res/layout/notification_template_part_line3.xml
@@ -19,7 +19,6 @@
android:id="@+id/line3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
android:orientation="horizontal"
android:gravity="center_vertical"
>
@@ -33,16 +32,6 @@
android:ellipsize="marquee"
android:fadingEdge="horizontal"
/>
- <TextView android:id="@+id/info"
- android:textAppearance="@style/TextAppearance.Material.Notification.Info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_weight="0"
- android:singleLine="true"
- android:gravity="center"
- android:paddingStart="8dp"
- />
<ImageView android:id="@+id/profile_badge_line3"
android:layout_width="@dimen/notification_badge_size"
android:layout_height="@dimen/notification_badge_size"
diff --git a/core/res/res/layout/notification_template_part_time.xml b/core/res/res/layout/notification_template_part_time.xml
index 37c7ebe366e5..442ff8cef443 100644
--- a/core/res/res/layout/notification_template_part_time.xml
+++ b/core/res/res/layout/notification_template_part_time.xml
@@ -19,8 +19,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
+ android:layout_marginEnd="4dp"
android:layout_weight="0"
android:singleLine="true"
- android:gravity="center"
- android:paddingStart="8dp"
/>
diff --git a/core/res/res/drawable/notification_icon_legacy_bg.xml b/core/res/res/layout/notification_template_progress.xml
index cc5755d5618c..85532add7861 100644
--- a/core/res/res/drawable/notification_icon_legacy_bg.xml
+++ b/core/res/res/layout/notification_template_progress.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2014 The Android Open Source Project
+ ~ 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.
@@ -14,9 +14,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval">
- <solid
- android:color="@color/notification_icon_bg_color"/>
-</shape>
+<ViewStub
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/progress"
+ android:layout="@layout/notification_template_progressbar"
+ android:layout_width="match_parent"
+ android:layout_height="15dp"
+ android:visibility="gone"
+ />
diff --git a/core/res/res/layout/notification_template_right_icon.xml b/core/res/res/layout/notification_template_right_icon.xml
new file mode 100644
index 000000000000..3b358ab01a96
--- /dev/null
+++ b/core/res/res/layout/notification_template_right_icon.xml
@@ -0,0 +1,27 @@
+<?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
+ -->
+
+<ImageView android:id="@+id/right_icon" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginTop="32dp"
+ android:layout_gravity="top|end"
+ android:scaleType="centerCrop"
+ />
+
+
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 7f8c460bda4c..dad68ba030e1 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -130,12 +130,9 @@
<drawable name="notification_template_divider">#29000000</drawable>
<drawable name="notification_template_divider_media">#29ffffff</drawable>
- <color name="notification_icon_bg_color">#ff9e9e9e</color>
+ <color name="notification_icon_default_color">#ff616161</color>
<color name="notification_action_color_filter">@color/secondary_text_material_light</color>
- <color name="notification_media_primary_color">@color/primary_text_material_dark</color>
- <color name="notification_media_secondary_color">@color/secondary_text_material_dark</color>
-
<color name="notification_progress_background_color">@color/secondary_text_material_light</color>
<!-- Keyguard colors -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 01daf268a0eb..7b4beccae0fc 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -130,11 +130,38 @@
<!-- Default padding for dialogs. -->
<dimen name="dialog_padding">16dp</dimen>
+ <!-- The margin on the start of the content view -->
+ <dimen name="notification_content_margin_start">16dp</dimen>
+
+ <!-- The margin on the end of the content view -->
+ <dimen name="notification_content_margin_end">16dp</dimen>
+
+ <!-- The margin on the end of the content view with a picture.-->
+ <dimen name="notification_content_picture_margin">56dp</dimen>
+
+ <!-- height of the content margin to accomodate for the header -->
+ <dimen name="notification_content_margin_top">30dp</dimen>
+
+ <!-- height of notification header view if present -->
+ <dimen name="notification_header_height">32dp</dimen>
+
+ <!-- Height of a small notification in the status bar -->
+ <dimen name="notification_min_height">84dp</dimen>
+
<!-- The width of the big icons in notifications. -->
<dimen name="notification_large_icon_width">64dp</dimen>
<!-- The width of the big icons in notifications. -->
<dimen name="notification_large_icon_height">64dp</dimen>
+ <!-- Min height of the notification content. -->
+ <dimen name="notification_min_content_height">54dp</dimen>
+
+ <!-- The minimum width of the app name in the header if it shrinks -->
+ <dimen name="notification_header_shrink_min_width">72dp</dimen>
+
+ <!-- The minimum height of the content if there is a picture present with big picture -->
+ <dimen name="notification_big_picture_content_min_height_with_picture">41dp</dimen>
+
<!-- Minimum width of the search view text entry area. -->
<dimen name="search_view_text_min_width">160dip</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8a002946b754..d6dd8423cc5a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -531,6 +531,12 @@
[CHAR LIMIT=4] -->
<string name="status_bar_notification_info_overflow">999+</string>
+ <!-- The number of notifications in the notification header. An example would be (2) or (12) -->
+ <string name="notification_children_count_bracketed">(<xliff:g id="notificationCount" example="1">%d</xliff:g>)</string>
+
+ <!-- The divider symbol between different parts of the notification header. not translatable [CHAR LIMIT=1] -->
+ <string name="notification_header_divider_symbol" translatable="false">•</string>
+
<!-- Displayed to the user to tell them that they have started up the phone in "safe mode" -->
<string name="safeMode">Safe mode</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8cf11340453f..9dbdaaa2941a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -197,11 +197,8 @@
<java-symbol type="id" name="inbox_text4" />
<java-symbol type="id" name="inbox_text5" />
<java-symbol type="id" name="inbox_text6" />
- <java-symbol type="id" name="inbox_more" />
- <java-symbol type="id" name="inbox_end_pad" />
<java-symbol type="id" name="status_bar_latest_event_content" />
<java-symbol type="id" name="action_divider" />
- <java-symbol type="id" name="overflow_divider" />
<java-symbol type="id" name="notification_main_column" />
<java-symbol type="id" name="sms_short_code_confirm_message" />
<java-symbol type="id" name="sms_short_code_detail_layout" />
@@ -218,7 +215,6 @@
<java-symbol type="id" name="pin_error_message" />
<java-symbol type="id" name="timePickerLayout" />
<java-symbol type="id" name="profile_badge_large_template" />
- <java-symbol type="id" name="profile_badge_line2" />
<java-symbol type="id" name="profile_badge_line3" />
<java-symbol type="id" name="transitionPosition" />
<java-symbol type="id" name="selection_start_handle" />
@@ -1877,15 +1873,11 @@
<java-symbol type="layout" name="notification_template_material_inbox" />
<java-symbol type="layout" name="notification_template_material_media" />
<java-symbol type="layout" name="notification_template_material_big_media" />
- <java-symbol type="layout" name="notification_template_material_big_media_narrow" />
<java-symbol type="layout" name="notification_template_material_big_text" />
- <java-symbol type="layout" name="notification_template_icon_group" />
+ <java-symbol type="layout" name="notification_template_header" />
<java-symbol type="layout" name="notification_material_media_action" />
<java-symbol type="color" name="notification_action_color_filter" />
- <java-symbol type="color" name="notification_icon_bg_color" />
- <java-symbol type="drawable" name="notification_icon_legacy_bg" />
- <java-symbol type="color" name="notification_media_primary_color" />
- <java-symbol type="color" name="notification_media_secondary_color" />
+ <java-symbol type="color" name="notification_icon_default_color" />
<java-symbol type="color" name="notification_progress_background_color" />
<java-symbol type="id" name="media_actions" />
@@ -2352,4 +2344,24 @@
<java-symbol type="id" name="suggestionContainer" />
<java-symbol type="id" name="addToDictionaryButton" />
<java-symbol type="id" name="deleteButton" />
+
+ <java-symbol type="string" name="notification_children_count_bracketed" />
+ <java-symbol type="id" name="app_name_text" />
+ <java-symbol type="id" name="number_of_children" />
+ <java-symbol type="id" name="header_sub_text" />
+ <java-symbol type="id" name="expand_button" />
+ <java-symbol type="id" name="notification_header" />
+ <java-symbol type="id" name="header_content_info" />
+ <java-symbol type="id" name="time_divider" />
+ <java-symbol type="id" name="sub_text_divider" />
+ <java-symbol type="id" name="content_info_divider" />
+ <java-symbol type="id" name="text_line_1" />
+ <java-symbol type="drawable" name="ic_arrow_up_14dp" />
+ <java-symbol type="dimen" name="notification_header_height" />
+ <java-symbol type="dimen" name="notification_big_picture_content_min_height_with_picture" />
+ <java-symbol type="dimen" name="notification_header_shrink_min_width" />
+ <java-symbol type="dimen" name="notification_content_margin_start" />
+ <java-symbol type="dimen" name="notification_content_margin_end" />
+ <java-symbol type="dimen" name="notification_content_picture_margin" />
+ <java-symbol type="dimen" name="notification_content_margin_top" />
</resources>
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 78a0a59c862a..461450595b77 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -34,12 +34,15 @@ import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import com.android.frameworks.coretests.R;
+import android.support.test.espresso.NoMatchingRootException;
+import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.ViewInteraction;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
@@ -165,18 +168,21 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV
assertFloatingToolbarIsDisplayed(getActivity());
}
- private static ViewInteraction onHandleView(int id) {
- return onView(allOf(withId(id), isAssignableFrom(Editor.HandleView.class)))
- .inRoot(withDecorView(hasDescendant(withId(id))));
- }
-
@SmallTest
public void testSelectionHandles() throws Exception {
final String text = "abcd efg hijk lmn";
onView(withId(R.id.textview)).perform(click());
onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+ assertNoSelectionHandles();
+
onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('f')));
+ onHandleView(com.android.internal.R.id.selection_start_handle)
+ .check(matches(isDisplayed()));
+ onHandleView(com.android.internal.R.id.selection_end_handle)
+ .check(matches(isDisplayed()));
+
final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
onHandleView(com.android.internal.R.id.selection_start_handle)
.perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
@@ -336,4 +342,25 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV
.perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('i')));
onView(withId(R.id.textview)).check(hasSelection("hijk"));
}
+
+ private static void assertNoSelectionHandles() {
+ try {
+ onHandleView(com.android.internal.R.id.selection_start_handle)
+ .check(matches(isDisplayed()));
+ } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) {
+ try {
+ onHandleView(com.android.internal.R.id.selection_end_handle)
+ .check(matches(isDisplayed()));
+ } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e1) {
+ return;
+ }
+ }
+ throw new AssertionError("Selection handle found");
+ }
+
+ private static ViewInteraction onHandleView(int id)
+ throws NoMatchingRootException, NoMatchingViewException, AssertionError {
+ return onView(allOf(withId(id), isAssignableFrom(Editor.HandleView.class)))
+ .inRoot(withDecorView(hasDescendant(withId(id))));
+ }
}
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index f9474ef35dd8..d313aa5b5f65 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -139,6 +139,7 @@ class RippleBackground extends RippleComponent {
exit.setInterpolator(LINEAR_INTERPOLATOR);
exit.setDuration(OPACITY_EXIT_DURATION);
exit.setStartDelay(fastEnterDuration);
+ exit.setStartValue(targetAlpha);
set.add(exit);
// Linear "fast" enter based on current opacity.
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
index 554777160792..ed6ee7eadd08 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
@@ -45,10 +45,20 @@ public class MtpManagerTest extends InstrumentationTestCase {
@Override
public void setUp() throws Exception {
mUsbManager = getContext().getSystemService(UsbManager.class);
- mUsbDevice = findDevice();
- mManager = new MtpManager(getContext());
- mManager.openDevice(mUsbDevice.getDeviceId());
- waitForStorages(mManager, mUsbDevice.getDeviceId());
+ for (int i = 0; i < 2; i++) {
+ mUsbDevice = findDevice();
+ mManager = new MtpManager(getContext());
+ mManager.openDevice(mUsbDevice.getDeviceId());
+ try {
+ waitForStorages(mManager, mUsbDevice.getDeviceId());
+ return;
+ } catch (IOException exp) {
+ // When the MTP device is Android, and it changes the USB device type from
+ // "Charging" to "MTP", the device ID will be updated. We need to find a device
+ // again.
+ continue;
+ }
+ }
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index e28b2e5e530a..2b1c66d99994 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -19,8 +19,12 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkRequest;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
@@ -62,6 +66,9 @@ public class WifiTracker {
private final Context mContext;
private final WifiManager mWifiManager;
private final IntentFilter mFilter;
+ private final ConnectivityManager mConnectivityManager;
+ private final NetworkRequest mNetworkRequest;
+ private WifiTrackerNetworkCallback mNetworkCallback;
private final AtomicBoolean mConnected = new AtomicBoolean(false);
private final WifiListener mListener;
@@ -104,13 +111,15 @@ public class WifiTracker {
public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
boolean includeSaved, boolean includeScans, boolean includePasspoints) {
this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
- (WifiManager) context.getSystemService(Context.WIFI_SERVICE), Looper.myLooper());
+ context.getSystemService(WifiManager.class),
+ context.getSystemService(ConnectivityManager.class), Looper.myLooper());
}
@VisibleForTesting
WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
boolean includeSaved, boolean includeScans, boolean includePasspoints,
- WifiManager wifiManager, Looper currentLooper) {
+ WifiManager wifiManager, ConnectivityManager connectivityManager,
+ Looper currentLooper) {
if (!includeSaved && !includeScans) {
throw new IllegalArgumentException("Must include either saved or scans");
}
@@ -127,6 +136,7 @@ public class WifiTracker {
mIncludeScans = includeScans;
mIncludePasspoints = includePasspoints;
mListener = wifiListener;
+ mConnectivityManager = connectivityManager;
// check if verbose logging has been turned on or off
sVerboseLogging = mWifiManager.getVerboseLoggingLevel();
@@ -139,7 +149,11 @@ public class WifiTracker {
mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
- mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+
+ mNetworkRequest = new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
}
/**
@@ -192,6 +206,9 @@ public class WifiTracker {
resumeScanning();
if (!mRegistered) {
mContext.registerReceiver(mReceiver, mFilter);
+ // NetworkCallback objects cannot be reused. http://b/20701525 .
+ mNetworkCallback = new WifiTrackerNetworkCallback();
+ mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
mRegistered = true;
}
}
@@ -207,6 +224,7 @@ public class WifiTracker {
mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_NETWORK_INFO);
mContext.unregisterReceiver(mReceiver);
+ mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
mRegistered = false;
}
pauseScanning();
@@ -461,12 +479,12 @@ public class WifiTracker {
mMainHandler.sendEmptyMessage(MainHandler.MSG_RESUME_SCANNING);
}
- mLastInfo = mWifiManager.getConnectionInfo();
if (networkInfo != null) {
mLastNetworkInfo = networkInfo;
}
WifiConfiguration connectionConfig = null;
+ mLastInfo = mWifiManager.getConnectionInfo();
if (mLastInfo != null) {
connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
}
@@ -532,12 +550,21 @@ public class WifiTracker {
mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
.sendToTarget();
- } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
- mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
}
}
};
+ private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+ if (network.equals(mWifiManager.getCurrentNetwork())) {
+ // We don't send a NetworkInfo object along with this message, because even if we
+ // fetch one from ConnectivityManager, it might be older than the most recent
+ // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
+ mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
+ }
+ }
+ }
+
private final class MainHandler extends Handler {
private static final int MSG_CONNECTED_CHANGED = 0;
private static final int MSG_WIFI_STATE_CHANGED = 1;
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index bf3982d9a71d..25346acd1600 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -144,6 +144,7 @@
android:name=".BugreportReceiver"
android:permission="android.permission.DUMP">
<intent-filter>
+ <action android:name="android.intent.action.BUGREPORT_STARTED" />
<action android:name="android.intent.action.BUGREPORT_FINISHED" />
</intent-filter>
</receiver>
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 4469d387853c..cff36f73f74d 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -17,6 +17,8 @@
<resources>
<string name="app_label">Shell</string>
+ <!-- Title of notification indicating a bugreport process is in progress. [CHAR LIMIT=50] -->
+ <string name="bugreport_in_progress_title">Bug report in progress</string>
<!-- Title of notification indicating a bugreport has been successfully captured. [CHAR LIMIT=50] -->
<string name="bugreport_finished_title">Bug report captured</string>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index a2030ef68817..d0e91d22f87a 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -21,11 +21,16 @@ import static com.android.shell.BugreportPrefs.getWarningState;
import java.io.BufferedOutputStream;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintWriter;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Date;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -45,26 +50,102 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcelable;
+import android.os.Process;
import android.os.SystemProperties;
import android.support.v4.content.FileProvider;
+import android.text.format.DateUtils;
import android.util.Log;
import android.util.Patterns;
+import android.util.SparseArray;
import android.widget.Toast;
+/**
+ * Service used to keep progress of bug reports processes ({@code dumpstate}).
+ * <p>
+ * The workflow is:
+ * <ol>
+ * <li>When {@code dumpstate} starts, it sends a {@code BUGREPORT_STARTED} with its pid and the
+ * estimated total effort.
+ * <li>{@link BugreportReceiver} receives the intent and delegates it to this service.
+ * <li>Upon start, this service:
+ * <ol>
+ * <li>Issues a system notification so user can watch the progresss (which is 0% initially).
+ * <li>Polls the {@link SystemProperties} for updates on the {@code dumpstate} progress.
+ * <li>If the progress changed, it updates the system notification.
+ * </ol>
+ * <li>As {@code dumpstate} progresses, it updates the system property.
+ * <li>When {@code dumpstate} finishes, it sends a {@code BUGREPORT_FINISHED} intent.
+ * <li>{@link BugreportReceiver} receives the intent and delegates it to this service, which in
+ * turn:
+ * <ol>
+ * <li>Updates the system notification so user can share the bug report.
+ * <li>Stops monitoring that {@code dumpstate} process.
+ * <li>Stops itself if it doesn't have any process left to monitor.
+ * </ol>
+ * </ol>
+ */
public class BugreportProgressService extends Service {
private static final String TAG = "Shell";
+ private static final boolean DEBUG = false;
private static final String AUTHORITY = "com.android.shell";
+ 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 EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
+ static final String EXTRA_PID = "android.intent.extra.PID";
+ static final String EXTRA_MAX = "android.intent.extra.MAX";
+ static final String EXTRA_NAME = "android.intent.extra.NAME";
+ static final String EXTRA_ORIGINAL_INTENT = "android.intent.extra.ORIGINAL_INTENT";
+
+ private static final int MSG_SERVICE_COMMAND = 1;
+ private static final int MSG_POLL = 2;
+
+ /** Polling frequency, in milliseconds. */
+ private static final long POLLING_FREQUENCY = 500;
+
+ /** How long (in ms) a dumpstate process will be monitored if it didn't show progress. */
+ private static final long INACTIVITY_TIMEOUT = 3 * DateUtils.MINUTE_IN_MILLIS;
+
+ /** System property used for monitoring progress. */
+ private static final String PROGRESS_PROPERTY_TEMPLATE = "dumpstate.%d.progress";
+
+ /** Managed dumpstate processes (keyed by pid) */
+ private final SparseArray<BugreportInfo> mProcesses = new SparseArray<>();
+
+ private Looper mServiceLooper;
+ private ServiceHandler mServiceHandler;
+
+ @Override
+ public void onCreate() {
+ HandlerThread thread = new HandlerThread("BugreportProgressServiceThread",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+
+ mServiceLooper = thread.getLooper();
+ mServiceHandler = new ServiceHandler(mServiceLooper);
+ }
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
- onBugreportFinished(intent);
+ // Handle it in a separate thread.
+ Message msg = mServiceHandler.obtainMessage();
+ msg.what = MSG_SERVICE_COMMAND;
+ msg.obj = intent;
+ mServiceHandler.sendMessage(msg);
}
+
+ // If service is killed it cannot be recreated because it would not know which
+ // dumpstate PIDs it would have to watch.
return START_NOT_STICKY;
}
@@ -73,26 +154,249 @@ public class BugreportProgressService extends Service {
return null;
}
- private void onBugreportFinished(Intent intent) {
- final Context context = getApplicationContext();
- final Configuration conf = context.getResources().getConfiguration();
- final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
- final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+ @Override
+ public void onDestroy() {
+ mServiceLooper.quit();
+ super.onDestroy();
+ }
- if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
- triggerLocalNotification(context, bugreportFile, screenshotFile);
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ writer.printf("Monitored dumpstate processes: \n");
+ synchronized (mProcesses) {
+ for (int i = 0; i < mProcesses.size(); i++) {
+ writer.printf("\t%s\n", mProcesses.valueAt(i));
+ }
+ }
+ }
+
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ pollProgress();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_POLL) {
+ pollProgress();
+ return;
+ }
+
+ if (msg.what != MSG_SERVICE_COMMAND) {
+ // Sanity check.
+ Log.e(TAG, "Invalid message type: " + msg.what);
+ return;
+ }
+
+ // At this point it's handling onStartCommand(), whose intent contains the extras
+ // originally received by BugreportReceiver.
+ if (!(msg.obj instanceof Intent)) {
+ // Sanity check.
+ Log.e(TAG, "Internal error: invalid msg.obj: " + msg.obj);
+ return;
+ }
+ final Parcelable parcel = ((Intent) msg.obj).getParcelableExtra(EXTRA_ORIGINAL_INTENT);
+ if (!(parcel instanceof Intent)) {
+ // Sanity check.
+ Log.e(TAG, "Internal error: msg.obj is missing extra " + EXTRA_ORIGINAL_INTENT);
+ return;
+ }
+
+ final Intent intent = (Intent) parcel;
+ final String action = intent.getAction();
+ int pid = intent.getIntExtra(EXTRA_PID, 0);
+ int max = intent.getIntExtra(EXTRA_MAX, -1);
+ String name = intent.getStringExtra(EXTRA_NAME);
+
+ if (DEBUG) Log.v(TAG, "action: " + action + ", name: " + name + ", pid: " + pid
+ + ", max: "+ max);
+ switch (action) {
+ case INTENT_BUGREPORT_STARTED:
+ if (!startProgress(name, pid, max)) {
+ stopSelfWhenDone();
+ return;
+ }
+ break;
+ case INTENT_BUGREPORT_FINISHED:
+ if (pid == -1) {
+ // Shouldn't happen, unless BUGREPORT_FINISHED is received from a legacy,
+ // out-of-sync dumpstate process.
+ Log.w(TAG, "Missing " + EXTRA_PID + " on intent " + intent);
+ }
+ stopProgress(pid, intent);
+ break;
+ default:
+ Log.w(TAG, "Unsupported intent: " + action);
+ }
+ return;
+
+ }
+
+ /**
+ * Creates the {@link BugreportInfo} for a process and issue a system notification to
+ * indicate its progress.
+ *
+ * @return whether it succeeded or not.
+ */
+ private boolean startProgress(String name, int pid, int max) {
+ if (name == null) {
+ Log.w(TAG, "Missing " + EXTRA_NAME + " on start intent");
+ name = "N/A";
+ }
+ if (pid == -1) {
+ Log.e(TAG, "Missing " + EXTRA_PID + " on start intent");
+ return false;
+ }
+ if (max <= 0) {
+ Log.e(TAG, "Invalid value for extra " + EXTRA_MAX + ": " + max);
+ return false;
+ }
+
+ final BugreportInfo info = new BugreportInfo(pid, name, max);
+ synchronized (mProcesses) {
+ if (mProcesses.indexOfKey(pid) >= 0) {
+ Log.w(TAG, "PID " + pid + " already watched");
+ } else {
+ mProcesses.put(info.pid, info);
+ }
+ }
+ updateProgress(info);
+ return true;
+ }
+
+ /**
+ * Updates the system notification for a given bug report.
+ */
+ private void updateProgress(BugreportInfo info) {
+ if (info.max <= 0 || info.progress < 0 || info.name == null) {
+ Log.e(TAG, "Invalid progress values for " + info);
+ return;
+ }
+
+ final Context context = getApplicationContext();
+ final NumberFormat nf = NumberFormat.getPercentInstance();
+ nf.setMinimumFractionDigits(2);
+ nf.setMaximumFractionDigits(2);
+ final String percentText = nf.format((double) info.progress / info.max);
+
+ final String title = context.getString(R.string.bugreport_in_progress_title);
+ final Notification notification = new Notification.Builder(context)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setContentTitle(title)
+ .setTicker(title)
+ .setContentText(info.name)
+ .setContentInfo(percentText)
+ .setProgress(info.max, info.progress, false)
+ // TODO: .setOngoing(true) once it has a CANCEL action
+ .setLocalOnly(true)
+ .setColor(context.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .build();
+
+ NotificationManager.from(context).notify(TAG, info.pid, notification);
+ }
+
+ /**
+ * Finalizes the progress on a given process and sends the finished intent.
+ */
+ private void stopProgress(int pid, Intent intent) {
+ synchronized (mProcesses) {
+ if (mProcesses.indexOfKey(pid) < 0) {
+ Log.w(TAG, "PID not watched: " + pid);
+ } else {
+ mProcesses.remove(pid);
+ }
+ stopSelfWhenDone();
+ }
+ if (DEBUG) Log.v(TAG, "stopProgress(" + pid + "): cancel notification");
+ NotificationManager.from(getApplicationContext()).cancel(TAG, pid);
+ if (intent != null) {
+ // Bug report finished fine: send a new, different notification.
+ if (DEBUG) Log.v(TAG, "stopProgress(" + pid + "): finish bug report");
+ onBugreportFinished(pid, intent);
+ }
+ }
+
+ /**
+ * Poll {@link SystemProperties} to get the progress on each monitored process.
+ */
+ private void pollProgress() {
+ synchronized (mProcesses) {
+ if (mProcesses.size() == 0) {
+ Log.d(TAG, "No process to poll progress.");
+ }
+ for (int i = 0; i < mProcesses.size(); i++) {
+ int pid = mProcesses.keyAt(i);
+ String property = String.format(PROGRESS_PROPERTY_TEMPLATE, pid);
+ int progress;
+ try {
+ progress = SystemProperties.getInt(property, 0);
+ } catch (IllegalArgumentException e) {
+ Log.v(TAG, "Could not read system property " + property, e);
+ continue;
+ }
+ if (progress == 0) {
+ Log.v(TAG, "System property " + property + " is not set yet");
+ continue;
+ }
+
+ BugreportInfo info = mProcesses.valueAt(i);
+
+ if (progress != info.progress) {
+ if (DEBUG) Log.v(TAG, "Updating progress for PID " + pid + " from "
+ + info.progress + " to " + progress);
+ info.progress = progress;
+ info.lastUpdate = System.currentTimeMillis();
+ updateProgress(info);
+ } else {
+ long inactiveTime = System.currentTimeMillis() - info.lastUpdate;
+ if (inactiveTime >= INACTIVITY_TIMEOUT) {
+ Log.w(TAG, "No progress update for process " + pid + " since "
+ + info.getFormattedLastUpdate());
+ stopProgress(info.pid, null);
+ }
+ }
+ }
+ // Keep polling...
+ sendEmptyMessageDelayed(MSG_POLL, POLLING_FREQUENCY);
+ }
+ }
+
+ /**
+ * Finishes the service when it's not monitoring any more processes.
+ */
+ private void stopSelfWhenDone() {
+ synchronized (mProcesses) {
+ if (mProcesses.size() > 0) {
+ if (DEBUG) Log.v(TAG, "Staying alive, waiting for pids " + mProcesses);
+ return;
+ }
+ Log.v(TAG, "No more pids to handle, shutting down");
+ stopSelf();
+ }
+ }
+
+ private void onBugreportFinished(int pid, Intent intent) {
+ final Context context = getApplicationContext();
+ final Configuration conf = context.getResources().getConfiguration();
+ final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
+ final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+
+ if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
+ triggerLocalNotification(context, pid, bugreportFile, screenshotFile);
+ }
}
- stopSelf();
}
/**
- * Responsible for triggering a notification that allows the user to start a
- * "share" intent with the bug report. On watches we have other methods to allow the user to
- * start this intent (usually by triggering it on another connected device); we don't need to
- * display the notification in this case.
+ * Responsible for triggering a notification that allows the user to start a "share" intent with
+ * the bug report. On watches we have other methods to allow the user to start this intent
+ * (usually by triggering it on another connected device); we don't need to display the
+ * notification in this case.
*/
- private static void triggerLocalNotification(final Context context, final File bugreportFile,
- final File screenshotFile) {
+ private static void triggerLocalNotification(final Context context, final int pid,
+ final File bugreportFile, final File screenshotFile) {
if (!bugreportFile.exists() || !bugreportFile.canRead()) {
Log.e(TAG, "Could not read bugreport file " + bugreportFile);
Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
@@ -103,10 +407,10 @@ public class BugreportProgressService extends Service {
boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
if (!isPlainText) {
// Already zipped, send it right away.
- sendBugreportNotification(context, bugreportFile, screenshotFile);
+ sendBugreportNotification(context, pid, bugreportFile, screenshotFile);
} else {
// Asynchronously zip the file first, then send it.
- sendZippedBugreportNotification(context, bugreportFile, screenshotFile);
+ sendZippedBugreportNotification(context, pid, bugreportFile, screenshotFile);
}
}
@@ -155,7 +459,7 @@ public class BugreportProgressService extends Service {
/**
* Sends a bugreport notitication.
*/
- private static void sendBugreportNotification(Context context, File bugreportFile,
+ private static void sendBugreportNotification(Context context, int pid, File bugreportFile,
File screenshotFile) {
// Files are kept on private storage, so turn into Uris that we can
// grant temporary permissions for.
@@ -173,10 +477,11 @@ public class BugreportProgressService extends Service {
}
notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final String title = context.getString(R.string.bugreport_finished_title);
final Notification.Builder builder = new Notification.Builder(context)
.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
- .setContentTitle(context.getString(R.string.bugreport_finished_title))
- .setTicker(context.getString(R.string.bugreport_finished_title))
+ .setContentTitle(title)
+ .setTicker(title)
.setContentText(context.getString(R.string.bugreport_finished_text))
.setContentIntent(PendingIntent.getActivity(
context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT))
@@ -185,19 +490,19 @@ public class BugreportProgressService extends Service {
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color));
- NotificationManager.from(context).notify(TAG, 0, builder.build());
+ NotificationManager.from(context).notify(TAG, pid, builder.build());
}
/**
* Sends a zipped bugreport notification.
*/
private static void sendZippedBugreportNotification(final Context context,
- final File bugreportFile, final File screenshotFile) {
+ final int pid, final File bugreportFile, final File screenshotFile) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
File zippedFile = zipBugreport(bugreportFile);
- sendBugreportNotification(context, zippedFile, screenshotFile);
+ sendBugreportNotification(context, pid, zippedFile, screenshotFile);
return null;
}
}.execute();
@@ -213,8 +518,8 @@ public class BugreportProgressService extends Service {
Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
File bugreportZippedFile = new File(zippedPath);
try (InputStream is = new FileInputStream(bugreportFile);
- ZipOutputStream zos = new ZipOutputStream(
- new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
+ ZipOutputStream zos = new ZipOutputStream(
+ new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
ZipEntry entry = new ZipEntry(bugreportFile.getName());
entry.setTime(bugreportFile.lastModified());
zos.putNextEntry(entry);
@@ -230,8 +535,8 @@ public class BugreportProgressService extends Service {
}
return bugreportZippedFile;
} catch (IOException e) {
- Log.e(TAG, "exception zipping file " + zippedPath, e);
- return bugreportFile; // Return original.
+ Log.e(TAG, "exception zipping file " + zippedPath, e);
+ return bugreportFile; // Return original.
}
}
@@ -281,4 +586,55 @@ public class BugreportProgressService extends Service {
return null;
}
}
+
+ /**
+ * Information about a bug report process while its in progress.
+ */
+ private static final class BugreportInfo {
+ /**
+ * {@code pid} of the {@code dumpstate} process generating the bug report.
+ */
+ final int pid;
+
+ /**
+ * Name of the bug report, will be used to rename the final files.
+ * <p>
+ * Initial value is the bug report filename reported by {@code dumpstate}, but user can
+ * change it later to a more meaningful name.
+ */
+ final String name;
+
+ /**
+ * Maximum progress of the bug report generation.
+ */
+ final int max;
+
+ /**
+ * Current progress of the bug report generation.
+ */
+ int progress;
+
+ /**
+ * Time of the last progress update.
+ */
+ long lastUpdate = System.currentTimeMillis();
+
+ BugreportInfo(int pid, String name, int max) {
+ this.pid = pid;
+ this.name = name;
+ this.max = max;
+ }
+
+ String getFormattedLastUpdate() {
+ return SimpleDateFormat.getDateTimeInstance().format(new Date(lastUpdate));
+ }
+
+ @Override
+ public String toString() {
+ final float percent = ((float) progress * 100 / max);
+ return String.format("Progress for %s (pid=%d): %d/%d (%2.2f%%) Last update: %s", name,
+ pid, progress, max, percent,
+ getFormattedLastUpdate());
+ }
+ }
}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index f1da14d57c24..5133162a1ec9 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -17,6 +17,8 @@
package com.android.shell;
import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.EXTRA_ORIGINAL_INTENT;
+import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED;
import static com.android.shell.BugreportProgressService.getFileExtra;
import java.io.File;
@@ -50,13 +52,16 @@ public class BugreportReceiver extends BroadcastReceiver {
// Clean up older bugreports in background
cleanupOldFiles(intent);
- // Delegate to service.
+ // Delegate intent handling to service.
Intent serviceIntent = new Intent(context, BugreportProgressService.class);
- serviceIntent.putExtras(intent.getExtras());
+ serviceIntent.putExtra(EXTRA_ORIGINAL_INTENT, intent);
context.startService(serviceIntent);
}
private void cleanupOldFiles(Intent intent) {
+ if (!INTENT_BUGREPORT_FINISHED.equals(intent.getAction())) {
+ return;
+ }
final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
final PendingResult result = goAsync();
new AsyncTask<Void, Void, Void>() {
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 1bdd9ddc874d..33c4ef1ffbd5 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -19,7 +19,12 @@ package com.android.shell;
import static android.test.MoreAsserts.assertContainsRegex;
import static com.android.shell.ActionSendMultipleConsumerActivity.UI_NAME;
import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.EXTRA_MAX;
+import static com.android.shell.BugreportProgressService.EXTRA_NAME;
+import static com.android.shell.BugreportProgressService.EXTRA_PID;
import static com.android.shell.BugreportProgressService.EXTRA_SCREENSHOT;
+import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED;
+import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_STARTED;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
@@ -96,6 +101,33 @@ public class BugreportReceiverTest extends InstrumentationTestCase {
cancelExistingNotifications();
}
+ public void testFullWorkflow() throws Exception {
+ final String name = "BUG, Y U NO REPORT?";
+ // TODO: call method to remove property instead
+ SystemProperties.set("dumpstate.42.progress", "-1");
+
+ Intent intent = new Intent(INTENT_BUGREPORT_STARTED);
+ intent.putExtra(EXTRA_PID, 42);
+ intent.putExtra(EXTRA_NAME, name);
+ intent.putExtra(EXTRA_MAX, 1000);
+ mContext.sendBroadcast(intent);
+
+ assertProgressNotification(name, "0.00%");
+
+ SystemProperties.set("dumpstate.42.progress", "108");
+ assertProgressNotification(name, "10.80%");
+
+ SystemProperties.set("dumpstate.42.progress", "500");
+ assertProgressNotification(name, "50.00%");
+
+ createTextFile(PLAIN_TEXT_PATH, BUGREPORT_CONTENT);
+ createTextFile(SCREENSHOT_PATH, SCREENSHOT_CONTENT);
+ Bundle extras = sendBugreportFinishedIntent(42, PLAIN_TEXT_PATH, SCREENSHOT_PATH);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT);
+
+ // TODO: assert service is down
+ }
+
public void testBugreportFinished_plainBugreportAndScreenshot() throws Exception {
createTextFile(PLAIN_TEXT_PATH, BUGREPORT_CONTENT);
createTextFile(SCREENSHOT_PATH, SCREENSHOT_CONTENT);
@@ -131,13 +163,32 @@ public class BugreportReceiverTest extends InstrumentationTestCase {
}
}
+ private void assertProgressNotification(String name, String percent) {
+ // TODO: it current looks for 3 distinct objects, without taking advantage of their
+ // relationship.
+ String title = mContext.getString(R.string.bugreport_in_progress_title);
+ Log.v(TAG, "Looking for progress notification title: '" + title+ "'");
+ mUiBot.getNotification(title);
+ Log.v(TAG, "Looking for progress notification details: '" + name + "-" + percent + "'");
+ mUiBot.getObject(name);
+ mUiBot.getObject(percent);
+ }
+
/**
* Sends a "bugreport finished" intent and waits for the result.
*
* @return extras sent to the bugreport finished consumer.
*/
private Bundle sendBugreportFinishedIntent(String bugreportPath, String screenshotPath) {
- Intent intent = new Intent("android.intent.action.BUGREPORT_FINISHED");
+ return sendBugreportFinishedIntent(null, bugreportPath, screenshotPath);
+ }
+
+ private Bundle sendBugreportFinishedIntent(Integer pid, String bugreportPath,
+ String screenshotPath) {
+ Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
+ if (pid != null) {
+ intent.putExtra(EXTRA_PID, pid);
+ }
if (bugreportPath != null) {
intent.putExtra(EXTRA_BUGREPORT, bugreportPath);
}
diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java
index f5dd31c6fdc0..fa1714efcd41 100644
--- a/packages/Shell/tests/src/com/android/shell/UiBot.java
+++ b/packages/Shell/tests/src/com/android/shell/UiBot.java
@@ -42,32 +42,49 @@ final class UiBot {
}
/**
- * Opens the system notification and clicks a given notification.
+ * Opens the system notification and gets a given notification.
*
* @param text Notificaton's text as displayed by the UI.
+ * @return notification object.
*/
- public void clickOnNotification(String text) {
+ public UiObject getNotification(String text) {
boolean opened = mDevice.openNotification();
Log.v(TAG, "openNotification(): " + opened);
boolean gotIt = mDevice.wait(Until.hasObject(By.pkg(SYSTEMUI_PACKAGED)), mTimeout);
assertTrue("could not get system ui (" + SYSTEMUI_PACKAGED + ")", gotIt);
- gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
- assertTrue("object with text '(" + text + "') not visible yet", gotIt);
-
- UiObject notification = getVisibleObject(text);
+ return getObject(text);
+ }
+ /**
+ * Opens the system notification and clicks a given notification.
+ *
+ * @param text Notificaton's text as displayed by the UI.
+ */
+ public void clickOnNotification(String text) {
+ UiObject notification = getNotification(text);
click(notification, "bug report notification");
}
/**
- * Gets an object which is guaranteed to be present in the current UI.\
+ * Gets an object that might not yet be available in current UI.
+ *
+ * @param text Object's text as displayed by the UI.
+ */
+ public UiObject getObject(String text) {
+ boolean gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
+ assertTrue("object with text '(" + text + "') not visible yet", gotIt);
+ return getVisibleObject(text);
+ }
+
+ /**
+ * Gets an object which is guaranteed to be present in the current UI.
*
* @param text Object's text as displayed by the UI.
*/
public UiObject getVisibleObject(String text) {
UiObject uiObject = mDevice.findObject(new UiSelector().text(text));
- assertTrue("could not find object with text '(" + text + "')", uiObject.exists());
+ assertTrue("could not find object with text '" + text + "'", uiObject.exists());
return uiObject;
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6fda2c69b4ac..2f79adfcb8e8 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -87,6 +87,7 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
<uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+ <uses-permission android:name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS" />
<uses-permission android:name="android.permission.SET_ORIENTATION" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
diff --git a/packages/SystemUI/res/drawable/notification_expand_more.xml b/packages/SystemUI/res/drawable/notification_expand_more.xml
index 5aa7937f3964..430fb0dd523d 100644
--- a/packages/SystemUI/res/drawable/notification_expand_more.xml
+++ b/packages/SystemUI/res/drawable/notification_expand_more.xml
@@ -12,7 +12,7 @@ Copyright (C) 2015 The Android Open Source Project
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.
+ limitations under the License._more
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22.0dp"
diff --git a/packages/SystemUI/res/layout/notification_header.xml b/packages/SystemUI/res/layout/notification_header.xml
deleted file mode 100644
index 3475d00bc3a2..000000000000
--- a/packages/SystemUI/res/layout/notification_header.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2015 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<com.android.systemui.statusbar.notification.NotificationHeaderView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_header_height"
- android:clipChildren="false">
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/notification_header_height"
- android:layout_gravity="start"
- android:gravity="center_vertical"
- android:paddingStart="@dimen/notification_content_margin_start">
- <ImageView
- android:id="@+id/header_notification_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_marginEnd="4dp"
- />
- <TextView
- android:id="@+id/number_of_children"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@*android:style/TextAppearance.Material.Notification"
- android:layout_marginEnd="2dp"
- android:layout_marginStart="1dp"
- />
- <TextView
- android:id="@+id/app_name_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Material.Notification.HeaderTitle"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="8dp"
- />
- <TextView
- android:id="@+id/app_title_sub_text_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Material.Notification.HeaderTitle"
- android:layout_marginEnd="8dp"
- android:text="@string/notification_header_divider_symbol"/>
- <TextView
- android:id="@+id/title_sub_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Material.Notification.HeaderTitle"
- android:layout_marginEnd="8dp" />
- <TextView
- android:id="@+id/post_time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="end|center_vertical"
- android:textAppearance="@*android:style/TextAppearance.Material.Notification.Time"
- android:paddingEnd="8dp"
- />
- </LinearLayout>
- <ImageButton
- android:id="@+id/notification_expand_button"
- android:background="@null"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_gravity="end|center_vertical"
- android:src="@drawable/notification_expand_more"
- android:layout_marginEnd="8dp"
- />
-</com.android.systemui.statusbar.notification.NotificationHeaderView>
diff --git a/packages/SystemUI/res/layout/recents_history.xml b/packages/SystemUI/res/layout/recents_history.xml
index de70d303f199..b65a5c58f6c7 100644
--- a/packages/SystemUI/res/layout/recents_history.xml
+++ b/packages/SystemUI/res/layout/recents_history.xml
@@ -19,16 +19,6 @@
android:layout_height="match_parent"
android:background="#99000000"
android:orientation="vertical">
- <TextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="14dp"
- android:gravity="start"
- android:text="@string/recents_history_label"
- android:textSize="24sp"
- android:textColor="#FFFFFF"
- android:fontFamily="sans-serif-medium" />
<android.support.v7.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/recents_history_button.xml b/packages/SystemUI/res/layout/recents_history_button.xml
index 471f518037da..601c5ed23916 100644
--- a/packages/SystemUI/res/layout/recents_history_button.xml
+++ b/packages/SystemUI/res/layout/recents_history_button.xml
@@ -22,5 +22,9 @@
android:textSize="14sp"
android:textColor="#FFFFFF"
android:textAllCaps="true"
+ android:shadowColor="#99000000"
+ android:shadowDx="0"
+ android:shadowDy="2"
+ android:shadowRadius="5"
android:fontFamily="sans-serif-medium"
android:visibility="invisible" /> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index b9088ecb09cd..0cea7ae6d042 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -61,14 +61,6 @@
/>
<ViewStub
- android:layout="@layout/notification_header"
- android:id="@+id/notification_header_stub"
- android:inflatedId="@+id/notification_header"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_header_height"
- />
-
- <ViewStub
android:layout="@layout/notification_guts"
android:id="@+id/notification_guts_stub"
android:inflatedId="@+id/notification_guts"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 26a0577327e4..bfd8af9d2a59 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -109,7 +109,6 @@
<color name="assist_orb_color">#ffffff</color>
<color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
- <color name="doze_small_icon_background_color">#ff434343</color>
<!-- The color of the navigation bar icons. Need to be in sync with ic_sysbar_* -->
<color name="navigation_bar_icon_color">#E5FFFFFF</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c85ada8e900c..086e9f44a786 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -48,11 +48,14 @@
<!-- Height of a single line notification in the status bar -->
<dimen name="notification_single_line_height">32sp</dimen>
- <!-- Height of a small notification in the status bar -->
- <dimen name="notification_min_height">64dp</dimen>
+ <!-- Height of a small notification in the status bar-->
+ <dimen name="notification_min_height">84dp</dimen>
+
+ <!-- Height of a small notification in the status bar which was used before android N -->
+ <dimen name="notification_min_height_legacy">64dp</dimen>
<!-- Height of a large notification in the status bar -->
- <dimen name="notification_max_height">256dp</dimen>
+ <dimen name="notification_max_height">276dp</dimen>
<!-- Height of a medium notification in the status bar -->
<dimen name="notification_mid_height">128dp</dimen>
@@ -214,9 +217,6 @@
<!-- The amount to offset when animating into an affiliate group. -->
<dimen name="recents_task_view_affiliate_group_enter_offset">64dp</dimen>
- <!-- The alpha to apply to a task thumbnail. -->
- <item name="recents_task_view_thumbnail_alpha" format="float" type="dimen">0.9</item>
-
<!-- The height of a task view bar. -->
<dimen name="recents_task_bar_height">56dp</dimen>
@@ -261,7 +261,7 @@
<!-- bottom_stack_peek_amount + notification_min_height
+ notification_collapse_second_card_padding -->
- <dimen name="min_stack_height">84dp</dimen>
+ <dimen name="min_stack_height">104dp</dimen>
<!-- The height of the area before the bottom stack in which the notifications slow down -->
<dimen name="bottom_stack_slow_down_length">12dp</dimen>
@@ -279,7 +279,7 @@
<dimen name="notification_padding_dimmed">0dp</dimen>
<!-- The padding between the individual notification cards. -->
- <dimen name="notification_padding">4dp</dimen>
+ <dimen name="notification_padding">2dp</dimen>
<!-- The minimum amount of top overscroll to go to the quick settings. -->
<dimen name="min_top_overscroll_to_qs">36dp</dimen>
@@ -299,7 +299,7 @@
<!-- Falsing threshold used when dismissing notifications from the lockscreen. -->
<dimen name="swipe_helper_falsing_threshold">70dp</dimen>
- <dimen name="notifications_top_padding">8dp</dimen>
+ <dimen name="notifications_top_padding">4dp</dimen>
<!-- Minimum distance the user has to drag down to go to the full shade. -->
<dimen name="keyguard_drag_down_min_distance">100dp</dimen>
@@ -351,9 +351,6 @@
<!-- radius of the corners of the material rounded rect background but negative-->
<dimen name="notification_material_rounded_rect_radius_negative">-2dp</dimen>
- <!-- height of notification header view if present -->
- <dimen name="notification_header_height">32dp</dimen>
-
<!-- The padding between notification children -->
<dimen name="notification_children_padding">2dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 04233ba064bd..d6a361c2be71 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -753,9 +753,6 @@
<!-- Text for overflow card on Keyguard when there is not enough space for all notifications on Keyguard. [CHAR LIMIT=1] -->
<string name="keyguard_more_overflow_text">+<xliff:g id="number_of_notifications" example="5">%d</xliff:g></string>
- <!-- The divider symbol between different parts of the notification header. not translatable [CHAR LIMIT=1] -->
- <string name="notification_header_divider_symbol" translatable="false">•</string>
-
<!-- An explanation for the visual speed bump in the notifications, which will appear when you click on it. [CHAR LIMIT=50] -->
<string name="speed_bump_explanation">Less urgent notifications below</string>
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index d9f7a46bb3c7..051921aaf164 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -509,7 +509,7 @@ public class ExpandHelper implements Gefingerpoken {
if (canBeExpanded) {
if (DEBUG) Log.d(TAG, "working on an expandable child");
mNaturalHeight = mScaler.getNaturalHeight();
- mSmallSize = v.getMinHeight();
+ mSmallSize = v.getMinExpandHeight();
} else {
if (DEBUG) Log.d(TAG, "working on a non-expandable child");
mNaturalHeight = mOldHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
index eddf2b1cfa98..5b8d3d66e332 100644
--- a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
@@ -26,6 +26,8 @@ import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+import java.util.ArrayList;
+
/**
* Helper to invert the colors of views and fade between the states.
*/
@@ -33,14 +35,24 @@ public class ViewInvertHelper {
private final Paint mDarkPaint = new Paint();
private final Interpolator mLinearOutSlowInInterpolator;
- private final View mTarget;
+ private final ArrayList<View> mTargets;
private final ColorMatrix mMatrix = new ColorMatrix();
private final ColorMatrix mGrayscaleMatrix = new ColorMatrix();
private final long mFadeDuration;
public ViewInvertHelper(View target, long fadeDuration) {
- mTarget = target;
- mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(mTarget.getContext(),
+ this(constructArray(target), fadeDuration);
+ }
+
+ private static ArrayList<View> constructArray(View target) {
+ final ArrayList<View> views = new ArrayList<>();
+ views.add(target);
+ return views;
+ }
+
+ public ViewInvertHelper(ArrayList<View> targets, long fadeDuration) {
+ mTargets = targets;
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(mTargets.get(0).getContext(),
android.R.interpolator.linear_out_slow_in);
mFadeDuration = fadeDuration;
}
@@ -53,14 +65,18 @@ public class ViewInvertHelper {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updateInvertPaint((Float) animation.getAnimatedValue());
- mTarget.setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+ for (int i = 0; i < mTargets.size(); i++) {
+ mTargets.get(i).setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+ }
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (!invert) {
- mTarget.setLayerType(View.LAYER_TYPE_NONE, null);
+ for (int i = 0; i < mTargets.size(); i++) {
+ mTargets.get(i).setLayerType(View.LAYER_TYPE_NONE, null);
+ }
}
}
});
@@ -73,16 +89,16 @@ public class ViewInvertHelper {
public void update(boolean invert) {
if (invert) {
updateInvertPaint(1f);
- mTarget.setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+ for (int i = 0; i < mTargets.size(); i++) {
+ mTargets.get(i).setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+ }
} else {
- mTarget.setLayerType(View.LAYER_TYPE_NONE, null);
+ for (int i = 0; i < mTargets.size(); i++) {
+ mTargets.get(i).setLayerType(View.LAYER_TYPE_NONE, null);
+ }
}
}
- public View getTarget() {
- return mTarget;
- }
-
private void updateInvertPaint(float intensity) {
float components = 1 - 2 * intensity;
final float[] invert = {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index a12a3f1872af..19b65f77da30 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -36,7 +36,6 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
-import android.view.View;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -162,9 +161,6 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
.setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color));
final Notification n = nb.build();
- if (n.headsUpContentView != null) {
- n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE);
- }
mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, n, UserHandle.ALL);
}
@@ -200,9 +196,6 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
mPlaySound = false;
}
final Notification n = nb.build();
- if (n.headsUpContentView != null) {
- n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE);
- }
mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, n, UserHandle.ALL);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 94c45a423139..a0c481a17375 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -724,18 +724,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
getResizeTaskDebugDialog().showResizeTaskDialog(event.task, mRecentsView);
}
- public final void onBusEvent(DragStartEvent event) {
- // Lock the orientation while dragging
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
-
- // TODO: docking requires custom accessibility actions
- }
-
- public final void onBusEvent(DragEndEvent event) {
- // Unlock the orientation when dragging completes
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_BEHIND);
- }
-
public final void onBusEvent(LaunchTaskSucceededEvent event) {
MetricsLogger.histogram(this, "overview_task_launch_index", event.taskIndexFromStackFront);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 8dd9e47c3d86..43db6660fab3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -82,7 +82,11 @@ public class RecentsActivityLaunchState {
if (launchedFromHome) {
return numTasks - 1;
} else {
- return numTasks - 2;
+ if (flags.isFastToggleRecentsEnabled()) {
+ return numTasks - 1;
+ } else {
+ return numTasks - 2;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 440ed6b15ac6..cdfad18311ad 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -69,7 +69,6 @@ public class RecentsConfiguration {
public final int smallestWidth;
/** Misc **/
- public boolean hasDockedTasks;
public boolean useHardwareLayers;
public boolean fakeShadows;
public int svelteLevel;
@@ -112,7 +111,6 @@ public class RecentsConfiguration {
// settings or via multi window
lockToAppEnabled = !ssp.hasFreeformWorkspaceSupport() &&
ssp.getSystemSetting(context, Settings.System.LOCK_TO_APP_ENABLED) != 0;
- hasDockedTasks = ssp.hasDockedTask();
// Recompute some values based on the given state, since we can not rely on the resource
// system to get certain values.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
index 957da9441ec6..3deeb47f0eb6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
@@ -19,7 +19,6 @@ package com.android.systemui.recents.events.ui.dragndrop;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.DragView;
import com.android.systemui.recents.views.DropTarget;
import com.android.systemui.recents.views.TaskView;
@@ -30,15 +29,13 @@ public class DragEndEvent extends EventBus.Event {
public final Task task;
public final TaskView taskView;
- public final DragView dragView;
public final DropTarget dropTarget;
public final ReferenceCountedTrigger postAnimationTrigger;
- public DragEndEvent(Task task, TaskView taskView, DragView dragView, DropTarget dropTarget,
+ public DragEndEvent(Task task, TaskView taskView, DropTarget dropTarget,
ReferenceCountedTrigger postAnimationTrigger) {
this.task = task;
this.taskView = taskView;
- this.dragView = dragView;
this.dropTarget = dropTarget;
this.postAnimationTrigger = postAnimationTrigger;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
index 2d42a0e0103d..b81c10c0d0a3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
@@ -16,9 +16,9 @@
package com.android.systemui.recents.events.ui.dragndrop;
+import android.graphics.Point;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.DragView;
import com.android.systemui.recents.views.TaskView;
/**
@@ -28,11 +28,11 @@ public class DragStartEvent extends EventBus.Event {
public final Task task;
public final TaskView taskView;
- public final DragView dragView;
+ public final Point tlOffset;
- public DragStartEvent(Task task, TaskView taskView, DragView dragView) {
+ public DragStartEvent(Task task, TaskView taskView, Point tlOffset) {
this.task = task;
this.taskView = taskView;
- this.dragView = dragView;
+ this.tlOffset = tlOffset;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
index f48883fa175e..9d3a99fc9e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
@@ -29,6 +29,8 @@ import android.view.animation.Interpolator;
import android.widget.LinearLayout;
import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.model.TaskStack;
@@ -43,6 +45,7 @@ public class RecentsHistoryView extends LinearLayout {
private RecyclerView mRecyclerView;
private RecentsHistoryAdapter mAdapter;
private boolean mIsVisible;
+ private Rect mSystemInsets = new Rect();
private Interpolator mFastOutSlowInInterpolator;
private Interpolator mFastOutLinearInInterpolator;
@@ -123,7 +126,8 @@ public class RecentsHistoryView extends LinearLayout {
* Updates the system insets of this history view to the provided values.
*/
public void setSystemInsets(Rect systemInsets) {
- setPadding(systemInsets.left, systemInsets.top, systemInsets.right, systemInsets.bottom);
+ mSystemInsets.set(systemInsets.left, systemInsets.top, systemInsets.right, systemInsets.bottom);
+ requestLayout();
}
/**
@@ -142,6 +146,26 @@ public class RecentsHistoryView extends LinearLayout {
}
@Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ RecentsConfiguration config = Recents.getConfiguration();
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ // Pad the view to align the history with the stack layout
+ Rect taskStackBounds = new Rect();
+ config.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top,
+ mSystemInsets.right, new Rect() /* searchBarSpaceBounds */, taskStackBounds);
+ int stackWidthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
+ int stackHeightPadding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.recents_stack_top_padding);
+ mRecyclerView.setPadding(stackWidthPadding + mSystemInsets.left,
+ stackHeightPadding + mSystemInsets.top,
+ stackWidthPadding + mSystemInsets.right, mSystemInsets.bottom);
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
setSystemInsets(insets.getSystemWindowInsets());
return insets;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index de40a37bf8a1..2b20c07d8be0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -56,6 +56,7 @@ import android.util.Log;
import android.util.MutableBoolean;
import android.util.Pair;
import android.view.Display;
+import android.view.IDockDividerVisibilityListener;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
@@ -409,7 +410,7 @@ public class SystemServicesProxy {
return thumbnail;
}
- Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId);
+ Bitmap thumbnail = getThumbnail(taskId);
if (thumbnail != null) {
thumbnail.setHasAlpha(false);
// We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
@@ -429,8 +430,12 @@ public class SystemServicesProxy {
/**
* Returns a task thumbnail from the activity manager
*/
- public static Bitmap getThumbnail(ActivityManager activityManager, int taskId) {
- ActivityManager.TaskThumbnail taskThumbnail = activityManager.getTaskThumbnail(taskId);
+ public Bitmap getThumbnail(int taskId) {
+ if (mAm == null) {
+ return null;
+ }
+
+ ActivityManager.TaskThumbnail taskThumbnail = mAm.getTaskThumbnail(taskId);
if (taskThumbnail == null) return null;
Bitmap thumbnail = taskThumbnail.mainThumbnail;
@@ -847,4 +852,15 @@ public class SystemServicesProxy {
e.printStackTrace();
}
}
+
+ public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+ if (mWm == null) return;
+
+ try {
+ WindowManagerGlobal.getWindowManagerService().registerDockDividerVisibilityListener(
+ listener);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 5921d1393e0f..3bb89a3e8274 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -128,7 +128,7 @@ public class RecentsTaskLoadPlan {
boolean isStackTask = true;
if (debugFlags.isHistoryEnabled()) {
boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
- isStackTask = !isFreeformTask && (!isHistoricalTask(t) ||
+ isStackTask = isFreeformTask || (!isHistoricalTask(t) ||
(t.lastActiveTime >= lastStackActiveTime &&
i >= (taskCount - MIN_NUM_TASKS)));
if (isStackTask && newLastStackActiveTime < 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
index 945fdc10de66..271a2a09d8e5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -16,7 +16,6 @@
package com.android.systemui.recents.views;
-import android.animation.ObjectAnimator;
import android.graphics.Outline;
import android.graphics.Rect;
import android.util.IntProperty;
@@ -65,6 +64,15 @@ public class AnimateableViewBounds extends ViewOutlineProvider {
mCornerRadius = cornerRadius;
}
+ /**
+ * Resets the right and bottom clip for this view.
+ */
+ public void reset() {
+ mClipRect.setEmpty();
+ mSourceView.invalidateOutline();
+ updateClipBounds();
+ }
+
@Override
public void getOutline(View view, Outline outline) {
outline.setAlpha(mMinAlpha + mAlpha / (1f - mMinAlpha));
@@ -82,15 +90,6 @@ public class AnimateableViewBounds extends ViewOutlineProvider {
}
}
- /**
- * Animates the bottom clip.
- */
- public void animateClipBottom(int bottom) {
- ObjectAnimator animator = ObjectAnimator.ofInt(this, CLIP_BOTTOM, getClipBottom(), bottom);
- animator.setDuration(150);
- animator.start();
- }
-
/** Sets the bottom clip. */
public void setClipBottom(int bottom, boolean force) {
if (bottom != mClipRect.bottom || force) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java b/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java
deleted file mode 100644
index 96dfaac360c3..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.widget.ImageView;
-
-public class DragView extends ImageView {
-
- // The offset from the top-left of the dragBitmap
- Point mTopLeftOffset;
-
- public DragView(Context context, Bitmap dragBitmap, Point tlOffset) {
- super(context);
-
- mTopLeftOffset = tlOffset;
- setImageBitmap(dragBitmap);
- }
-
- public Point getTopLeftOffset() {
- return mTopLeftOffset;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
index 90d62c1d4f68..a70b66d5a437 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -16,6 +16,7 @@
package com.android.systemui.recents.views;
+import android.graphics.Rect;
import android.util.Log;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
@@ -52,8 +53,8 @@ public class FreeformWorkspaceLayoutAlgorithm {
int numFreeformTasks = stackLayout.mNumFreeformTasks;
if (!freeformTasks.isEmpty()) {
// Calculate the cell width/height depending on the number of freeform tasks
- mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
- mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks /
+ mFreeformCellXCount = Math.max(1, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
+ mFreeformCellYCount = Math.max(1, (int) Math.ceil((float) numFreeformTasks /
mFreeformCellXCount));
// For now, make the cells square
mFreeformCellWidth = Math.min(stackLayout.mFreeformRect.width() / mFreeformCellXCount,
@@ -94,15 +95,21 @@ public class FreeformWorkspaceLayoutAlgorithm {
public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
TaskStackLayoutAlgorithm stackLayout) {
if (mTaskIndexMap.containsKey(task.key)) {
- // This is a freeform task, so lay it out in the freeform workspace
+ Rect taskRect = stackLayout.mTaskRect;
int taskIndex = mTaskIndexMap.get(task.key);
- int topOffset = (stackLayout.mFreeformRect.top - stackLayout.mTaskRect.top);
+ int topOffset = (stackLayout.mFreeformRect.top - taskRect.top);
int x = taskIndex % mFreeformCellXCount;
int y = taskIndex / mFreeformCellXCount;
- float scale = (float) mFreeformCellWidth / stackLayout.mTaskRect.width();
- int scaleXOffset = (int) (((1f - scale) * stackLayout.mTaskRect.width()) / 2);
- int scaleYOffset = (int) (((1f - scale) * stackLayout.mTaskRect.height()) / 2);
- transformOut.scale = scale * 0.9f;
+
+ int bitmapWidth = task.thumbnail.getWidth();
+ int bitmapHeight = task.thumbnail.getHeight();
+ float thumbnailScale = Math.min((float) mFreeformCellWidth / bitmapWidth,
+ (float) mFreeformCellHeight / bitmapHeight);
+ float thumbnailWidth = bitmapWidth * thumbnailScale;
+ float thumbnailHeight = bitmapHeight * thumbnailScale;
+ int scaleXOffset = (int) (((1f - thumbnailScale) * thumbnailWidth) / 2);
+ int scaleYOffset = (int) (((1f - thumbnailScale) * thumbnailHeight) / 2);
+ transformOut.scale = thumbnailScale * 0.9f;
transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
transformOut.translationY = topOffset + y * mFreeformCellHeight - scaleYOffset;
transformOut.translationZ = stackLayout.mMaxTranslationZ;
@@ -115,7 +122,6 @@ public class FreeformWorkspaceLayoutAlgorithm {
if (DEBUG) {
Log.d(TAG, "getTransform: " + task.key + ", " + transformOut);
}
-
return transformOut;
}
return null;
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 0557f651acc6..551f067c7790 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -81,7 +81,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
RecentsTransitionHelper mTransitionHelper;
RecentsViewTouchHandler mTouchHandler;
- DragView mDragView;
TaskStack.DockState[] mVisibleDockStates = {
TaskStack.DockState.LEFT,
TaskStack.DockState.TOP,
@@ -341,13 +340,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
}
- if (mDragView != null) {
- Rect taskRect = mTaskStackView.mLayoutAlgorithm.mTaskRect;
- mDragView.measure(
- MeasureSpec.makeMeasureSpec(taskRect.width(), MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(taskRect.height(), MeasureSpec.AT_MOST));
- }
-
// Measure the history button with the full space above the stack, but width-constrained
// to the stack
Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
@@ -379,11 +371,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
}
- if (mDragView != null) {
- mDragView.layout(left, top, left + mDragView.getMeasuredWidth(),
- top + mDragView.getMeasuredHeight());
- }
-
// Layout the history button left-aligned with the stack, but offset from the top of the
// view
Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
@@ -461,10 +448,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
/**** EventBus Events ****/
public final void onBusEvent(DragStartEvent event) {
- // Add the drag view
- mDragView = event.dragView;
- addView(mDragView);
-
updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
TaskStack.DockState.NONE.viewState.dockAreaAlpha);
}
@@ -480,65 +463,36 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
}
public final void onBusEvent(final DragEndEvent event) {
- final Runnable cleanUpRunnable = new Runnable() {
- @Override
- public void run() {
- // Remove the drag view
- removeView(mDragView);
- mDragView = null;
- }
- };
-
// Animate the overlay alpha back to 0
updateVisibleDockRegions(null, -1);
- if (event.dropTarget == null) {
- // No drop targets for hit, so just animate the task back to its place
- event.postAnimationTrigger.increment();
- event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
- @Override
- public void run() {
- cleanUpRunnable.run();
- }
- });
- // Animate the task back to where it was before then clean up afterwards
- TaskViewTransform taskTransform = new TaskViewTransform();
- TaskStackLayoutAlgorithm layoutAlgorithm = mTaskStackView.getStackAlgorithm();
- layoutAlgorithm.getStackTransform(event.task,
- mTaskStackView.getScroller().getStackScroll(), taskTransform, null);
- event.dragView.animate()
- .scaleX(taskTransform.scale)
- .scaleY(taskTransform.scale)
- .translationX((layoutAlgorithm.mTaskRect.left - event.dragView.getLeft())
- + taskTransform.translationX)
- .translationY((layoutAlgorithm.mTaskRect.top - event.dragView.getTop())
- + taskTransform.translationY)
- .setDuration(175)
- .setInterpolator(mFastOutSlowInInterpolator)
- .withEndAction(event.postAnimationTrigger.decrementAsRunnable())
- .start();
-
- } else if (event.dropTarget instanceof TaskStack.DockState) {
+ // Handle the case where we drop onto a dock region
+ if (event.dropTarget instanceof TaskStack.DockState) {
final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
- // For now, just remove the drag view and the original task
- // TODO: Animate the task to the drop target rect before launching it above
- cleanUpRunnable.run();
+ // Remove the task after it is docked
+ if (event.taskView.isFocusedTask()) {
+ mTaskStackView.resetFocusedTask();
+ }
+ event.taskView.animate()
+ .alpha(0f)
+ .setDuration(150)
+ .setInterpolator(mFastOutLinearInInterpolator)
+ .setUpdateListener(null)
+ .setListener(null)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mTaskStackView.getStack().removeTask(event.task);
+ }
+ })
+ .withLayer()
+ .start();
// Dock the task and launch it
SystemServicesProxy ssp = Recents.getSystemServices();
ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode);
launchTask(event.task, null, INVALID_STACK_ID);
-
- } else {
- // We dropped on another drop target, so just add the cleanup to the post animation
- // trigger
- event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
- @Override
- public void run() {
- cleanUpRunnable.run();
- }
- });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index c7edc92fc207..2920295038ee 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -27,6 +27,7 @@ import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
@@ -64,8 +65,8 @@ public class RecentsViewTouchHandler {
private Task mDragTask;
private TaskView mTaskView;
- private DragView mDragView;
+ private Point mTaskViewOffset = new Point();
private Point mDownPos = new Point();
private boolean mDragging;
@@ -111,20 +112,24 @@ public class RecentsViewTouchHandler {
/**** Events ****/
public final void onBusEvent(DragStartEvent event) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
mRv.getParent().requestDisallowInterceptTouchEvent(true);
mDragging = true;
mDragTask = event.task;
mTaskView = event.taskView;
- mDragView = event.dragView;
mDropTargets.clear();
- float x = mDownPos.x - mDragView.getTopLeftOffset().x;
- float y = mDownPos.y - mDragView.getTopLeftOffset().y;
- mDragView.setTranslationX(x);
- mDragView.setTranslationY(y);
+ int[] recentsViewLocation = new int[2];
+ mRv.getLocationInWindow(recentsViewLocation);
+ mTaskViewOffset.set(mTaskView.getLeft() - recentsViewLocation[0] + event.tlOffset.x,
+ mTaskView.getTop() - recentsViewLocation[1] + event.tlOffset.y);
+ float x = mDownPos.x - mTaskViewOffset.x;
+ float y = mDownPos.y - mTaskViewOffset.y;
+ mTaskView.setTranslationX(x);
+ mTaskView.setTranslationY(y);
RecentsConfiguration config = Recents.getConfiguration();
- if (!config.hasDockedTasks) {
+ if (!ssp.hasDockedTask()) {
// Add the dock state drop targets (these take priority)
TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation();
for (TaskStack.DockState dockState : dockStates) {
@@ -140,7 +145,6 @@ public class RecentsViewTouchHandler {
mDragging = false;
mDragTask = null;
mTaskView = null;
- mDragView = null;
mLastDropTarget = null;
}
@@ -160,8 +164,8 @@ public class RecentsViewTouchHandler {
int height = mRv.getMeasuredHeight();
float evX = ev.getX();
float evY = ev.getY();
- float x = evX - mDragView.getTopLeftOffset().x;
- float y = evY - mDragView.getTopLeftOffset().y;
+ float x = evX - mTaskViewOffset.x;
+ float y = evY - mTaskViewOffset.y;
DropTarget currentDropTarget = null;
for (DropTarget target : mDropTargets) {
@@ -176,8 +180,8 @@ public class RecentsViewTouchHandler {
currentDropTarget));
}
- mDragView.setTranslationX(x);
- mDragView.setTranslationY(y);
+ mTaskView.setTranslationX(x);
+ mTaskView.setTranslationY(y);
}
break;
}
@@ -187,7 +191,7 @@ public class RecentsViewTouchHandler {
ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(
mRv.getContext(), null, null, null);
postAnimationTrigger.increment();
- EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView, mDragView,
+ EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView,
mLastDropTarget, postAnimationTrigger));
postAnimationTrigger.decrement();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 30efd5f3fa82..0395e9992abf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -16,6 +16,10 @@
package com.android.systemui.recents.views;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.ComponentName;
import android.content.Context;
@@ -1420,7 +1424,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
+ private AnimatorSet mDropAnimation;
+
public final void onBusEvent(DragStartEvent event) {
+ // Cancel the existing drop animation
+ Utilities.cancelAnimationWithoutCallbacks(mDropAnimation);
+
if (event.task.isFreeformTask()) {
// Animate to the front of the stack
mStackScroller.animateScroll(mStackScroller.getStackScroll(),
@@ -1441,56 +1450,84 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
public final void onBusEvent(final DragEndEvent event) {
- if (event.dropTarget != mFreeformWorkspaceDropTarget &&
- event.dropTarget != mStackDropTarget) {
- return;
- }
- if (event.task.isFreeformTask() && event.dropTarget == mFreeformWorkspaceDropTarget) {
- // TODO: Animate back into view
- return;
- }
- if (!event.task.isFreeformTask() && event.dropTarget == mStackDropTarget) {
- // TODO: Animate back into view
+ // We don't handle drops on the dock regions
+ if (event.dropTarget instanceof TaskStack.DockState) {
return;
}
- // Move the task to the right position in the stack (ie. the front of the stack if freeform
- // or the front of the stack if fullscreen). Note, we MUST move the tasks before we update
- // their stack ids, otherwise, the keys will have changed.
- if (event.dropTarget == mFreeformWorkspaceDropTarget) {
- mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
- updateLayout(true);
- } else if (event.dropTarget == mStackDropTarget) {
- mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
- updateLayout(true);
+ boolean isFreeformTask = event.task.isFreeformTask();
+ boolean hasChangedStacks =
+ (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) ||
+ (isFreeformTask && event.dropTarget == mStackDropTarget);
+ if (hasChangedStacks) {
+ ArrayList<Animator> animations = new ArrayList<>();
+
+ // Move the task to the right position in the stack (ie. the front of the stack if
+ // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
+ // before we update their stack ids, otherwise, the keys will have changed.
+ if (event.dropTarget == mFreeformWorkspaceDropTarget) {
+ mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
+ updateLayout(true);
+
+ // Update the clipping to match the scaled bitmap rect
+ TaskViewThumbnail thumbnailView = event.taskView.mThumbnailView;
+ float thumbnailScale = thumbnailView.computeThumbnailScale(true);
+ RectF bitmapRect = thumbnailView.getScaledBitmapRect(thumbnailScale);
+ AnimateableViewBounds viewBounds = event.taskView.getViewBounds();
+ int clipRight = (int) (thumbnailView.getMeasuredWidth() - bitmapRect.width());
+ int clipBottom = (int) (thumbnailView.getMeasuredHeight() - bitmapRect.height());
+ animations.add(ObjectAnimator.ofFloat(thumbnailView, TaskViewThumbnail.BITMAP_SCALE,
+ thumbnailView.getBitmapScale(), thumbnailScale));
+ animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_BOTTOM,
+ viewBounds.getClipBottom(), clipBottom));
+ animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_RIGHT,
+ viewBounds.getClipRight(), clipRight));
+ } else if (event.dropTarget == mStackDropTarget) {
+ mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
+ updateLayout(true);
+
+ // Reset the clipping when animating to the stack
+ TaskViewThumbnail thumbnailView = event.taskView.mThumbnailView;
+ float thumbnailScale = thumbnailView.computeThumbnailScale(false);
+ AnimateableViewBounds viewBounds = event.taskView.getViewBounds();
+ animations.add(ObjectAnimator.ofFloat(thumbnailView, TaskViewThumbnail.BITMAP_SCALE,
+ thumbnailView.getBitmapScale(), thumbnailScale));
+ animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_BOTTOM,
+ viewBounds.getClipBottom(), 0));
+ animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_RIGHT,
+ viewBounds.getClipRight(), 0));
+ }
+
+ // Move the task to the new stack in the system after the animation completes
+ event.postAnimationTrigger.increment();
+ event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+ @Override
+ public void run() {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
+ }
+ });
+
+ // Animate the normal properties of the view
+ mDropAnimation = new AnimatorSet();
+ mDropAnimation.playTogether(animations);
+ mDropAnimation.setDuration(250);
+ mDropAnimation.setInterpolator(mFastOutSlowInInterpolator);
+ mDropAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ event.postAnimationTrigger.decrement();
+ }
+ });
+ mDropAnimation.start();
}
event.postAnimationTrigger.increment();
- event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
- @Override
- public void run() {
- SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
- }
- });
+ event.taskView.animate()
+ .withEndAction(event.postAnimationTrigger.decrementAsRunnable());
- // Animate the drag view to the new position
- mLayoutAlgorithm.getStackTransform(event.task, mStackScroller.getStackScroll(),
- mTmpTransform, null);
- event.dragView.animate()
- .scaleX(mTmpTransform.scale)
- .scaleY(mTmpTransform.scale)
- .translationX((mLayoutAlgorithm.mTaskRect.left - event.dragView.getLeft())
- + mTmpTransform.translationX)
- .translationY((mLayoutAlgorithm.mTaskRect.top - event.dragView.getTop())
- + mTmpTransform.translationY)
- .setDuration(175)
- .setInterpolator(mFastOutSlowInInterpolator)
- .withEndAction(event.postAnimationTrigger.decrementAsRunnable())
- .start();
-
- // Animate the other views into place
- requestSynchronizeStackViewsWithModel(175);
+ // Animate the tack view back into position
+ requestSynchronizeStackViewsWithModel(250);
}
public final void onBusEvent(StackViewScrolledEvent event) {
@@ -1522,7 +1559,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
- tv.animate().alpha(0f).setDuration(200).start();
+ tv.animate()
+ .alpha(0f)
+ .setDuration(200)
+ .setUpdateListener(null)
+ .setListener(null)
+ .withLayer()
+ .start();
}
}
@@ -1531,7 +1574,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
- tv.animate().alpha(1f).setDuration(200).start();
+ tv.animate()
+ .alpha(1f)
+ .setDuration(200)
+ .setUpdateListener(null)
+ .setListener(null)
+ .withLayer()
+ .start();
}
}
@@ -1543,7 +1592,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
Task task = tv.getTask();
// Reset the previously focused task before it is removed from the stack
- resetFocusedTask();
+ if (tv.isFocusedTask()) {
+ resetFocusedTask();
+ }
// Announce for accessibility
tv.announceForAccessibility(getContext().getString(
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 6db2eb98a215..f2c89e66f517 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -22,8 +22,6 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
@@ -244,6 +242,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
void resetViewProperties() {
setDim(0);
setLayerType(View.LAYER_TYPE_NONE, null);
+ setVisibility(View.VISIBLE);
+ getViewBounds().reset();
TaskViewTransform.reset(this);
if (mActionButtonView != null) {
mActionButtonView.setScaleX(1f);
@@ -251,18 +251,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mActionButtonView.setAlpha(1f);
mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
}
- setVisibility(View.VISIBLE);
- }
-
- /**
- * When we are un/filtering, this method will set up the transform that we are animating to,
- * in order to hide the task.
- */
- void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
- // Fade the view out and slide it away
- toTransform.alpha = 0f;
- toTransform.translationY += 200;
- toTransform.translationZ = 0;
}
/** Prepares this task view for the enter-recents animations. This is called earlier in the
@@ -296,8 +284,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
}
// Apply the current dim
setDim(initialDim);
- // Prepare the thumbnail view alpha
- mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask);
}
/** Animates this task view as it enters recents */
@@ -409,6 +395,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
.translationY(ctx.offscreenTranslationY)
.setStartDelay(0)
.setUpdateListener(null)
+ .setListener(null)
.setInterpolator(mFastOutLinearInInterpolator)
.setDuration(taskViewExitToHomeDuration)
.withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
@@ -425,9 +412,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
R.dimen.recents_task_view_affiliate_group_enter_offset);
if (isLaunchingTask) {
- // Animate the thumbnail alpha back into full opacity for the window animation out
- mThumbnailView.startLaunchTaskAnimation(postAnimRunnable);
-
// Animate the dim
if (mDimAlpha > 0) {
ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
@@ -448,10 +432,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
.setStartDelay(0)
.setDuration(taskViewExitToAppDuration)
.setInterpolator(mFastOutLinearInInterpolator)
+ .withEndAction(postAnimRunnable)
.start();
} else {
// Hide the dismiss button
- mHeaderView.startLaunchTaskDismissAnimation();
+ mHeaderView.startLaunchTaskDismissAnimation(postAnimRunnable);
// If this is another view in the task grouping and is in front of the launch task,
// animate it away first
if (occludesLaunchTarget) {
@@ -459,6 +444,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
.translationY(getTranslationY() + taskViewAffiliateGroupEnterOffset)
.setStartDelay(0)
.setUpdateListener(null)
+ .setListener(null)
.setInterpolator(mFastOutLinearInInterpolator)
.setDuration(taskViewExitToAppDuration)
.start();
@@ -480,6 +466,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
.alpha(0f)
.setStartDelay(delay)
.setUpdateListener(null)
+ .setListener(null)
.setInterpolator(mFastOutSlowInInterpolator)
.setDuration(taskViewRemoveAnimDuration)
.withEndAction(new Runnable() {
@@ -637,7 +624,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mIsFocused = isFocused;
mIsFocusAnimated = animated;
mHeaderView.onTaskViewFocusChanged(isFocused, animated);
- mThumbnailView.onFocusChanged(isFocused);
if (isFocused) {
if (requestViewFocus && !isFocused()) {
requestFocus();
@@ -688,21 +674,13 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
@Override
public void onTaskDataLoaded() {
SystemServicesProxy ssp = Recents.getSystemServices();
- RecentsConfiguration config = Recents.getConfiguration();
if (mThumbnailView != null && mHeaderView != null) {
// Bind each of the views to the new task data
mThumbnailView.rebindToTask(mTask);
mHeaderView.rebindToTask(mTask);
// Rebind any listeners
mActionButtonView.setOnClickListener(this);
-
- // Only enable long-click if we have a freeform workspace to drag to/from, or if we
- // aren't already docked
- if (ssp.hasFreeformWorkspaceSupport() || !config.hasDockedTasks) {
- setOnLongClickListener(this);
- } else {
- setOnLongClickListener(null);
- }
+ setOnLongClickListener(this);
}
mTaskDataLoaded = true;
}
@@ -742,58 +720,27 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
@Override
public boolean onLongClick(View v) {
- if (v == this) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (v == this && !ssp.hasDockedTask()) {
// Start listening for drag events
setClipViewInStack(false);
+ // Enlarge the view slightly
final float finalScale = getScaleX() * 1.05f;
- final int width = getWidth();
- final int height = getHeight();
- Bitmap dragBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas c = new Canvas(dragBitmap);
- mThumbnailView.draw(c);
- mHeaderView.draw(c);
- c.setBitmap(null);
-
- // The downTouchPos is relative to the currently transformed TaskView, but we will be
- // dragging a copy of the full task view, which makes it easier for us to animate them
- // when the user drops
- mDownTouchPos.x += ((1f - getScaleX()) * width) / 2;
- mDownTouchPos.y += ((1f - getScaleY()) * height) / 2;
-
- // Initiate the drag
- final DragView dragView = new DragView(getContext(), dragBitmap, mDownTouchPos);
- dragView.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRect(0, 0, width, height);
- }
- });
- dragView.setScaleX(getScaleX());
- dragView.setScaleY(getScaleY());
- dragView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- // Hide this task view after the drag view is attached
- setVisibility(View.INVISIBLE);
- // Animate the alpha slightly to indicate dragging
- dragView.setElevation(getElevation());
- dragView.setTranslationZ(getTranslationZ());
- dragView.animate()
- .scaleX(finalScale)
- .scaleY(finalScale)
- .setDuration(175)
- .setInterpolator(mFastOutSlowInInterpolator)
- .start();
- }
+ animate()
+ .scaleX(finalScale)
+ .scaleY(finalScale)
+ .setDuration(175)
+ .setUpdateListener(null)
+ .setListener(null)
+ .setInterpolator(mFastOutSlowInInterpolator)
+ .start();
+
+ mDownTouchPos.x += ((1f - getScaleX()) * getWidth()) / 2;
+ mDownTouchPos.y += ((1f - getScaleY()) * getHeight()) / 2;
- @Override
- public void onViewDetachedFromWindow(View v) {
- // Do nothing
- }
- });
EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
- EventBus.getDefault().send(new DragStartEvent(mTask, this, dragView));
+ EventBus.getDefault().send(new DragStartEvent(mTask, this, mDownTouchPos));
return true;
}
return false;
@@ -806,9 +753,6 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
public void run() {
- // Show this task view
- setVisibility(View.VISIBLE);
-
// Animate the drag view back from where it is, to the view location, then after
// it returns, update the clip state
setClipViewInStack(true);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 76c6691aa2bd..85b4b9baae25 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -284,7 +284,7 @@ public class TaskViewHeader extends FrameLayout
}
/** Animates this task bar dismiss button when launching a task. */
- void startLaunchTaskDismissAnimation() {
+ void startLaunchTaskDismissAnimation(final Runnable postAnimationRunanble) {
if (mDismissButton.getVisibility() == View.VISIBLE) {
int taskViewExitToAppDuration = mContext.getResources().getInteger(
R.integer.recents_task_exit_to_app_duration);
@@ -294,6 +294,7 @@ public class TaskViewHeader extends FrameLayout
.setStartDelay(0)
.setInterpolator(mFastOutSlowInInterpolator)
.setDuration(taskViewExitToAppDuration)
+ .withEndAction(postAnimationRunanble)
.start();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index b3d263e848ae..c288afb87302 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -16,9 +16,6 @@
package com.android.systemui.recents.views;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
@@ -31,11 +28,12 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.Property;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import com.android.systemui.R;
-import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
@@ -45,6 +43,19 @@ import com.android.systemui.recents.model.Task;
*/
public class TaskViewThumbnail extends View {
+ public static final Property<TaskViewThumbnail, Float> BITMAP_SCALE =
+ new FloatProperty<TaskViewThumbnail>("bitmapScale") {
+ @Override
+ public void setValue(TaskViewThumbnail object, float scale) {
+ object.setBitmapScale(scale);
+ }
+
+ @Override
+ public Float get(TaskViewThumbnail object) {
+ return object.getBitmapScale();
+ }
+ };
+
private Task mTask;
// Drawing
@@ -60,18 +71,6 @@ public class TaskViewThumbnail extends View {
Interpolator mFastOutSlowInInterpolator;
- // Thumbnail alpha
- float mThumbnailAlpha;
- ValueAnimator mThumbnailAlphaAnimator;
- ValueAnimator.AnimatorUpdateListener mThumbnailAlphaUpdateListener
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mThumbnailAlpha = (float) animation.getAnimatedValue();
- updateThumbnailPaintFilter();
- }
- };
-
// Task bar clipping, the top of this thumbnail can be clipped against the opaque header
// bar that overlaps this thumbnail
View mTaskBar;
@@ -105,17 +104,11 @@ public class TaskViewThumbnail extends View {
}
@Override
- protected void onFinishInflate() {
- mThumbnailAlpha = getResources().getFloat(R.dimen.recents_task_view_thumbnail_alpha);
- updateThumbnailPaintFilter();
- }
-
- @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
mLayoutRect.set(0, 0, getWidth(), getHeight());
- updateThumbnailScale();
+ setBitmapScale(computeThumbnailScale(mTask != null ? mTask.isFreeformTask() : false));
}
}
@@ -137,12 +130,15 @@ public class TaskViewThumbnail extends View {
Shader.TileMode.CLAMP);
mDrawPaint.setShader(mBitmapShader);
mBitmapRect.set(0, 0, bm.getWidth(), bm.getHeight());
- updateThumbnailScale();
} else {
mBitmapShader = null;
mDrawPaint.setShader(null);
}
- updateThumbnailPaintFilter();
+ if (mTask != null) {
+ setBitmapScale(computeThumbnailScale(mTask != null ? mTask.isFreeformTask() : false));
+ } else {
+ setBitmapScale(1f);
+ }
}
/** Updates the paint to draw the thumbnail. */
@@ -150,36 +146,61 @@ public class TaskViewThumbnail extends View {
if (mInvisible) {
return;
}
- int mul = (int) ((1.0f - mDimAlpha) * mThumbnailAlpha * 255);
- int add = (int) ((1.0f - mDimAlpha) * (1 - mThumbnailAlpha) * 255);
+ int mul = (int) ((1.0f - mDimAlpha) * 255);
if (mBitmapShader != null) {
mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul));
- mLightingColorFilter.setColorAdd(Color.argb(0, add, add, add));
mDrawPaint.setColorFilter(mLightingColorFilter);
mDrawPaint.setColor(0xffffffff);
} else {
- int grey = mul + add;
+ int grey = mul;
mDrawPaint.setColorFilter(null);
mDrawPaint.setColor(Color.argb(255, grey, grey, grey));
}
invalidate();
}
- /** Updates the thumbnail shader's scale transform. */
- void updateThumbnailScale() {
- if (mBitmapShader != null) {
- if (mTask.isFreeformTask()) {
- // For freeform tasks, we scale the bitmap rect to fit in the layout rect
- mBitmapScale = Math.min(mLayoutRect.width() / mBitmapRect.width(),
- mLayoutRect.height() / mBitmapRect.height());
- } else {
- // For stack tasks, we scale the bitmap to fit the width
- mBitmapScale = Math.max(1f, mLayoutRect.width() / mBitmapRect.width());
- }
+ /**
+ * Returns the scale to apply to a thumbnail bitmap relative to this view rect.
+ */
+ public float computeThumbnailScale(boolean isFreeformTask) {
+ if (isFreeformTask) {
+ // For freeform tasks, we scale the bitmap rect to fit in the layout rect
+ return Math.min(mLayoutRect.width() / mBitmapRect.width(),
+ mLayoutRect.height() / mBitmapRect.height());
+ } else {
+ // For stack tasks, we scale the bitmap to fit the width
+ return Math.max(1f, mLayoutRect.width() / mBitmapRect.width());
+ }
+ }
+ /**
+ * Returns the scaled bitmap rect.
+ */
+ public RectF getScaledBitmapRect(float scale) {
+ RectF scaledBitmapRect = new RectF(mBitmapRect);
+ scaledBitmapRect.left *= scale;
+ scaledBitmapRect.top *= scale;
+ scaledBitmapRect.right *= scale;
+ scaledBitmapRect.bottom *= scale;
+ return scaledBitmapRect;
+ }
+
+ /**
+ * Sets the scale of the bitmap relative to this view.
+ */
+ public void setBitmapScale(float scale) {
+ if (mBitmapShader != null) {
+ mBitmapScale = scale;
mScaleMatrix.setScale(mBitmapScale, mBitmapScale);
mBitmapShader.setLocalMatrix(mScaleMatrix);
}
+ if (!mInvisible) {
+ invalidate();
+ }
+ }
+
+ public float getBitmapScale() {
+ return mBitmapScale;
}
/** Updates the clip rect based on the given task bar. */
@@ -227,67 +248,4 @@ public class TaskViewThumbnail extends View {
mTask = null;
setThumbnail(null);
}
-
- /** Handles focus changes. */
- void onFocusChanged(boolean focused) {
- if (focused) {
- if (Float.compare(getAlpha(), 1f) != 0) {
- startFadeAnimation(1f, 150, null);
- }
- } else {
- float taskViewThumbnailAlpha = getResources().getFloat(
- R.dimen.recents_task_view_thumbnail_alpha);
- if (Float.compare(getAlpha(), taskViewThumbnailAlpha) != 0) {
- startFadeAnimation(taskViewThumbnailAlpha, 150, null);
- }
- }
- }
-
- /**
- * Prepares for the enter recents animation, this gets called before the the view
- * is first visible and will be followed by a startEnterRecentsAnimation() call.
- */
- void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask) {
- if (isTaskViewLaunchTargetTask) {
- mThumbnailAlpha = 1f;
- } else {
- mThumbnailAlpha = getResources().getFloat(
- R.dimen.recents_task_view_thumbnail_alpha);
- }
- updateThumbnailPaintFilter();
- }
-
- /** Animates this task thumbnail as it enters Recents. */
- void startEnterRecentsAnimation(Runnable postAnimRunnable) {
- float taskViewThumbnailAlpha = getResources().getFloat(
- R.dimen.recents_task_view_thumbnail_alpha);
- startFadeAnimation(taskViewThumbnailAlpha,
- getResources().getInteger(R.integer.recents_task_enter_from_app_duration),
- postAnimRunnable);
- }
-
- /** Animates this task thumbnail as it exits Recents. */
- void startLaunchTaskAnimation(Runnable postAnimRunnable) {
- int taskViewExitToAppDuration = mContext.getResources().getInteger(
- R.integer.recents_task_exit_to_app_duration);
- startFadeAnimation(1f, taskViewExitToAppDuration, postAnimRunnable);
- }
-
- /** Starts a new thumbnail alpha animation. */
- void startFadeAnimation(float finalAlpha, int duration, final Runnable postAnimRunnable) {
- Utilities.cancelAnimationWithoutCallbacks(mThumbnailAlphaAnimator);
- mThumbnailAlphaAnimator = ValueAnimator.ofFloat(mThumbnailAlpha, finalAlpha);
- mThumbnailAlphaAnimator.setDuration(duration);
- mThumbnailAlphaAnimator.setInterpolator(mFastOutSlowInInterpolator);
- mThumbnailAlphaAnimator.addUpdateListener(mThumbnailAlphaUpdateListener);
- if (postAnimRunnable != null) {
- mThumbnailAlphaAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- postAnimRunnable.run();
- }
- });
- }
- mThumbnailAlphaAnimator.start();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index 174ff33e9681..6d43f9c570ca 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -122,6 +122,7 @@ public class TaskViewTransform {
} else {
anim.setUpdateListener(null);
}
+ anim.setListener(null);
anim.setStartDelay(startDelay)
.setDuration(duration)
.setInterpolator(interp)
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 50e010fc2928..6ff7a3e08159 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -17,10 +17,14 @@
package com.android.systemui.stackdivider;
import android.content.res.Configuration;
+import android.view.IDockDividerVisibilityListener;
import android.view.LayoutInflater;
+import android.view.View;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@@ -33,6 +37,8 @@ public class Divider extends SystemUI {
private int mDividerWindowWidth;
private DividerWindowManager mWindowManager;
private DividerView mView;
+ private DockDividerVisibilityListener mDockDividerVisibilityListener;
+ private boolean mVisible = false;
@Override
public void start() {
@@ -41,6 +47,9 @@ public class Divider extends SystemUI {
com.android.internal.R.dimen.docked_stack_divider_thickness);
update(mContext.getResources().getConfiguration());
putComponent(Divider.class, this);
+ mDockDividerVisibilityListener = new DockDividerVisibilityListener();
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.registerDockDividerVisibilityListener(mDockDividerVisibilityListener);
}
@Override
@@ -56,6 +65,7 @@ public class Divider extends SystemUI {
private void addDivider(Configuration configuration) {
mView = (DividerView)
LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
+ mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
final int width = landscape ? mDividerWindowWidth : MATCH_PARENT;
final int height = landscape ? MATCH_PARENT : mDividerWindowWidth;
@@ -71,4 +81,23 @@ public class Divider extends SystemUI {
removeDivider();
addDivider(configuration);
}
+
+ private void updateVisibility(final boolean visible) {
+ mView.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mVisible != visible) {
+ mVisible = visible;
+ mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+ });
+ }
+
+ class DockDividerVisibilityListener extends IDockDividerVisibilityListener.Stub {
+ @Override
+ public void onDockDividerVisibilityChanged(boolean visible) {
+ updateVisibility(visible);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 723989affae6..5d4c64e8cec3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -40,7 +40,6 @@ import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
-import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.AsyncTask;
@@ -193,6 +192,7 @@ public abstract class BaseStatusBar extends SystemUI implements
protected IDreamManager mDreamManager;
PowerManager mPowerManager;
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ protected int mRowMinHeightLegacy;
protected int mRowMinHeight;
protected int mRowMaxHeight;
@@ -887,15 +887,6 @@ public abstract class BaseStatusBar extends SystemUI implements
entry.row.setShowingLegacyBackground(true);
entry.legacy = true;
}
- } else {
- // Using platform templates
- final int color = sbn.getNotification().color;
- if (isMediaNotification(entry)) {
- entry.row.setTintColor(color == Notification.COLOR_DEFAULT
- ? mContext.getColor(
- R.color.notification_material_background_media_default_color)
- : color);
- }
}
if (entry.icon != null) {
@@ -1299,6 +1290,17 @@ public abstract class BaseStatusBar extends SystemUI implements
}
}
+ /**
+ * Called when the panel was layouted expanded for the first time after being collapsed.
+ */
+ public void onPanelExpandedAndLayouted() {
+ if (mState == StatusBarState.KEYGUARD) {
+ // Since the number of notifications is determined based on the height of the view, we
+ // need to update them.
+ updateRowStates();
+ }
+ }
+
protected class H extends Handler {
public void handleMessage(Message m) {
switch (m.what) {
@@ -1337,7 +1339,6 @@ public abstract class BaseStatusBar extends SystemUI implements
PackageManager pmUser = getPackageManagerForUser(mContext,
entry.notification.getUser().getIdentifier());
- int maxHeight = mRowMaxHeight;
final StatusBarNotification sbn = entry.notification;
entry.cacheContentViews(mContext, null);
@@ -1498,18 +1499,6 @@ public abstract class BaseStatusBar extends SystemUI implements
Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic);
icon.setImageDrawable(iconDrawable);
- if (entry.targetSdk >= Build.VERSION_CODES.LOLLIPOP
- || mNotificationColorUtil.isGrayscaleIcon(iconDrawable)) {
- icon.setBackgroundResource(
- com.android.internal.R.drawable.notification_icon_legacy_bg);
- int padding = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_large_icon_circle_padding);
- icon.setPadding(padding, padding, padding, padding);
- if (sbn.getNotification().color != Notification.COLOR_DEFAULT) {
- icon.getBackground().setColorFilter(
- sbn.getNotification().color, PorterDuff.Mode.SRC_ATOP);
- }
- }
if (profileBadge != null) {
Drawable profileDrawable = mContext.getPackageManager().getUserBadgeForDensity(
@@ -1536,11 +1525,6 @@ public abstract class BaseStatusBar extends SystemUI implements
R.style.TextAppearance_Material_Notification_Parenthetical);
}
- int topPadding = Notification.Builder.calculateTopPadding(mContext,
- false /* hasThreeLines */,
- mContext.getResources().getConfiguration().fontScale);
- title.setPadding(0, topPadding, 0, 0);
-
contentContainerPublic.setContractedChild(publicViewLocal);
entry.autoRedacted = true;
}
@@ -1553,7 +1537,7 @@ public abstract class BaseStatusBar extends SystemUI implements
}
}
entry.row = row;
- entry.row.setHeightRange(mRowMinHeight, maxHeight);
+ updateNotificationHeightRange(entry);
entry.row.setOnActivatedListener(this);
entry.row.setExpandable(bigContentViewLocal != null);
@@ -1566,11 +1550,19 @@ public abstract class BaseStatusBar extends SystemUI implements
row.setUserExpanded(userExpanded);
}
row.setUserLocked(userLocked);
- row.setEntry(entry);
+ row.updateStatusBarNotification(entry.notification);
applyRemoteInput(entry);
return true;
}
+ private void updateNotificationHeightRange(Entry entry) {
+ boolean customView = entry.getContentView().getId()
+ != com.android.internal.R.id.status_bar_latest_event_content;
+ boolean beforeN = entry.targetSdk < Build.VERSION_CODES.N;
+ int minHeight = customView && beforeN ? mRowMinHeightLegacy : mRowMinHeight;
+ entry.row.setHeightRange(minHeight, mRowMaxHeight);
+ }
+
/**
* Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
* via first-class API.
@@ -1982,15 +1974,15 @@ public abstract class BaseStatusBar extends SystemUI implements
}
/**
+ * @param recompute wheter the number should be recomputed
* @return The number of notifications we show on Keyguard.
*/
- protected abstract int getMaxKeyguardNotifications();
+ protected abstract int getMaxKeyguardNotifications(boolean recompute);
/**
* Updates expanded, dimmed and locked states of notification rows.
*/
protected void updateRowStates() {
- int maxKeyguardNotifications = getMaxKeyguardNotifications();
mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
@@ -1998,6 +1990,10 @@ public abstract class BaseStatusBar extends SystemUI implements
int visibleNotifications = 0;
boolean onKeyguard = mState == StatusBarState.KEYGUARD;
+ int maxNotifications = 0;
+ if (onKeyguard) {
+ maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
+ }
for (int i = 0; i < N; i++) {
NotificationData.Entry entry = activeNotifications.get(i);
if (onKeyguard) {
@@ -2013,7 +2009,7 @@ public abstract class BaseStatusBar extends SystemUI implements
== View.VISIBLE;
boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
if ((isLockscreenPublicMode() && !mShowLockscreenNotifications) ||
- (onKeyguard && (visibleNotifications >= maxKeyguardNotifications
+ (onKeyguard && (visibleNotifications >= maxNotifications
&& !childWithVisibleSummary
|| !showOnKeyguard))) {
entry.row.setVisibility(View.GONE);
@@ -2188,8 +2184,7 @@ public abstract class BaseStatusBar extends SystemUI implements
// update the contentIntent
mNotificationClicker.register(entry.row, sbn);
- entry.row.setEntry(entry);
- entry.row.notifyContentUpdated();
+ entry.row.updateStatusBarNotification(entry.notification);
entry.row.resetHeight();
applyRemoteInput(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index 140519163773..857019875d9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -120,12 +120,12 @@ public class DragDownHelper implements Gefingerpoken {
if (mLastHeight > mMinDragDistance) {
if (!mDraggedFarEnough) {
mDraggedFarEnough = true;
- mDragDownCallback.onThresholdReached();
+ mDragDownCallback.onCrossedThreshold(true);
}
} else {
if (mDraggedFarEnough) {
mDraggedFarEnough = false;
- mDragDownCallback.onDragDownReset();
+ mDragDownCallback.onCrossedThreshold(false);
}
}
return true;
@@ -236,7 +236,12 @@ public class DragDownHelper implements Gefingerpoken {
*/
boolean onDraggedDown(View startingChild, int dragLengthY);
void onDragDownReset();
- void onThresholdReached();
+
+ /**
+ * The user has dragged either above or below the threshold
+ * @param above whether he dragged above it
+ */
+ void onCrossedThreshold(boolean above);
void onTouchSlopExceeded();
void setEmptyDragAmount(float amount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 360390023df3..81e20aaf13ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -25,16 +25,17 @@ import android.graphics.drawable.Drawable;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewStub;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.LinearInterpolator;
import android.widget.Chronometer;
import android.widget.ImageView;
+import android.widget.RemoteViews;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.statusbar.notification.NotificationHeaderView;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
import com.android.systemui.statusbar.stack.StackScrollState;
@@ -89,22 +90,31 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
private boolean mIsHeadsUp;
private boolean mLastChronometerRunning = true;
private NotificationHeaderView mNotificationHeader;
- private ViewStub mNotificationHeaderStub;
private ViewStub mChildrenContainerStub;
private NotificationGroupManager mGroupManager;
private boolean mChildrenExpanded;
private boolean mIsSummaryWithChildren;
private NotificationChildrenContainer mChildrenContainer;
private ViewStub mGutsStub;
- private boolean mHasNotificationHeader;
private boolean mIsSystemChildExpanded;
private boolean mIsPinned;
private FalsingManager mFalsingManager;
private boolean mJustClicked;
- private NotificationData.Entry mEntry;
+ private boolean mIconAnimationRunning;
private boolean mShowNoBackground;
private ExpandableNotificationRow mNotificationParent;
+ private OnClickListener mExpandClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mGroupManager.isSummaryOfGroup(mStatusBarNotification)) {
+ mGroupManager.toggleGroupExpansion(mStatusBarNotification);
+ } else {
+ setUserExpanded(!isExpanded());
+ notifyHeightChanged(true);
+ }
+ }
+ };
public NotificationContentView getPrivateLayout() {
return mPrivateLayout;
@@ -117,6 +127,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
public void setIconAnimationRunning(boolean running) {
setIconAnimationRunning(running, mPublicLayout);
setIconAnimationRunning(running, mPrivateLayout);
+ setIconAnimationRunningForChild(running, mNotificationHeader);
+ if (mIsSummaryWithChildren) {
+ List<ExpandableNotificationRow> notificationChildren =
+ mChildrenContainer.getNotificationChildren();
+ for (int i = 0; i < notificationChildren.size(); i++) {
+ ExpandableNotificationRow child = notificationChildren.get(i);
+ child.setIconAnimationRunning(running);
+ }
+ }
+ mIconAnimationRunning = running;
}
private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
@@ -161,10 +181,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
}
- private void setStatusBarNotification(StatusBarNotification statusBarNotification) {
+ public void updateStatusBarNotification(StatusBarNotification statusBarNotification) {
mStatusBarNotification = statusBarNotification;
- mPrivateLayout.setStatusBarNotification(statusBarNotification);
+ mPrivateLayout.onNotificationUpdated(statusBarNotification);
+ mPublicLayout.onNotificationUpdated(statusBarNotification);
updateVetoButton();
+ if (mIsSummaryWithChildren) {
+ recreateNotificationHeader();
+ }
+ if (mIconAnimationRunning) {
+ setIconAnimationRunning(true);
+ }
onChildrenCountChanged();
}
@@ -381,11 +408,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
}
- public void setEntry(NotificationData.Entry entry) {
- mEntry = entry;
- setStatusBarNotification(entry.notification);
- }
-
public CharSequence getSubText() {
Notification notification = mStatusBarNotification.getNotification();
CharSequence subText = notification.extras.getCharSequence(Notification.EXTRA_SUMMARY_TEXT);
@@ -452,6 +474,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
super.onFinishInflate();
mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
+ mPrivateLayout.setExpandClickListener(mExpandClickListener);
+ mPublicLayout.setExpandClickListener(mExpandClickListener);
mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
@@ -462,15 +486,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mGutsStub = null;
}
});
- mNotificationHeaderStub = (ViewStub) findViewById(R.id.notification_header_stub);
- mNotificationHeaderStub.setOnInflateListener(new ViewStub.OnInflateListener() {
- @Override
- public void onInflate(ViewStub stub, View inflated) {
- mNotificationHeader = (NotificationHeaderView) inflated;
- mNotificationHeader.setGroupManager(mGroupManager);
- mNotificationHeader.bind(mEntry);
- }
- });
mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@@ -494,6 +509,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
return;
}
mChildrenContainer.setVisibility(mIsSummaryWithChildren ? VISIBLE : INVISIBLE);
+ mNotificationHeader.setVisibility(mIsSummaryWithChildren ? VISIBLE : INVISIBLE);
mPrivateLayout.setVisibility(!mIsSummaryWithChildren ? VISIBLE : INVISIBLE);
}
@@ -523,6 +539,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
mRowMinHeight = rowMinHeight;
mMaxViewHeight = rowMaxHeight;
+ mPrivateLayout.setSmallHeight(mRowMinHeight);
+ mPublicLayout.setSmallHeight(mRowMinHeight);
}
public boolean isExpandable() {
@@ -534,6 +552,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
public void setExpandable(boolean expandable) {
mExpandable = expandable;
+ mPrivateLayout.updateExpandButtons(isExpandable());
}
/**
@@ -654,8 +673,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
if (mSensitive && mHideSensitiveForIntrinsicHeight) {
return mRowMinHeight;
} else if (mIsSummaryWithChildren && !mOnKeyguard) {
- return mChildrenContainer.getIntrinsicHeight()
- + mNotificationHeader.getHeight();
+ return mChildrenContainer.getIntrinsicHeight();
} else if (mIsHeadsUp) {
if (inExpansionState) {
return Math.max(mMaxExpandHeight, mHeadsUpHeight);
@@ -681,12 +699,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
private void onChildrenCountChanged() {
- mIsSummaryWithChildren = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
+ mIsSummaryWithChildren = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
&& mGroupManager.hasGroupChildren(mStatusBarNotification);
- if (mIsSummaryWithChildren && mChildrenContainer == null) {
- mChildrenContainerStub.inflate();
+ if (mIsSummaryWithChildren) {
+ if (mChildrenContainer == null) {
+ mChildrenContainerStub.inflate();
+ }
+ if (mNotificationHeader == null) {
+ recreateNotificationHeader();
+ }
}
- updateNotificationHeader();
+ mPrivateLayout.updateExpandButtons(isExpandable());
+ updateHeaderChildCount();
updateChildrenVisibility(true);
}
@@ -771,6 +795,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
animateShowingPublic(delay, duration);
}
+ mPrivateLayout.updateExpandButtons(isExpandable());
updateVetoButton();
mShowingPublicInitialized = true;
}
@@ -811,23 +836,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
}
- public void updateNotificationHeader() {
- boolean hasHeader = hasNotificationHeader();
- if (hasHeader != mHasNotificationHeader) {
- if (hasHeader) {
- if (mNotificationHeader == null) {
- mNotificationHeaderStub.inflate();
- }
- mNotificationHeader.setVisibility(View.VISIBLE);
- } else if (mNotificationHeader != null) {
- mNotificationHeader.setVisibility(View.GONE);
- }
- notifyHeightChanged(true /* needsAnimation */);
- }
- if (hasHeader) {
- mNotificationHeader.bind(mEntry);
+ public void updateHeaderChildCount() {
+ if (mIsSummaryWithChildren) {
+ mNotificationHeader.setChildCount(
+ mChildrenContainer.getNotificationChildren().size());
}
- mHasNotificationHeader = hasHeader;
}
public static void applyTint(View v, int color) {
@@ -876,8 +889,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
@Override
public int getMaxContentHeight() {
if (mIsSummaryWithChildren && !mShowingPublic) {
- return mChildrenContainer.getMaxContentHeight()
- + mNotificationHeader.getHeight();
+ return mChildrenContainer.getMaxContentHeight();
}
NotificationContentView showingLayout = getShowingLayout();
return showingLayout.getMaxHeight();
@@ -885,15 +897,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
@Override
public int getMinHeight() {
- if (mIsSummaryWithChildren && !mOnKeyguard) {
- return mChildrenContainer.getMinHeight()
- + mNotificationHeader.getHeight();
- }
NotificationContentView showingLayout = getShowingLayout();
return showingLayout.getMinHeight();
}
@Override
+ public int getMinExpandHeight() {
+ if (mIsSummaryWithChildren && !mOnKeyguard) {
+ return mChildrenContainer.getMinHeight();
+ }
+ return getMinHeight();
+ }
+
+ @Override
protected boolean shouldLimitViewHeight() {
return !mIsSummaryWithChildren;
}
@@ -908,9 +924,21 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
}
- public void notifyContentUpdated() {
- mPublicLayout.notifyContentUpdated();
- mPrivateLayout.notifyContentUpdated();
+ private void recreateNotificationHeader() {
+ final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
+ getStatusBarNotification().getNotification());
+ final RemoteViews header = builder.makeNotificationHeader();
+ if (mNotificationHeader == null) {
+ mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
+ final View expandButton = mNotificationHeader.findViewById(
+ com.android.internal.R.id.expand_button);
+ expandButton.setVisibility(VISIBLE);
+ mNotificationHeader.setOnClickListener(mExpandClickListener);
+ addView(mNotificationHeader);
+ } else {
+ header.reapply(getContext(), mNotificationHeader);
+ }
+ updateHeaderChildCount();
}
public boolean isMaxExpandHeightInitialized() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index af59ac7ca9eb..51602e7ab831 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -195,6 +195,15 @@ public abstract class ExpandableView extends FrameLayout {
}
/**
+ * @return The minimum height this child chan be expanded to. Note that this might be different
+ * than {@link #getMinHeight()} because some elements can't be collapsed by an expand gesture
+ * to it's absolute minimal height
+ */
+ public int getMinExpandHeight() {
+ return getHeight();
+ }
+
+ /**
* Sets the notification as dimmed. The default implementation does nothing.
*
* @param dimmed Whether the notification should be dimmed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigMediaNarrowViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigMediaNarrowViewWrapper.java
deleted file mode 100644
index 91e540426c1b..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigMediaNarrowViewWrapper.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.view.View;
-
-/**
- * Wraps a big media narrow notification template layout.
- */
-public class NotificationBigMediaNarrowViewWrapper extends NotificationMediaViewWrapper {
-
- protected NotificationBigMediaNarrowViewWrapper(Context ctx,
- View view) {
- super(ctx, view);
- }
-
- @Override
- public boolean needsRoundRectClipping() {
- return true;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigPictureViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigPictureViewWrapper.java
deleted file mode 100644
index ffe0cd1b4beb..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigPictureViewWrapper.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.view.View;
-
-/**
- * Wraps a notification view inflated from a big picture style template.
- */
-public class NotificationBigPictureViewWrapper extends NotificationTemplateViewWrapper {
-
- protected NotificationBigPictureViewWrapper(Context ctx, View view) {
- super(ctx, view);
- }
-
- @Override
- public boolean needsRoundRectClipping() {
- return true;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 5aedaf102421..bca649110a14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -24,6 +24,7 @@ import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
+import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
@@ -52,7 +53,6 @@ public class NotificationContentView extends FrameLayout {
private final Rect mClipBounds = new Rect();
private final int mSingleLineHeight;
- private final int mSmallHeight;
private final int mHeadsUpHeight;
private final int mRoundRectRadius;
private final Interpolator mLinearInterpolator = new LinearInterpolator();
@@ -77,7 +77,9 @@ public class NotificationContentView extends FrameLayout {
private boolean mIsHeadsUp;
private boolean mShowingLegacyBackground;
private boolean mIsChildInGroup;
+ private int mSmallHeight;
private StatusBarNotification mStatusBarNotification;
+ private NotificationGroupManager mGroupManager;
private final ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
= new ViewTreeObserver.OnPreDrawListener() {
@@ -96,7 +98,7 @@ public class NotificationContentView extends FrameLayout {
mRoundRectRadius);
}
};
- private NotificationGroupManager mGroupManager;
+ private OnClickListener mExpandClickListener;
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -104,7 +106,6 @@ public class NotificationContentView extends FrameLayout {
mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
mSingleLineHeight = getResources().getDimensionPixelSize(
R.dimen.notification_single_line_height);
- mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
mHeadsUpHeight = getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
mRoundRectRadius = getResources().getDimensionPixelSize(
R.dimen.notification_material_rounded_rect_radius);
@@ -114,6 +115,10 @@ public class NotificationContentView extends FrameLayout {
setOutlineProvider(mOutlineProvider);
}
+ public void setSmallHeight(int smallHeight) {
+ mSmallHeight = smallHeight;
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
@@ -443,19 +448,6 @@ public class NotificationContentView extends FrameLayout {
}
}
- public void notifyContentUpdated() {
- updateSingleLineView();
- selectLayout(false /* animate */, true /* force */);
- if (mContractedChild != null) {
- mContractedWrapper.notifyContentUpdated();
- mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
- }
- if (mExpandedChild != null) {
- mExpandedWrapper.notifyContentUpdated();
- }
- updateRoundRectClipping();
- }
-
public boolean isContentExpandable() {
return mExpandedChild != null;
}
@@ -488,9 +480,21 @@ public class NotificationContentView extends FrameLayout {
updateSingleLineView();
}
- public void setStatusBarNotification(StatusBarNotification statusBarNotification) {
+ public void onNotificationUpdated(StatusBarNotification statusBarNotification) {
mStatusBarNotification = statusBarNotification;
updateSingleLineView();
+ selectLayout(false /* animate */, true /* force */);
+ if (mContractedChild != null) {
+ mContractedWrapper.notifyContentUpdated();
+ mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
+ }
+ if (mExpandedChild != null) {
+ mExpandedWrapper.notifyContentUpdated();
+ }
+ if (mHeadsUpChild != null) {
+ mHeadsUpWrapper.notifyContentUpdated();
+ }
+ updateRoundRectClipping();
}
private void updateSingleLineView() {
@@ -515,4 +519,20 @@ public class NotificationContentView extends FrameLayout {
public void setGroupManager(NotificationGroupManager groupManager) {
mGroupManager = groupManager;
}
+
+ public void setExpandClickListener(OnClickListener expandClickListener) {
+ mExpandClickListener = expandClickListener;
+ }
+
+ public void updateExpandButtons(boolean expandable) {
+ if (mExpandedChild != null) {
+ mExpandedWrapper.updateExpandability(expandable, mExpandClickListener);
+ }
+ if (mContractedChild != null) {
+ mContractedWrapper.updateExpandability(expandable, mExpandClickListener);
+ }
+ if (mHeadsUpChild != null) {
+ mHeadsUpWrapper.updateExpandability(expandable, mExpandClickListener);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java
deleted file mode 100644
index 953c373c47e9..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.content.Context;
-import android.view.View;
-
-/**
- * Wraps a media notification.
- */
-public class NotificationMediaViewWrapper extends NotificationTemplateViewWrapper {
-
- protected NotificationMediaViewWrapper(Context ctx, View view) {
- super(ctx, view);
- }
-
- @Override
- public void setDark(boolean dark, boolean fade, long delay) {
-
- // Only update the large icon, because the rest is already inverted.
- setPictureGrayscale(dark, fade, delay);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
index af6ccd88b95b..f20ccd5b92c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
@@ -20,24 +20,32 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
+import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.ImageView;
+import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
import com.android.systemui.statusbar.phone.NotificationPanelView;
+import java.util.ArrayList;
+
/**
* Wraps a notification view inflated from a template.
*/
@@ -47,69 +55,64 @@ public class NotificationTemplateViewWrapper extends NotificationViewWrapper {
private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
0, PorterDuff.Mode.SRC_ATOP);
private final int mIconDarkAlpha;
- private final int mIconBackgroundDarkColor;
+ private final int mIconDarkColor = 0xffffffff;
+ private final int mDarkProgressTint = 0xffffffff;
private final Interpolator mLinearOutSlowInInterpolator;
- private int mIconBackgroundColor;
+ private int mColor;
private ViewInvertHelper mInvertHelper;
private ImageView mIcon;
protected ImageView mPicture;
- /** Whether the icon needs to be forced grayscale when in dark mode. */
- private boolean mIconForceGraysaleWhenDark;
private TextView mSubText;
- private TextView mInfoText;
- private View mProfileBadge;
- private View mThirdLineDivider;
- private View mThirdLine;
+ private View mSubTextDivider;
+ private ImageView mExpandButton;
+ private ViewGroup mNotificationHeader;
+ private ProgressBar mProgressBar;
protected NotificationTemplateViewWrapper(Context ctx, View view) {
super(view);
mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
- mIconBackgroundDarkColor =
- ctx.getColor(R.color.doze_small_icon_background_color);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx,
android.R.interpolator.linear_out_slow_in);
+
resolveViews();
}
private void resolveViews() {
View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
- mInvertHelper = mainColumn != null
- ? new ViewInvertHelper(mainColumn, NotificationPanelView.DOZE_ANIMATION_DURATION)
- : null;
- ImageView largeIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
- ImageView rightIcon = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
- mIcon = resolveIcon(largeIcon, rightIcon);
- mPicture = resolvePicture(largeIcon);
- mIconBackgroundColor = resolveBackgroundColor(mIcon);
- mSubText = (TextView) mView.findViewById(com.android.internal.R.id.text);
- mInfoText = (TextView) mView.findViewById(com.android.internal.R.id.info);
- mProfileBadge = mView.findViewById(com.android.internal.R.id.profile_badge_line3);
- mThirdLineDivider = mView.findViewById(com.android.internal.R.id.overflow_divider);
- mThirdLine = mView.findViewById(com.android.internal.R.id.line3);
-
- // If the icon already has a color filter, we assume that we already forced the icon to be
- // white when we created the notification.
- final Drawable iconDrawable = mIcon != null ? mIcon.getDrawable() : null;
- mIconForceGraysaleWhenDark = iconDrawable != null && iconDrawable.getColorFilter() != null;
- }
-
- private ImageView resolveIcon(ImageView largeIcon, ImageView rightIcon) {
- return largeIcon != null && largeIcon.getBackground() != null ? largeIcon
- : rightIcon != null && rightIcon.getVisibility() == View.VISIBLE ? rightIcon
- : null;
- }
-
- private ImageView resolvePicture(ImageView largeIcon) {
- return largeIcon != null && largeIcon.getBackground() == null
- ? largeIcon
- : null;
+ mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
+ mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
+ mSubText = (TextView) mView.findViewById(com.android.internal.R.id.header_sub_text);
+ mSubTextDivider = mView.findViewById(com.android.internal.R.id.sub_text_divider);
+ mExpandButton = (ImageView) mView.findViewById(com.android.internal.R.id.expand_button);
+ mColor = resolveColor(mExpandButton);
+ final View progress = mView.findViewById(com.android.internal.R.id.progress);
+ if (progress instanceof ProgressBar) {
+ mProgressBar = (ProgressBar) progress;
+ } else {
+ // It's still a viewstub
+ mProgressBar = null;
+ }
+ mNotificationHeader = (ViewGroup) mView.findViewById(
+ com.android.internal.R.id.notification_header);
+ ArrayList<View> viewsToInvert = new ArrayList<>();
+ if (mainColumn != null) {
+ viewsToInvert.add(mainColumn);
+ }
+ for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
+ View child = mNotificationHeader.getChildAt(i);
+ if (child != mIcon) {
+ viewsToInvert.add(child);
+ }
+ }
+ mInvertHelper = new ViewInvertHelper(viewsToInvert,
+ NotificationPanelView.DOZE_ANIMATION_DURATION);
}
- private int resolveBackgroundColor(ImageView icon) {
- if (icon != null && icon.getBackground() != null) {
- ColorFilter filter = icon.getBackground().getColorFilter();
+ private int resolveColor(ImageView icon) {
+ if (icon != null && icon.getDrawable() != null) {
+ ColorFilter filter = icon.getDrawable().getColorFilter();
if (filter instanceof PorterDuffColorFilter) {
return ((PorterDuffColorFilter) filter).getColor();
}
@@ -138,18 +141,43 @@ public class NotificationTemplateViewWrapper extends NotificationViewWrapper {
if (fade) {
fadeIconColorFilter(mIcon, dark, delay);
fadeIconAlpha(mIcon, dark, delay);
- if (!mIconForceGraysaleWhenDark) {
- fadeGrayscale(mIcon, dark, delay);
- }
} else {
updateIconColorFilter(mIcon, dark);
updateIconAlpha(mIcon, dark);
- if (!mIconForceGraysaleWhenDark) {
- updateGrayscale(mIcon, dark);
- }
}
}
setPictureGrayscale(dark, fade, delay);
+ setProgressBarDark(dark, fade, delay);
+ }
+
+ private void setProgressBarDark(boolean dark, boolean fade, long delay) {
+ if (mProgressBar != null) {
+ if (fade) {
+ fadeProgressDark(mProgressBar, dark, delay);
+ } else {
+ updateProgressDark(mProgressBar, dark);
+ }
+ }
+ }
+
+ private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) {
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (float) animation.getAnimatedValue();
+ updateProgressDark(target, t);
+ }
+ }, dark, delay, null /* listener */);
+ }
+
+ private void updateProgressDark(ProgressBar target, float intensity) {
+ int color = interpolateColor(mColor, mDarkProgressTint, intensity);
+ target.getIndeterminateDrawable().mutate().setTint(color);
+ target.getProgressDrawable().mutate().setTint(color);
+ }
+
+ private void updateProgressDark(ProgressBar target, boolean dark) {
+ updateProgressDark(target, dark ? 1f : 0f);
}
protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
@@ -218,14 +246,14 @@ public class NotificationTemplateViewWrapper extends NotificationViewWrapper {
}
private void updateIconColorFilter(ImageView target, float intensity) {
- int color = interpolateColor(mIconBackgroundColor, mIconBackgroundDarkColor, intensity);
+ int color = interpolateColor(mColor, mIconDarkColor, intensity);
mIconColorFilter.setColor(color);
- Drawable background = target.getBackground();
+ Drawable iconDrawable = target.getDrawable();
- // The background might be null for legacy notifications. Also, the notification might have
- // been modified during the animation, so background might be null here.
- if (background != null) {
- background.mutate().setColorFilter(mIconColorFilter);
+ // Also, the notification might have been modified during the animation, so background
+ // might be null here.
+ if (iconDrawable != null) {
+ iconDrawable.mutate().setColorFilter(mIconColorFilter);
}
}
@@ -250,33 +278,17 @@ public class NotificationTemplateViewWrapper extends NotificationViewWrapper {
boolean subTextAvailable = !TextUtils.isEmpty(mSubText.getText());
if (visible && subTextAvailable) {
mSubText.setVisibility(View.VISIBLE);
+ mSubTextDivider.setVisibility(View.VISIBLE);
} else {
mSubText.setVisibility(View.GONE);
+ mSubTextDivider.setVisibility(View.GONE);
}
- // TODO: figure out what to do with the number (same place as contentInfo)
- // work profile badge. For now we hide it since it looks nicer.
- boolean infoAvailable = !TextUtils.isEmpty(mInfoText.getText());
- if (visible && infoAvailable) {
- mInfoText.setVisibility(View.VISIBLE);
- } else {
- mInfoText.setVisibility(View.GONE);
- }
- boolean showThirdLine = (visible && (infoAvailable || subTextAvailable))
- || mProfileBadge.getVisibility() == View.VISIBLE;
- if (mThirdLineDivider != null) {
- if (showThirdLine) {
- mThirdLineDivider.setVisibility(View.VISIBLE);
- } else {
- mThirdLineDivider.setVisibility(View.GONE);
- }
- }
- if (mThirdLine != null) {
- if (showThirdLine) {
- mThirdLine.setVisibility(View.VISIBLE);
- } else {
- mThirdLine.setVisibility(View.GONE);
- }
- }
+ }
+
+ @Override
+ public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
+ mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
+ mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
private void updateGrayscaleMatrix(float intensity) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
index 9bce54858c29..e83ecb7436ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
@@ -34,15 +34,7 @@ public abstract class NotificationViewWrapper {
public static NotificationViewWrapper wrap(Context ctx, View v) {
if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
- if (TAG_BIG_MEDIA_NARROW.equals(v.getTag())) {
- return new NotificationBigMediaNarrowViewWrapper(ctx, v);
- } else if (TAG_MEDIA.equals(v.getTag())) {
- return new NotificationMediaViewWrapper(ctx, v);
- } else if (TAG_BIG_PICTURE.equals(v.getTag())) {
- return new NotificationBigMediaNarrowViewWrapper(ctx, v);
- } else {
- return new NotificationTemplateViewWrapper(ctx, v);
- }
+ return new NotificationTemplateViewWrapper(ctx, v);
} else {
return new NotificationCustomViewWrapper(v);
}
@@ -83,4 +75,12 @@ public abstract class NotificationViewWrapper {
public void setSubTextVisible(boolean visible) {
mSubTextVisible = visible;
}
+
+ /**
+ * Update the appearance of the expand button.
+ *
+ * @param expandable should this view be expandable
+ * @param onClickListener the listener to invoke when the expand affordance is clicked on
+ */
+ public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderView.java
deleted file mode 100644
index ec26cc4a19ef..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderView.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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.notification;
-
-import android.annotation.Nullable;
-import android.app.Notification;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.service.notification.StatusBarNotification;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.util.NotificationColorUtil;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.BaseStatusBar;
-import com.android.systemui.statusbar.ExpandableNotificationRow;
-import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-
-import java.util.List;
-
-/**
- * A header for a notification view
- */
-public class NotificationHeaderView extends FrameLayout {
-
- private static final int DEFAULT_ICON_TINT_COLOR = 0xff616161;
- private final NotificationColorUtil mNotificationColorUtil;
- private NotificationData.Entry mNotificationEntry;
- private ImageView mIconView;
- private TextView mAppName;
- private TextView mPostTime;
- private TextView mChildCount;
- private TextView mSubTextDivider;
- private TextView mSubText;
- private NotificationGroupManager mGroupManager;
- private ImageButton mExpandButton;
-
- public NotificationHeaderView(Context context) {
- this(context, null);
- }
-
- public NotificationHeaderView(Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- mNotificationColorUtil = NotificationColorUtil.getInstance(context);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mIconView = (ImageView) findViewById(R.id.header_notification_icon);
- mAppName = (TextView) findViewById(R.id.app_name_text);
- mSubTextDivider = (TextView) findViewById(R.id.app_title_sub_text_divider);
- mSubText = (TextView) findViewById(R.id.title_sub_text);
- mPostTime = (TextView) findViewById(R.id.post_time);
- mChildCount = (TextView) findViewById(R.id.number_of_children);
- mExpandButton = (ImageButton) findViewById(R.id.notification_expand_button);
- mExpandButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mGroupManager.toggleGroupExpansion(mNotificationEntry.notification);
- }
- });
- }
-
- public void bind(NotificationData.Entry notificationEntry) {
- mNotificationEntry = notificationEntry;
- StatusBarNotification sbn = notificationEntry.notification;
- int notificationColor = getNotificationColor(sbn);
- bindIcon(notificationColor);
- bindNumber(notificationColor);
- bindAppName(sbn);
- bindSubText();
- bindTime(sbn);
- bindExpandButton(sbn);
- }
-
- private void bindExpandButton(StatusBarNotification sbn) {
- boolean summaryOfGroup = mGroupManager.isSummaryOfGroup(sbn);
- mExpandButton.setVisibility(summaryOfGroup ? VISIBLE : GONE);
- }
-
- private void bindSubText() {
- List<ExpandableNotificationRow> notificationChildren =
- mNotificationEntry.row.getNotificationChildren();
- CharSequence subText = null;
- if (notificationChildren != null) {
- for (int i = 0; i < notificationChildren.size(); i++) {
- ExpandableNotificationRow row = notificationChildren.get(i);
- CharSequence rowSubText = row.getSubText();
- if (TextUtils.isEmpty(rowSubText)
- || (subText != null && !subText.equals(rowSubText))) {
- // The children don't have a common subText
- subText = null;
- break;
- } else if (subText == null) {
- subText = rowSubText;
- }
- }
- };
- setSubText(subText);
- }
-
- private void setSubText(CharSequence subText) {
- boolean goneInHeader = TextUtils.isEmpty(subText);
- if (goneInHeader) {
- mSubText.setVisibility(GONE);
- mSubTextDivider.setVisibility(GONE);
- } else {
- mSubText.setVisibility(VISIBLE);
- mSubText.setText(subText);
- mSubTextDivider.setVisibility(VISIBLE);
- }
- List<ExpandableNotificationRow> notificationChildren =
- mNotificationEntry.row.getNotificationChildren();
- if (notificationChildren != null) {
- for (int i = 0; i < notificationChildren.size(); i++) {
- ExpandableNotificationRow row = notificationChildren.get(i);
- row.setContentSubTextVisible(goneInHeader);
- }
- }
- }
-
- private int getNotificationColor(StatusBarNotification sbn) {
- int color = sbn.getNotification().color;
- if (color == Notification.COLOR_DEFAULT) {
- return DEFAULT_ICON_TINT_COLOR;
- }
- return color;
- }
-
- private void bindNumber(int notificationColor) {
- int numberOfNotificationChildren = mNotificationEntry.row.getNumberOfNotificationChildren();
- boolean visible = numberOfNotificationChildren > 0;
- if (visible) {
- mChildCount.setText("(" + numberOfNotificationChildren + ")");
- mChildCount.setTextColor(notificationColor);
- mChildCount.setVisibility(VISIBLE);
- } else {
- mChildCount.setVisibility(GONE);
- }
- }
-
- private void bindTime(StatusBarNotification sbn) {
-
- }
-
- private void bindIcon(int notificationColor) {
- Drawable icon = mNotificationEntry.icon.getDrawable().getConstantState()
- .newDrawable(getResources()).mutate();
- mIconView.setImageDrawable(icon);
- if (NotificationUtils.isGrayscale(mIconView, mNotificationColorUtil)) {
- icon.setTint(notificationColor);
- }
- }
-
- private void bindAppName(StatusBarNotification sbn) {
- PackageManager pmUser = BaseStatusBar.getPackageManagerForUser(getContext(),
- sbn.getUser().getIdentifier());
- final String pkg = sbn.getPackageName();
- String appname = pkg;
- try {
- final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
- PackageManager.GET_UNINSTALLED_PACKAGES
- | PackageManager.GET_DISABLED_COMPONENTS);
- if (info != null) {
- appname = String.valueOf(pmUser.getApplicationLabel(info));
-
- }
- } catch (PackageManager.NameNotFoundException e) {
- // app is gone, just show package name
- }
- mAppName.setText(appname);
- }
-
- public void setGroupManager(NotificationGroupManager groupManager) {
- mGroupManager = groupManager;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index a15d35e5918d..784cb48f8707 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -96,6 +96,11 @@ public class KeyguardClockPositionAlgorithm {
mEmptyDragAmount = emptyDragAmount;
}
+ public float getMinStackScrollerPadding(int height, int keyguardStatusHeight) {
+ return mClockYFractionMin * height + keyguardStatusHeight / 2
+ + mClockNotificationsMarginMin;
+ }
+
public void run(Result result) {
int y = getClockY() - mKeyguardStatusHeight / 2;
float clockAdjustment = getClockYExpansionAdjustment();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index fbe97300aa3d..08da0d3344c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -92,8 +92,6 @@ public class NotificationGroupManager {
if (group.children.isEmpty()) {
if (group.summary == null) {
mGroupMap.remove(groupKey);
- } else if (!group.expanded) {
- group.summary.row.updateNotificationHeader();
}
}
}
@@ -109,9 +107,6 @@ public class NotificationGroupManager {
}
if (notif.isGroupChild()) {
group.children.add(added);
- if (group.summary != null && group.children.size() == 1 && !group.expanded) {
- group.summary.row.updateNotificationHeader();
- }
} else {
group.summary = added;
group.expanded = added.row.areChildrenExpanded();
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 79701ed1f035..c0e3ec1f6889 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -44,6 +44,7 @@ import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
+
import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.DejankUtils;
@@ -448,6 +449,36 @@ public class NotificationPanelView extends PanelView implements
requestScrollerTopPaddingUpdate(animate);
}
+ /**
+ * @param maximum the maximum to return at most
+ * @return the maximum keyguard notifications that can fit on the screen
+ */
+ public int computeMaxKeyguardNotifications(int maximum) {
+ float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding(getHeight(),
+ mKeyguardStatusView.getHeight());
+ int keyguardPadding = getResources().getDimensionPixelSize(
+ R.dimen.notification_padding_dimmed);
+ final int overflowheight = getResources().getDimensionPixelSize(
+ R.dimen.notification_summary_height);
+ float bottomStackSize = mNotificationStackScroller.getKeyguardBottomStackSize();
+ float availableSpace = mNotificationStackScroller.getHeight() - minPadding - overflowheight
+ - bottomStackSize;
+ int count = 0;
+ for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
+ ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ availableSpace -= child.getMinHeight() + keyguardPadding;
+ if (availableSpace >= 0 && count < maximum) {
+ count++;
+ } else {
+ return count;
+ }
+ }
+ return count;
+ }
+
private void startClockAnimation(int y) {
if (mClockAnimationTarget == y) {
return;
@@ -1663,7 +1694,7 @@ public class NotificationPanelView extends PanelView implements
}
private float getFadeoutAlpha() {
- float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight())
+ float alpha = (getNotificationsTopY() + mNotificationStackScroller.getFirstItemMinHeight())
/ (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize()
- mNotificationStackScroller.getCollapseSecondCardPadding());
alpha = Math.max(0, Math.min(alpha, 1));
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 fafedc33f0b1..21d803dd4e25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -36,9 +36,7 @@ import android.widget.FrameLayout;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.R;
-import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.classifier.HumanInteractionClassifier;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState;
@@ -891,6 +889,7 @@ public abstract class PanelView extends FrameLayout {
if (mStatusBar.getStatusBarWindow().getHeight()
!= mStatusBar.getStatusBarHeight()) {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ mStatusBar.onPanelExpandedAndLayouted();
if (animate) {
mBar.startOpeningPanel(PanelView.this);
notifyExpandingStarted();
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 6d8e650eb8ac..05660ecb383b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -110,8 +110,8 @@ import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -171,7 +171,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
@@ -338,7 +337,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private long mKeyguardFadingAwayDelay;
private long mKeyguardFadingAwayDuration;
- int mKeyguardMaxNotificationCount;
+ int mMaxAllowedKeyguardNotifications;
boolean mExpandedVisible;
@@ -428,6 +427,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private boolean mAutohideSuspended;
private int mStatusBarMode;
private int mNavigationBarMode;
+ private int mMaxKeyguardNotifications;
private ViewMediatorCallback mKeyguardViewMediatorCallback;
private ScrimController mScrimController;
@@ -3129,10 +3129,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mNaturalBarHeight = res.getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height);
+ mRowMinHeightLegacy = res.getDimensionPixelSize(R.dimen.notification_min_height_legacy);
mRowMinHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
mRowMaxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
- mKeyguardMaxNotificationCount = res.getInteger(R.integer.keyguard_max_notification_count);
+ mMaxAllowedKeyguardNotifications = res.getInteger(R.integer.keyguard_max_notification_count);
if (DEBUG) Log.v(TAG, "updateResources");
}
@@ -3913,8 +3914,18 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
@Override
- protected int getMaxKeyguardNotifications() {
- return mKeyguardMaxNotificationCount;
+ protected int getMaxKeyguardNotifications(boolean recompute) {
+ if (recompute) {
+ mMaxKeyguardNotifications = Math.max(1,
+ mNotificationPanel.computeMaxKeyguardNotifications(
+ mMaxAllowedKeyguardNotifications));
+ return mMaxKeyguardNotifications;
+ }
+ return mMaxKeyguardNotifications;
+ }
+
+ public int getMaxKeyguardNotifications() {
+ return getMaxKeyguardNotifications(false /* recompute */);
}
public NavigationBarView getNavigationBarView() {
@@ -3944,11 +3955,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
@Override
public void onDragDownReset() {
mStackScroller.setDimmed(true /* dimmed */, true /* animated */);
+ mStackScroller.resetScrollPosition();
}
@Override
- public void onThresholdReached() {
- mStackScroller.setDimmed(false /* dimmed */, true /* animate */);
+ public void onCrossedThreshold(boolean above) {
+ mStackScroller.setDimmed(!above /* dimmed */, true /* animate */);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 93a8fd8ed343..0917528696b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -110,7 +110,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
// When enabling location, a user consent dialog will pop up, and the
// setting won't be fully enabled until the user accepts the agreement.
int mode = enabled
- ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY : Settings.Secure.LOCATION_MODE_OFF;
+ ? Settings.Secure.LOCATION_MODE_PREVIOUS : Settings.Secure.LOCATION_MODE_OFF;
// QuickSettings always runs as the owner, so specifically set the settings
// for the current foreground user.
return Settings.Secure
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index c9ebc843768f..77a9871b6727 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -78,7 +78,7 @@ public class NotificationChildrenContainer extends ViewGroup {
mNotificationAppearDistance = getResources().getDimensionPixelSize(
R.dimen.notification_appear_distance);
mNotificationHeaderHeight = getResources().getDimensionPixelSize(
- R.dimen.notification_header_height);
+ com.android.internal.R.dimen.notification_header_height);
mHeaderTopPaddingSubstraction = 2 * getResources().getDisplayMetrics().density;
mCollapsedBottompadding = 10 * getResources().getDisplayMetrics().density;
mHybridViewManager = new HybridNotificationViewManager(getContext(), this);
@@ -246,7 +246,7 @@ public class NotificationChildrenContainer extends ViewGroup {
* in @param maxAllowedVisibleChildren
*/
private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
- int intrinsicHeight = 0;
+ int intrinsicHeight = mNotificationHeaderHeight;;
int visibleChildren = 0;
int childCount = mChildren.size();
for (int i = 0; i < childCount; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index aeca97c31376..185d32d5b36a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -272,7 +272,7 @@ public class NotificationStackScrollLayout extends ViewGroup
@Override
protected void onDraw(Canvas canvas) {
if (DEBUG) {
- int y = mCollapsedSize;
+ int y = mTopPadding;
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
y = (int) (getLayoutHeight() - mBottomStackPeekSize
- mBottomStackSlowDownHeight);
@@ -550,8 +550,9 @@ public class NotificationStackScrollLayout extends ViewGroup
return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
}
- public int getItemHeight() {
- return mCollapsedSize;
+ public int getFirstItemMinHeight() {
+ final ExpandableView firstChild = getFirstChildNotGone();
+ return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
}
public int getBottomStackPeekSize() {
@@ -1321,14 +1322,14 @@ public class NotificationStackScrollLayout extends ViewGroup
ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
if (firstChild != null) {
int contentHeight = getContentHeight();
- int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
+ mBottomStackSlowDownHeight);
if (scrollRange > 0) {
- View lastChild = getLastChildNotGone();
+ int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
// We want to at least be able collapse the first item and not ending in a weird
// end state.
- scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize);
+ scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight
+ - firstChild.getMinHeight());
}
}
return scrollRange;
@@ -1337,12 +1338,12 @@ public class NotificationStackScrollLayout extends ViewGroup
/**
* @return the first child which has visibility unequal to GONE
*/
- private View getFirstChildNotGone() {
+ private ExpandableView getFirstChildNotGone() {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
- return child;
+ return (ExpandableView) child;
}
}
return null;
@@ -1504,7 +1505,10 @@ public class NotificationStackScrollLayout extends ViewGroup
}
public int getMinStackHeight() {
- return mCollapsedSize + mBottomStackPeekSize + mCollapseSecondCardPadding;
+ final ExpandableView firstChild = getFirstChildNotGone();
+ final int firstChildMinHeight = firstChild != null ? (int) firstChild.getMinHeight()
+ : mCollapsedSize;
+ return firstChildMinHeight + mBottomStackPeekSize + mCollapseSecondCardPadding;
}
public float getTopPaddingOverflow() {
@@ -1512,7 +1516,10 @@ public class NotificationStackScrollLayout extends ViewGroup
}
public int getPeekHeight() {
- return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize
+ final ExpandableView firstChild = getFirstChildNotGone();
+ final int firstChildMinHeight = firstChild != null ? (int) firstChild.getMinHeight()
+ : mCollapsedSize;
+ return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize
+ mCollapseSecondCardPadding;
}
@@ -2266,6 +2273,11 @@ public class NotificationStackScrollLayout extends ViewGroup
return Math.max(emptyMargin, 0);
}
+ public float getKeyguardBottomStackSize() {
+ return mBottomStackPeekSize + getResources().getDimensionPixelSize(
+ R.dimen.bottom_stack_slow_down_length);
+ }
+
public void onExpansionStarted() {
mIsExpansionChanging = true;
mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 65ca95bfd8d4..953f28795126 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -42,7 +42,7 @@ public class StackScrollAlgorithm {
private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
private static final int MAX_ITEMS_IN_TOP_STACK = 3;
- public static final float DIMMED_SCALE = 0.95f;
+ public static final float DIMMED_SCALE = 0.98f;
private int mPaddingBetweenElements;
private int mCollapsedSize;
@@ -72,6 +72,7 @@ public class StackScrollAlgorithm {
private int mMaxNotificationHeight;
private boolean mScaleDimmed;
private HeadsUpManager mHeadsUpManager;
+ private int mFirstChildMinHeight;
public StackScrollAlgorithm(Context context) {
initConstants(context);
@@ -155,7 +156,7 @@ public class StackScrollAlgorithm {
// Due to the overScroller, the stackscroller can have negative scroll state. This is
// already accounted for by the top padding and doesn't need an additional adaption
scrollY = Math.max(0, scrollY);
- algorithmState.scrollY = (int) (scrollY + mCollapsedSize + bottomOverScroll);
+ algorithmState.scrollY = (int) (scrollY + mFirstChildMinHeight + bottomOverScroll);
updateVisibleChildren(resultState, algorithmState);
@@ -424,7 +425,8 @@ public class StackScrollAlgorithm {
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
+ mPaddingBetweenElements;
- float scrollOffset = yPositionInScrollView - algorithmState.scrollY + mCollapsedSize;
+ float scrollOffset = yPositionInScrollView - algorithmState.scrollY +
+ mFirstChildMinHeight;
if (i == algorithmState.lastTopStackIndex + 1) {
// Normally the position of this child is the position in the regular scrollview,
@@ -451,10 +453,10 @@ public class StackScrollAlgorithm {
>= bottomStackStart && !mIsExpansionChanging && i != 0 && mIsSmallScreen) {
// we just collapse this element slightly
int newSize = (int) Math.max(bottomStackStart - mPaddingBetweenElements -
- childViewState.yTranslation, mCollapsedSize);
+ childViewState.yTranslation, child.getMinHeight());
childViewState.height = newSize;
updateStateForChildTransitioningInBottom(algorithmState, bottomStackStart,
- bottomPeekStart, childViewState.yTranslation, childViewState,
+ child, childViewState.yTranslation, childViewState,
childHeight);
}
clampPositionToBottomStackStart(childViewState, childViewState.height,
@@ -471,7 +473,7 @@ public class StackScrollAlgorithm {
// According to the regular scroll view we are currently translating out of /
// into the bottom of the screen
updateStateForChildTransitioningInBottom(algorithmState,
- bottomStackStart, bottomPeekStart, currentYPosition,
+ bottomStackStart, child, currentYPosition,
childViewState, childHeight);
}
} else {
@@ -484,12 +486,13 @@ public class StackScrollAlgorithm {
// The first card is always rendered.
if (i == 0) {
childViewState.alpha = 1.0f;
- childViewState.yTranslation = Math.max(mCollapsedSize - algorithmState.scrollY, 0);
+ childViewState.yTranslation = Math.max(
+ mFirstChildMinHeight - algorithmState.scrollY, 0);
if (childViewState.yTranslation + childViewState.height
> bottomPeekStart - mCollapseSecondCardPadding) {
childViewState.height = (int) Math.max(
bottomPeekStart - mCollapseSecondCardPadding
- - childViewState.yTranslation, mCollapsedSize);
+ - childViewState.yTranslation, mFirstChildMinHeight);
}
childViewState.location = StackViewState.LOCATION_FIRST_CARD;
}
@@ -501,7 +504,8 @@ public class StackScrollAlgorithm {
if (ambientState.isShadeExpanded() && topHeadsUpEntry != null
&& child != topHeadsUpEntry) {
- childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() - mCollapsedSize;
+ childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() -
+ mFirstChildMinHeight;
}
childViewState.yTranslation += ambientState.getTopPadding()
+ ambientState.getStackTranslation();
@@ -528,7 +532,7 @@ public class StackScrollAlgorithm {
boolean isTopEntry = topHeadsUpEntry == row;
if (mIsExpanded) {
if (isTopEntry) {
- childState.height += row.getHeadsUpHeight() - mCollapsedSize;
+ childState.height += row.getHeadsUpHeight() - mFirstChildMinHeight;
}
childState.height = Math.max(childState.height, row.getHeadsUpHeight());
// Ensure that the heads up is always visible even when scrolled off from the bottom
@@ -588,7 +592,7 @@ public class StackScrollAlgorithm {
private void clampPositionToTopStackEnd(StackViewState childViewState,
int childHeight) {
childViewState.yTranslation = Math.max(childViewState.yTranslation,
- mCollapsedSize - childHeight);
+ mFirstChildMinHeight - childHeight);
}
private int getMaxAllowedChildHeight(View child, AmbientState ambientState) {
@@ -597,7 +601,7 @@ public class StackScrollAlgorithm {
if (ambientState == null && row.isHeadsUp()
|| ambientState != null && ambientState.getTopHeadsUpEntry() == child) {
int extraSize = row.getIntrinsicHeight() - row.getHeadsUpHeight();
- return mCollapsedSize + extraSize;
+ return mFirstChildMinHeight + extraSize;
}
return row.getIntrinsicHeight();
} else if (child instanceof ExpandableView) {
@@ -608,7 +612,7 @@ public class StackScrollAlgorithm {
}
private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
- float transitioningPositionStart, float bottomPeakStart, float currentYPosition,
+ float transitioningPositionStart, ExpandableView child, float currentYPosition,
StackViewState childViewState, int childHeight) {
// This is the transitioning element on top of bottom stack, calculate how far we are in.
@@ -620,9 +624,10 @@ public class StackScrollAlgorithm {
float offset = mBottomStackIndentationFunctor.getValue(algorithmState.partialInBottom);
algorithmState.itemsInBottomStack += algorithmState.partialInBottom;
int newHeight = childHeight;
- if (childHeight > mCollapsedSize && mIsSmallScreen) {
+ if (childHeight > child.getMinHeight() && mIsSmallScreen) {
newHeight = (int) Math.max(Math.min(transitioningPositionStart + offset -
- mPaddingBetweenElements - currentYPosition, childHeight), mCollapsedSize);
+ mPaddingBetweenElements - currentYPosition, childHeight),
+ child.getMinHeight());
childViewState.height = newHeight;
}
childViewState.yTranslation = transitioningPositionStart + offset - newHeight
@@ -689,7 +694,7 @@ public class StackScrollAlgorithm {
numItemsBefore = algorithmState.itemsInTopStack - i;
}
// The end position of the current child
- float currentChildEndY = mCollapsedSize + mTopStackTotalSize
+ float currentChildEndY = mFirstChildMinHeight + mTopStackTotalSize
- mTopStackIndentationFunctor.getValue(numItemsBefore);
childViewState.yTranslation = currentChildEndY - childHeight;
}
@@ -701,7 +706,7 @@ public class StackScrollAlgorithm {
// We are hidden behind the top card and faded out, so we can hide ourselves.
childViewState.alpha = 0.0f;
}
- childViewState.yTranslation = mCollapsedSize - childHeight;
+ childViewState.yTranslation = mFirstChildMinHeight - childHeight;
childViewState.location = StackViewState.LOCATION_TOP_STACK_HIDDEN;
}
@@ -730,7 +735,7 @@ public class StackScrollAlgorithm {
+ childHeight
+ mPaddingBetweenElements;
if (yPositionInScrollView < algorithmState.scrollY) {
- if (i == 0 && algorithmState.scrollY <= mCollapsedSize) {
+ if (i == 0 && algorithmState.scrollY <= mFirstChildMinHeight) {
// The starting position of the bottom stack peek
int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize -
@@ -740,14 +745,14 @@ public class StackScrollAlgorithm {
? mFirstChildMaxHeight
: childHeight;
childViewState.height = (int) Math.max(Math.min(bottomPeekStart, maxHeight),
- mCollapsedSize);
+ mFirstChildMinHeight);
algorithmState.itemsInTopStack = 1.0f;
} else if (yPositionInScrollViewAfterElement < algorithmState.scrollY) {
// According to the regular scroll view we are fully off screen
algorithmState.itemsInTopStack += 1.0f;
if (i == 0) {
- childViewState.height = mCollapsedSize;
+ childViewState.height = child.getMinHeight();
}
} else {
// According to the regular scroll view we are partially off screen
@@ -766,8 +771,8 @@ public class StackScrollAlgorithm {
// If it is expanded we have to collapse it to a new size
float newSize = yPositionInScrollViewAfterElement
- mPaddingBetweenElements
- - algorithmState.scrollY + mCollapsedSize;
- newSize = Math.max(mCollapsedSize, newSize);
+ - algorithmState.scrollY + mFirstChildMinHeight;
+ newSize = Math.max(mFirstChildMinHeight, newSize);
algorithmState.itemsInTopStack = 1.0f;
childViewState.height = (int) newSize;
}
@@ -809,7 +814,7 @@ public class StackScrollAlgorithm {
// Interpolate the index from 0 to 2 while the second item is
// translating in.
stackIndex -= 1.0f;
- if (algorithmState.scrollY > mCollapsedSize) {
+ if (algorithmState.scrollY > mFirstChildMinHeight) {
// Since there is a shadow treshhold, we cant just interpolate from 0 to
// 2 but we interpolate from 0.1f to 2.0f when scrolled in. The jump in
@@ -863,7 +868,7 @@ public class StackScrollAlgorithm {
ExpandableNotificationRow row =
(ExpandableNotificationRow) mFirstChildWhileExpanding;
if (row.isHeadsUp()) {
- mFirstChildMaxHeight += mCollapsedSize - row.getHeadsUpHeight();
+ mFirstChildMaxHeight += mFirstChildMinHeight - row.getHeadsUpHeight();
}
}
} else {
@@ -927,7 +932,11 @@ public class StackScrollAlgorithm {
this.mIsExpanded = isExpanded;
}
- public void notifyChildrenChanged(final ViewGroup hostView) {
+ public void notifyChildrenChanged(final NotificationStackScrollLayout hostView) {
+ int firstItemMinHeight = hostView.getFirstItemMinHeight();
+ if (firstItemMinHeight != mFirstChildMinHeight) {
+ mFirstChildMinHeight = firstItemMinHeight;
+ }
if (mIsExpansionChanging) {
hostView.post(new Runnable() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index bbe5dd90d11f..39a2986f434f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -130,7 +130,7 @@ public class TvStatusBar extends BaseStatusBar {
}
@Override
- protected int getMaxKeyguardNotifications() {
+ protected int getMaxKeyguardNotifications(boolean recompute) {
return 0;
}
diff --git a/preloaded-classes b/preloaded-classes
index 79c0957e48ae..e44b25e9674e 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -1765,6 +1765,7 @@ android.util.StateSet
android.util.SuperNotCalledException
android.util.TypedValue
android.util.Xml
+android.util.jar.StrictJarFile
android.view.AbsSavedState
android.view.AbsSavedState$1
android.view.AbsSavedState$2
@@ -3361,9 +3362,6 @@ java.util.jar.Attributes$Name
java.util.jar.JarEntry
java.util.jar.JarFile
java.util.jar.JarFile$JarFileEnumerator
-java.util.jar.Manifest
-java.util.jar.ManifestReader
-java.util.jar.StrictJarFile
java.util.logging.ConsoleHandler
java.util.logging.ErrorManager
java.util.logging.Filter
@@ -3824,4 +3822,11 @@ org.xml.sax.helpers.DefaultHandler
org.xmlpull.v1.XmlPullParser
org.xmlpull.v1.XmlPullParserException
org.xmlpull.v1.XmlSerializer
+<<<<<<< HEAD
sun.misc.Unsafe
+=======
+<<<<<<< HEAD
+=======
+sun.misc.Unsafe
+>>>>>>> 631d21f... Move StrictJarFile from libcore to framework
+>>>>>>> 43ea2cc... DO NOT MERGE Move StrictJarFile from libcore to framework
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3d358ec5e8eb..11fdbb5886e1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -433,7 +433,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
AccessibilityEvent.obtain(event)).sendToTarget();
}
event.recycle();
- getUserStateLocked(resolvedUserId).mHandledFeedbackTypes = 0;
}
return (OWN_PROCESS_ID != Binder.getCallingPid());
}
@@ -1051,9 +1050,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
Service service = state.mBoundServices.get(i);
if (service.mIsDefault == isDefault) {
- if (canDispatchEventToServiceLocked(service, event,
- state.mHandledFeedbackTypes)) {
- state.mHandledFeedbackTypes |= service.mFeedbackType;
+ if (canDispatchEventToServiceLocked(service, event)) {
service.notifyAccessibilityEvent(event);
}
}
@@ -1088,19 +1085,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
/**
* Determines if given event can be dispatched to a service based on the package of the
- * event source and already notified services for that event type. Specifically, a
- * service is notified if it is interested in events from the package and no other service
- * providing the same feedback type has been notified. Exception are services the
- * provide generic feedback (feedback type left as a safety net for unforeseen feedback
- * types) which are always notified.
+ * event source. Specifically, a service is notified if it is interested in events from the
+ * package.
*
* @param service The potential receiver.
* @param event The event.
- * @param handledFeedbackTypes The feedback types for which services have been notified.
* @return True if the listener should be notified, false otherwise.
*/
- private boolean canDispatchEventToServiceLocked(Service service, AccessibilityEvent event,
- int handledFeedbackTypes) {
+ private boolean canDispatchEventToServiceLocked(Service service, AccessibilityEvent event) {
if (!service.canReceiveEventsLocked()) {
return false;
@@ -1121,15 +1113,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
String packageName = (event.getPackageName() != null)
? event.getPackageName().toString() : null;
- if (packageNames.isEmpty() || packageNames.contains(packageName)) {
- int feedbackType = service.mFeedbackType;
- if ((handledFeedbackTypes & feedbackType) != feedbackType
- || feedbackType == AccessibilityServiceInfo.FEEDBACK_GENERIC) {
- return true;
- }
- }
-
- return false;
+ return (packageNames.isEmpty() || packageNames.contains(packageName));
}
private void unbindAllServicesLocked(UserState userState) {
@@ -3886,8 +3870,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
public final Set<ComponentName> mTouchExplorationGrantedServices =
new HashSet<>();
- public int mHandledFeedbackTypes = 0;
-
public int mLastSentClientState = -1;
public boolean mIsAccessibilityEnabled;
@@ -3950,7 +3932,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
mBindingServices.clear();
// Clear event management state.
- mHandledFeedbackTypes = 0;
mLastSentClientState = -1;
// Clear state persisted in settings.
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 78a4e359ad24..42dd9a8da29c 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -70,6 +70,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
+import android.hardware.input.InputManagerInternal;
import android.inputmethodservice.InputMethodService;
import android.net.Uri;
import android.os.Binder;
@@ -1930,6 +1931,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
+ private void notifyInputMethodSubtypeChanged(final int userId,
+ @Nullable final InputMethodInfo inputMethodInfo,
+ @Nullable final InputMethodSubtype subtype) {
+ final InputManagerInternal inputManagerInternal =
+ LocalServices.getService(InputManagerInternal.class);
+ if (inputManagerInternal != null) {
+ inputManagerInternal.onInputMethodSubtypeChanged(userId, inputMethodInfo, subtype);
+ }
+ }
+
/* package */ void setInputMethodLocked(String id, int subtypeId) {
InputMethodInfo info = mMethodMap.get(id);
if (info == null) {
@@ -1972,8 +1983,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mCurMethod.changeInputMethodSubtype(newSubtype);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call changeInputMethodSubtype");
+ return;
}
}
+ notifyInputMethodSubtypeChanged(mSettings.getCurrentUserId(), info, newSubtype);
}
return;
}
@@ -1999,6 +2012,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
} finally {
Binder.restoreCallingIdentity(ident);
}
+
+ notifyInputMethodSubtypeChanged(mSettings.getCurrentUserId(), info,
+ getCurrentInputMethodSubtypeLocked());
}
@Override
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index da8152861edf..d6c6f13b606a 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -16,6 +16,8 @@
package com.android.server;
+import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
import android.app.admin.DevicePolicyManager;
import android.app.backup.BackupManager;
import android.app.trust.IStrongAuthTracker;
@@ -388,6 +390,13 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
+ private void unlockUser(int userId, byte[] token) {
+ try {
+ ActivityManagerNative.getDefault().unlockUser(userId, token);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
private byte[] getCurrentHandle(int userId) {
CredentialHash credential;
@@ -612,6 +621,7 @@ public class LockSettingsService extends ILockSettings.Stub {
byte[] hash = credentialUtil.toHash(credential, userId);
if (Arrays.equals(hash, storedHash.hash)) {
unlockKeystore(credentialUtil.adjustForKeystore(credential), userId);
+ unlockUser(userId, null);
// migrate credential to GateKeeper
credentialUtil.setCredential(credential, null, userId);
if (!hasChallenge) {
@@ -664,6 +674,7 @@ public class LockSettingsService extends ILockSettings.Stub {
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
// credential has matched
unlockKeystore(credential, userId);
+ unlockUser(userId, null);
if (shouldReEnroll) {
credentialUtil.setCredential(credential, credential, userId);
}
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index a32bb2f213bd..bd43a71173d7 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -781,6 +781,7 @@ class MountService extends IMountService.Stub
}
private void handleSystemReady() {
+ initIfReadyAndConnected();
resetIfReadyAndConnected();
// Start scheduling nominally-daily fstrim operations
@@ -828,6 +829,22 @@ class MountService extends IMountService.Stub
mVolumes.put(internal.id, internal);
}
+ private void initIfReadyAndConnected() {
+ Slog.d(TAG, "Thinking about init, mSystemReady=" + mSystemReady
+ + ", mDaemonConnected=" + mDaemonConnected);
+ if (mSystemReady && mDaemonConnected && StorageManager.isFileBasedEncryptionEnabled()) {
+ final List<UserInfo> users = mContext.getSystemService(UserManager.class)
+ .getUsers();
+ for (UserInfo user : users) {
+ try {
+ mCryptConnector.execute("cryptfs", "lock_user_key", user.id);
+ } catch (NativeDaemonConnectorException e) {
+ Slog.w(TAG, "Failed to init vold", e);
+ }
+ }
+ }
+ }
+
private void resetIfReadyAndConnected() {
Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady
+ ", mDaemonConnected=" + mDaemonConnected);
@@ -928,6 +945,7 @@ class MountService extends IMountService.Stub
}
private void handleDaemonConnected() {
+ initIfReadyAndConnected();
resetIfReadyAndConnected();
/*
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 557b3869a886..99470c808b8f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3356,10 +3356,8 @@ public final class ActivityManagerService extends ActivityManagerNative
try {
try {
- if (AppGlobals.getPackageManager().isPackageFrozen(app.info.packageName)) {
- // This is caught below as if we had failed to fork zygote
- throw new RuntimeException("Package " + app.info.packageName + " is frozen!");
- }
+ final int userId = UserHandle.getUserId(app.uid);
+ AppGlobals.getPackageManager().checkPackageStartable(app.info.packageName, userId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index c3bd982ec8a2..94a200a11c8f 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -184,7 +184,6 @@ public final class ActivityStackSupervisor implements DisplayListener {
static final int CONTAINER_CALLBACK_TASK_LIST_EMPTY = FIRST_SUPERVISOR_STACK_MSG + 11;
static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12;
static final int SHOW_LOCK_TASK_ESCAPE_MESSAGE_MSG = FIRST_SUPERVISOR_STACK_MSG + 13;
- static final int SHOW_NON_RESIZEABLE_DOCK_TOAST = FIRST_SUPERVISOR_STACK_MSG + 14;
private static final String VIRTUAL_DISPLAY_BASE_NAME = "ActivityViewVirtualDisplay";
@@ -2592,6 +2591,11 @@ public final class ActivityStackSupervisor implements DisplayListener {
mService.setFocusedActivityLocked(r, "startedActivity");
}
updateUserStackLocked(r.userId, targetStack);
+
+ if (!r.task.mResizeable && isStackDockedInEffect(targetStack.mStackId)) {
+ showNonResizeableDockToast(r.task.taskId);
+ }
+
return ActivityManager.START_SUCCESS;
}
@@ -3114,7 +3118,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
for (int i = 0; i < count; i++) {
moveTaskToStackLocked(tasks.get(i).taskId,
FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS, "resizeStack",
- true /* animate */);
+ false /* animate */);
}
// stack shouldn't contain anymore activities, so nothing to resume.
@@ -3376,6 +3380,10 @@ public final class ActivityStackSupervisor implements DisplayListener {
final ActivityStack stack = moveTaskToStackUncheckedLocked(
task, stackId, toTop, forceFocus, "moveTaskToStack:" + reason);
+ if (!animate) {
+ stack.mNoAnimActivities.add(topActivity);
+ }
+
// Make sure the task has the appropriate bounds/size for the stack it is in.
if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
@@ -3393,7 +3401,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
resumeTopActivitiesLocked();
if (!task.mResizeable && isStackDockedInEffect(stackId)) {
- showNonResizeableDockToast();
+ showNonResizeableDockToast(taskId);
}
}
@@ -4327,8 +4335,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
- void showNonResizeableDockToast() {
- mHandler.sendEmptyMessage(SHOW_NON_RESIZEABLE_DOCK_TOAST);
+ private void showNonResizeableDockToast(int taskId) {
+ mWindowManager.scheduleShowNonResizeableDockToast(taskId);
}
void showLockTaskToast() {
@@ -4643,13 +4651,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
}
} break;
- case SHOW_NON_RESIZEABLE_DOCK_TOAST: {
- final Toast toast = Toast.makeText(
- mService.mContext,
- mService.mContext.getString(R.string.dock_non_resizeble_text),
- Toast.LENGTH_LONG);
- toast.show();
- } break;
+
}
}
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b30905e4d590..9c2914976bac 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -624,6 +624,23 @@ final class UserController {
throw new SecurityException(msg);
}
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ return unlockUserCleared(userId, token);
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
+
+ boolean unlockUserCleared(final int userId, byte[] token) {
+ synchronized (mService) {
+ final UserState uss = mStartedUsers.get(userId);
+ if (uss.unlocked) {
+ // Bail early when already unlocked
+ return true;
+ }
+ }
+
final UserInfo userInfo = getUserInfo(userId);
final IMountService mountService = IMountService.Stub
.asInterface(ServiceManager.getService("mount"));
@@ -631,7 +648,7 @@ final class UserController {
mountService.unlockUserKey(userId, userInfo.serialNumber, token);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to unlock: " + e.getMessage());
- throw e.rethrowAsRuntimeException();
+ return false;
}
synchronized (mService) {
@@ -639,6 +656,11 @@ final class UserController {
updateUserUnlockedState(uss);
}
+ final Intent intent = new Intent(Intent.ACTION_USER_UNLOCKED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ mService.broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
+ AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID, userId);
+
return true;
}
@@ -1011,7 +1033,7 @@ final class UserController {
if ((flags & ActivityManager.FLAG_OR_STOPPED) != 0) {
return true;
}
- if ((flags & ActivityManager.FLAG_WITH_AMNESIA) != 0) {
+ if ((flags & ActivityManager.FLAG_AND_LOCKED) != 0) {
// If user is currently locked, we fall through to default "running"
// behavior below
if (state.unlocked) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 81ae8acca30c..ae8fca83754c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.input;
+import android.annotation.Nullable;
import android.view.Display;
import com.android.internal.os.SomeArgs;
import com.android.internal.R;
@@ -83,6 +84,9 @@ import android.view.PointerIcon;
import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
import android.widget.Toast;
import java.io.File;
@@ -116,6 +120,7 @@ public class InputManagerService extends IInputManager.Stub
private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
private static final int MSG_RELOAD_DEVICE_ALIASES = 5;
private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 6;
+ private static final int MSG_INPUT_METHOD_SUBTYPE_CHANGED = 7;
// Pointer to native input manager service object.
private final long mPtr;
@@ -1206,6 +1211,15 @@ public class InputManagerService extends IInputManager.Stub
}
}
+ // Must be called on handler.
+ private void handleSwitchInputMethodSubtype(int userId,
+ @Nullable InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype subtype) {
+ if (DEBUG) {
+ Slog.i(TAG, "InputMethodSubtype changed: userId=" + userId
+ + " ime=" + inputMethodInfo + " subtype=" + subtype);
+ }
+ }
+
public void switchKeyboardLayout(int deviceId, int direction) {
mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
}
@@ -1757,12 +1771,22 @@ public class InputManagerService extends IInputManager.Stub
case MSG_RELOAD_DEVICE_ALIASES:
reloadDeviceAliases();
break;
- case MSG_DELIVER_TABLET_MODE_CHANGED:
+ case MSG_DELIVER_TABLET_MODE_CHANGED: {
SomeArgs args = (SomeArgs) msg.obj;
long whenNanos = (args.argi1 & 0xFFFFFFFFl) | ((long) args.argi2 << 32);
boolean inTabletMode = (boolean) args.arg1;
deliverTabletModeChanged(whenNanos, inTabletMode);
break;
+ }
+ case MSG_INPUT_METHOD_SUBTYPE_CHANGED: {
+ final int userId = msg.arg1;
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final InputMethodInfo inputMethodInfo = (InputMethodInfo) args.arg1;
+ final InputMethodSubtype subtype = (InputMethodSubtype) args.arg2;
+ args.recycle();
+ handleSwitchInputMethodSubtype(userId, inputMethodInfo, subtype);
+ break;
+ }
}
}
}
@@ -1920,5 +1944,15 @@ public class InputManagerService extends IInputManager.Stub
public void setInteractive(boolean interactive) {
nativeSetInteractive(mPtr, interactive);
}
+
+ @Override
+ public void onInputMethodSubtypeChanged(int userId,
+ @Nullable InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype subtype) {
+ final SomeArgs someArgs = SomeArgs.obtain();
+ someArgs.arg1 = inputMethodInfo;
+ someArgs.arg2 = subtype;
+ mHandler.obtainMessage(MSG_INPUT_METHOD_SUBTYPE_CHANGED, userId, 0, someArgs)
+ .sendToTarget();
+ }
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index f92f631a06d0..246da2e10de5 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -63,7 +63,7 @@ import java.util.ArrayList;
*/
public class MediaSessionRecord implements IBinder.DeathRecipient {
private static final String TAG = "MediaSessionRecord";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
/**
* The length of time a session will still be considered active after
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 90dd10eae69a..d5c31134a4eb 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -287,6 +287,9 @@ public class MediaSessionService extends SystemService implements Monitor {
* 6. We need to tell the session to do any final cleanup (onDestroy)
*/
private void destroySessionLocked(MediaSessionRecord session) {
+ if (DEBUG) {
+ Log.d(TAG, "Destroying session : " + session.toString());
+ }
int userId = session.getUserId();
UserRecord user = mUserRecords.get(userId);
if (user != null) {
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 611718ebacb5..61c320bced3c 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -16,13 +16,17 @@
package com.android.server.media;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.PlaybackState;
import android.media.session.MediaSession;
+import android.os.RemoteException;
import android.os.UserHandle;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
/**
* Keeps track of media sessions and their priority for notifications, media
@@ -61,6 +65,36 @@ public class MediaSessionStack {
private ArrayList<MediaSessionRecord> mCachedTransportControlList;
/**
+ * Checks if a media session is created from the most recent app.
+ *
+ * @param record A media session record to be examined.
+ * @return true if the media session's package name equals to the most recent app, false
+ * otherwise.
+ */
+ private static boolean isFromMostRecentApp(MediaSessionRecord record) {
+ if (ActivityManager.getCurrentUser() != record.getUserId()) {
+ return false;
+ }
+ try {
+ List<ActivityManager.RecentTaskInfo> tasks =
+ ActivityManagerNative.getDefault().getRecentTasks(1,
+ ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
+ ActivityManager.RECENT_IGNORE_UNAVAILABLE |
+ ActivityManager.RECENT_INCLUDE_PROFILES |
+ ActivityManager.RECENT_WITH_EXCLUDED, record.getUserId());
+ if (tasks != null && !tasks.isEmpty()) {
+ ActivityManager.RecentTaskInfo recentTask = tasks.get(0);
+ if (recentTask.baseIntent != null)
+ return recentTask.baseIntent.getComponent().getPackageName()
+ .equals(record.getPackageName());
+ }
+ } catch (RemoteException e) {
+ return false;
+ }
+ return false;
+ }
+
+ /**
* Add a record to the priority tracker.
*
* @param record The record to add.
@@ -68,7 +102,9 @@ public class MediaSessionStack {
public void addSession(MediaSessionRecord record) {
mSessions.add(record);
clearCache();
- mLastInterestingRecord = record;
+ if (isFromMostRecentApp(record)) {
+ mLastInterestingRecord = record;
+ }
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4bc79cb13fce..c7d1171cd4c2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2800,15 +2800,24 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
- public boolean isPackageFrozen(String packageName) {
+ public void checkPackageStartable(String packageName, int userId) {
+ final boolean userKeyUnlocked = isUserKeyUnlocked(userId);
+
synchronized (mPackages) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
- if (ps != null) {
- return ps.frozen;
+ if (ps == null) {
+ throw new SecurityException("Package " + packageName + " was not found!");
+ }
+
+ if (ps.frozen) {
+ throw new SecurityException("Package " + packageName + " is currently frozen!");
+ }
+
+ if (!userKeyUnlocked && !(ps.pkg.applicationInfo.isEncryptionAware()
+ || ps.pkg.applicationInfo.isPartiallyEncryptionAware())) {
+ throw new SecurityException("Package " + packageName + " is not encryption aware!");
}
}
- Slog.w(TAG, "Package " + packageName + " is missing; assuming frozen");
- return true;
}
@Override
@@ -3143,29 +3152,36 @@ public class PackageManagerService extends IPackageManager.Stub {
}
/**
- * Augment the given flags depending on current user running state. This is
- * purposefully done before acquiring {@link #mPackages} lock.
+ * Return if the user key is currently unlocked.
*/
- private int augmentFlagsForUser(int flags, int userId) {
+ private boolean isUserKeyUnlocked(int userId) {
if (StorageManager.isFileBasedEncryptionEnabled()) {
final IMountService mount = IMountService.Stub
.asInterface(ServiceManager.getService("mount"));
if (mount == null) {
- // We must be early in boot, so the best we can do is assume the
- // user is fully running.
- Slog.w(TAG, "Early during boot, assuming not encrypted");
- return flags;
+ Slog.w(TAG, "Early during boot, assuming locked");
+ return false;
}
final long token = Binder.clearCallingIdentity();
try {
- if (!mount.isUserKeyUnlocked(userId)) {
- flags |= PackageManager.MATCH_ENCRYPTION_AWARE_ONLY;
- }
+ return mount.isUserKeyUnlocked(userId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
Binder.restoreCallingIdentity(token);
}
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Augment the given flags depending on current user running state. This is
+ * purposefully done before acquiring {@link #mPackages} lock.
+ */
+ private int augmentFlagsForUser(int flags, int userId) {
+ if (!isUserKeyUnlocked(userId)) {
+ flags |= PackageManager.MATCH_ENCRYPTION_AWARE_ONLY;
}
return flags;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 424b90234ca2..da10a94215dc 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -652,6 +652,7 @@ public class UserManagerService extends IUserManager.Stub {
private void initDefaultGuestRestrictions() {
synchronized (mGuestRestrictions) {
if (mGuestRestrictions.isEmpty()) {
+ mGuestRestrictions.putBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
}
@@ -1652,6 +1653,11 @@ public class UserManagerService extends IUserManager.Stub {
}
updateUserIds();
Bundle restrictions = new Bundle();
+ if (isGuest) {
+ synchronized (mGuestRestrictions) {
+ restrictions.putAll(mGuestRestrictions);
+ }
+ }
synchronized (mRestrictionsLock) {
mBaseUserRestrictions.append(userId, restrictions);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ae6874f720b1..639753a13b16 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2533,7 +2533,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
} else if (win.getAttrs().type == TYPE_DOCK_DIVIDER) {
- if (transit == TRANSIT_ENTER) {
+ if (transit == TRANSIT_ENTER || transit == TRANSIT_SHOW) {
return R.anim.fade_in;
} else if (transit == TRANSIT_EXIT) {
return R.anim.fade_out;
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 6b6246760b39..df8d5d6f6e9b 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -18,7 +18,11 @@ package com.android.server.wm;
import android.content.Context;
import android.graphics.Rect;
+import android.os.RemoteException;
import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.IDockDividerVisibilityListener;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.view.WindowManager.DOCKED_BOTTOM;
@@ -40,6 +44,8 @@ public class DockedStackDividerController {
private WindowState mWindow;
private final Rect mTmpRect = new Rect();
private final Rect mLastRect = new Rect();
+ private IDockDividerVisibilityListener mListener;
+ private boolean mLastVisibility = false;
DockedStackDividerController(Context context, DisplayContent displayContent) {
mDisplayContent = displayContent;
@@ -67,12 +73,21 @@ public class DockedStackDividerController {
}
void reevaluateVisibility() {
- if (mWindow == null) return;
+ if (mWindow == null) {
+ return;
+ }
TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID);
- if (stack != null && stack.isVisibleLocked()) {
- mWindow.showLw(true /* doAnimation */);
- } else {
- mWindow.hideLw(true /* doAnimation */);
+ final boolean visible = stack != null && stack.isVisibleLocked();
+ if (mLastVisibility == visible) {
+ return;
+ }
+ mLastVisibility = visible;
+ if (mListener != null) {
+ try {
+ mListener.onDockDividerVisibilityChanged(visible);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "visibility call failed: " + e);
+ }
}
}
@@ -110,4 +125,11 @@ public class DockedStackDividerController {
}
mLastRect.set(frame);
}
+
+ public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+ if (mListener != null && listener != null) {
+ throw new IllegalStateException("Dock divider visibility listener already set!");
+ }
+ mListener = listener;
+ }
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 74e8e53f87e4..15a3c4867fc5 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -26,6 +26,11 @@ import static com.android.server.wm.WindowManagerService.TAG;
import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
+import static com.android.server.wm.WindowManagerService.H.SHOW_NON_RESIZEABLE_DOCK_TOAST;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
import android.app.ActivityManager.StackId;
import android.content.res.Configuration;
@@ -76,6 +81,11 @@ class Task implements DimLayer.DimLayerUser {
// Whether the task is resizeable
private boolean mResizeable;
+ // Whether we need to show toast about the app being non-resizeable when it becomes visible.
+ // This flag is set when a non-resizeable task is docked (or side-by-side). It's cleared
+ // after we show the toast.
+ private boolean mShowNonResizeableDockToast;
+
// Whether the task is currently being drag-resized
private boolean mDragResizing;
@@ -92,6 +102,41 @@ class Task implements DimLayer.DimLayerUser {
return mStack.getDisplayContent();
}
+ void setShowNonResizeableDockToast() {
+ mShowNonResizeableDockToast = true;
+ }
+
+ void scheduleShowNonResizeableDockToastIfNeeded() {
+ if (!mShowNonResizeableDockToast) {
+ return;
+ }
+ final DisplayContent displayContent = mStack.getDisplayContent();
+ // If docked stack is not yet visible, we don't want to show the toast yet,
+ // since we need the visible rect of the docked task to position the toast.
+ if (displayContent == null || displayContent.getDockedStackLocked() == null) {
+ return;
+ }
+
+ mShowNonResizeableDockToast = false;
+
+ final int dockSide = mStack.getDockSide();
+ int xOffset = 0;
+ int yOffset = 0;
+ if (dockSide != DOCKED_INVALID) {
+ mStack.getBounds(mTmpRect);
+ displayContent.getLogicalDisplayRect(mTmpRect2);
+
+ if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) {
+ xOffset = mTmpRect.centerX() - mTmpRect2.centerX();
+ } else if (dockSide == DOCKED_TOP) {
+ // We don't adjust for DOCKED_BOTTOM case since it's already at the bottom.
+ yOffset = mTmpRect2.bottom - mTmpRect.bottom;
+ }
+ mService.mH.obtainMessage(
+ SHOW_NON_RESIZEABLE_DOCK_TOAST, xOffset, yOffset).sendToTarget();
+ }
+ }
+
void addAppToken(int addPos, AppWindowToken wtoken) {
final int lastPos = mAppTokens.size();
if (addPos >= lastPos) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 87deaa4d930c..97889756b8c7 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -658,10 +658,11 @@ public class TaskStack implements DimLayer.DimLayerUser {
}
/**
- * For docked workspace provides information which side of the screen was the dock anchored.
+ * For docked workspace (or workspace that's side-by-side to the docked), provides
+ * information which side of the screen was the dock anchored.
*/
int getDockSide() {
- if (mStackId != DOCKED_STACK_ID) {
+ if (mStackId != DOCKED_STACK_ID && !StackId.isResizeableByDockedStack(mStackId)) {
return DOCKED_INVALID;
}
if (mDisplayContent == null) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ac90daf97b90..dab195f5abba 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -121,6 +121,7 @@ import android.view.DropPermissionHolder;
import android.view.Gravity;
import android.view.IApplicationToken;
import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.IDockDividerVisibilityListener;
import android.view.IInputFilter;
import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
@@ -149,8 +150,10 @@ import android.view.WindowManagerInternal;
import android.view.WindowManagerPolicy;
import android.view.WindowManagerPolicy.PointerEventListener;
import android.view.animation.Animation;
+import android.widget.Toast;
import com.android.internal.app.IAssistScreenshotReceiver;
+import com.android.internal.R;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
@@ -2689,8 +2692,7 @@ public class WindowManagerService extends IWindowManager.Stub
// need to see about starting one.
final boolean notExitingOrAnimating =
!win.mExiting && !win.isAnimatingWithSavedSurface();
- result |= notExitingOrAnimating
- ? RELAYOUT_RES_SURFACE_CHANGED : 0;
+ result |= notExitingOrAnimating ? RELAYOUT_RES_SURFACE_CHANGED : 0;
if (notExitingOrAnimating) {
focusMayChange = tryStartingAnimation(win, winAnimator, isDefaultDisplay,
focusMayChange);
@@ -3075,7 +3077,7 @@ public class WindowManagerService extends IWindowManager.Stub
// TODO:
}
- boolean checkCallingPermission(String permission, String func) {
+ private boolean checkCallingPermission(String permission, String func) {
// Quick check: if the calling permission is me, it's all okay.
if (Binder.getCallingPid() == Process.myPid()) {
return true;
@@ -4787,6 +4789,17 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ public void scheduleShowNonResizeableDockToast(int taskId) {
+ synchronized (mWindowMap) {
+ Task task = mTaskIdToTask.get(taskId);
+ if (task == null) {
+ if (DEBUG_STACK) Slog.i(TAG, "scheduleShowToast: could not find taskId=" + taskId);
+ return;
+ }
+ task.setShowNonResizeableDockToast();
+ }
+ }
+
public void getStackBounds(int stackId, Rect bounds) {
synchronized (mWindowMap) {
final TaskStack stack = mStackIdToStack.get(stackId);
@@ -7452,6 +7465,7 @@ public class WindowManagerService extends IWindowManager.Stub
public static final int RESIZE_TASK = 44;
public static final int TWO_FINGER_SCROLL_START = 45;
+ public static final int SHOW_NON_RESIZEABLE_DOCK_TOAST = 46;
/**
* Used to denote that an integer field in a message will not be used.
@@ -8024,6 +8038,17 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
break;
+ case SHOW_NON_RESIZEABLE_DOCK_TOAST: {
+ final Toast toast = Toast.makeText(mContext,
+ mContext.getString(R.string.dock_non_resizeble_text),
+ Toast.LENGTH_LONG);
+ final int gravity = toast.getGravity();
+ final int xOffset = toast.getXOffset() + msg.arg1;
+ final int yOffset = toast.getYOffset() + msg.arg2;
+ toast.setGravity(gravity, xOffset, yOffset);
+ toast.show();
+ }
+ break;
}
if (DEBUG_WINDOW_TRACE) {
Slog.v(TAG, "handleMessage: exit");
@@ -10211,6 +10236,29 @@ public class WindowManagerService extends IWindowManager.Stub
mDestroySurface.add(win);
}
+ @Override
+ public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+ if (!checkCallingPermission(android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS,
+ "registerDockDividerVisibilityListener()")) {
+ return;
+ }
+ // TODO(multi-display): The listener is registered on the default display only.
+ final DockedStackDividerController controller =
+ getDefaultDisplayContentLocked().getDockedDividerController();
+ controller.registerDockDividerVisibilityListener(listener);
+ try {
+ listener.asBinder().linkToDeath(new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ getDefaultDisplayContentLocked().getDockedDividerController()
+ .registerDockDividerVisibilityListener(null);
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ controller.registerDockDividerVisibilityListener(null);
+ }
+ }
+
private final class LocalService extends WindowManagerInternal {
@Override
public void requestTraversalFromDisplayManager() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 064b4128a2ce..29cadf3e67c4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -730,7 +730,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mVisibleFrame.set(mContentFrame);
mStableFrame.set(mContentFrame);
} else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
- if (isVisibleLw()) {
+ if (isVisibleLw() || mWinAnimator.isAnimating()) {
// We don't adjust the dock divider frame for reasons other than performance. The
// real reason is that if it gets adjusted before it is shown for the first time,
// it would get size (0, 0). This causes a problem when we finally show the dock
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 93c2ff608442..132b1b60ec4b 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1474,6 +1474,11 @@ class WindowStateAnimator {
}
mWin.mAppToken.updateReportedVisibilityLocked();
}
+
+ final Task task = mWin.getTask();
+ if (task != null) {
+ task.scheduleShowNonResizeableDockToastIfNeeded();
+ }
return true;
}
return false;
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 4821678373d9..c967c2ba6e21 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -147,6 +147,11 @@ public class MockContext extends Context {
}
@Override
+ public SharedPreferences getSharedPreferences(File file, int mode) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public FileInputStream openFileInput(String name) throws FileNotFoundException {
throw new UnsupportedOperationException();
}
diff --git a/tests/NetworkSecurityConfigTest/res/xml/override_dedup.xml b/tests/NetworkSecurityConfigTest/res/xml/override_dedup.xml
new file mode 100644
index 000000000000..5ba56754e768
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/override_dedup.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <!-- Entry with a bad pin. Connections to this will only succeed if overridePins is set. -->
+ <domain-config>
+ <domain>android.com</domain>
+ <pin-set>
+ <pin digest="SHA-256">aaaaaaaaIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ </pin-set>
+ <trust-anchors>
+ <certificates src="system" overridePins="false" />
+ </trust-anchors>
+ </domain-config>
+ <!-- override that contains all of the system CA store. This should completely override the
+ anchors in the domain config-above with ones that have overridePins set. -->
+ <debug-overrides>
+ <trust-anchors>
+ <certificates src="system" />
+ </trust-anchors>
+ </debug-overrides>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java
index 92eadc06cd49..69b2a9d55642 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java
@@ -19,15 +19,29 @@ package android.security.net.config;
import java.util.Set;
import java.security.cert.X509Certificate;
+import com.android.org.conscrypt.TrustedCertificateIndex;
+
/** @hide */
public class TestCertificateSource implements CertificateSource {
private final Set<X509Certificate> mCertificates;
+ private final TrustedCertificateIndex mIndex = new TrustedCertificateIndex();
public TestCertificateSource(Set<X509Certificate> certificates) {
mCertificates = certificates;
+ for (X509Certificate cert : certificates) {
+ mIndex.index(cert);
+ }
}
public Set<X509Certificate> getCertificates() {
return mCertificates;
}
+
+ public X509Certificate findBySubjectAndPublicKey(X509Certificate cert) {
+ java.security.cert.TrustAnchor anchor = mIndex.findBySubjectAndPublicKey(cert);
+ if (anchor == null) {
+ return null;
+ }
+ return anchor.getTrustedCert();
+ }
}
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
index c6f3680f455c..998bb681dd24 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -402,4 +402,22 @@ public class XmlConfigTests extends AndroidTestCase {
context.init(null, tms, null);
TestUtils.assertConnectionSucceeds(context, "android.com" , 443);
}
+
+ public void testDebugDedup() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_dedup, true);
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ // Check android.com.
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+ PinSet pinSet = config.getPins();
+ assertFalse(pinSet.pins.isEmpty());
+ // Check that all TrustAnchors come from the override pins debug source.
+ for (TrustAnchor anchor : config.getTrustAnchors()) {
+ assertTrue(anchor.overridesPins);
+ }
+ // Try connections.
+ SSLContext context = TestUtils.getSSLContext(source);
+ TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+ TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
+ }
}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index e22e76de66c2..fb0fe38da1ff 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1537,12 +1537,20 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil
std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue();
while (!workQueue.empty()) {
CompileResourceWorkItem& workItem = workQueue.front();
- err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file, &table, xmlFlags);
+ int xmlCompilationFlags = xmlFlags | XML_COMPILE_PARSE_VALUES
+ | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS;
+ if (!workItem.needsCompiling) {
+ xmlCompilationFlags &= ~XML_COMPILE_ASSIGN_ATTRIBUTE_IDS;
+ xmlCompilationFlags &= ~XML_COMPILE_PARSE_VALUES;
+ }
+ err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.xmlRoot,
+ workItem.file, &table, xmlCompilationFlags);
+
if (err == NO_ERROR) {
assets->addResource(workItem.resPath.getPathLeaf(),
- workItem.resPath,
- workItem.file,
- workItem.file->getResourceType());
+ workItem.resPath,
+ workItem.file,
+ workItem.file->getResourceType());
} else {
hasErrors = true;
}
@@ -1737,9 +1745,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil
manifestFile->getGroupEntry(),
manifestFile->getResourceType());
err = compileXmlFile(bundle, assets, String16(), manifestFile,
- outManifestFile, &table,
- XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
- | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES);
+ outManifestFile, &table, XML_COMPILE_STANDARD_RESOURCE & ~XML_COMPILE_STRIP_COMMENTS);
if (err < NO_ERROR) {
return err;
}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index d5a09d817b1e..0e470d924f5a 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -88,8 +88,11 @@ status_t compileXmlFile(const Bundle* bundle,
root->setUTF8(true);
}
- bool hasErrors = false;
+ if (table->processBundleFormat(bundle, resourceName, target, root) != NO_ERROR) {
+ return UNKNOWN_ERROR;
+ }
+ bool hasErrors = false;
if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
status_t err = root->assignResourceIds(assets, table);
if (err != NO_ERROR) {
@@ -97,9 +100,11 @@ status_t compileXmlFile(const Bundle* bundle,
}
}
- status_t err = root->parseValues(assets, table);
- if (err != NO_ERROR) {
- hasErrors = true;
+ if ((options&XML_COMPILE_PARSE_VALUES) != 0) {
+ status_t err = root->parseValues(assets, table);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
}
if (hasErrors) {
@@ -114,7 +119,7 @@ status_t compileXmlFile(const Bundle* bundle,
printf("Input XML Resource:\n");
root->print();
}
- err = root->flatten(target,
+ status_t err = root->flatten(target,
(options&XML_COMPILE_STRIP_COMMENTS) != 0,
(options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
if (err != NO_ERROR) {
@@ -4755,9 +4760,9 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle,
newConfig.sdkVersion = sdkVersionToGenerate;
sp<AaptFile> newFile = new AaptFile(target->getSourceFile(),
AaptGroupEntry(newConfig), target->getResourceType());
- String8 resPath = String8::format("res/%s/%s",
+ String8 resPath = String8::format("res/%s/%s.xml",
newFile->getGroupEntry().toDirName(target->getResourceType()).string(),
- target->getSourceFile().getPathLeaf().string());
+ String8(resourceName).string());
resPath.convertToResPath();
// Add a resource table entry.
@@ -4784,9 +4789,11 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle,
item.resourceName = resourceName;
item.resPath = resPath;
item.file = newFile;
+ item.xmlRoot = newRoot;
+ item.needsCompiling = false; // This step occurs after we parse/assign, so we don't need
+ // to do it again.
mWorkQueue.push(item);
}
-
return NO_ERROR;
}
@@ -4825,3 +4832,226 @@ void ResourceTable::getDensityVaryingResources(
}
}
}
+
+static String16 buildNamespace(const String16& package) {
+ return String16("http://schemas.android.com/apk/res/") + package;
+}
+
+static sp<XMLNode> findOnlyChildElement(const sp<XMLNode>& parent) {
+ const Vector<sp<XMLNode> >& children = parent->getChildren();
+ sp<XMLNode> onlyChild;
+ for (size_t i = 0; i < children.size(); i++) {
+ if (children[i]->getType() != XMLNode::TYPE_CDATA) {
+ if (onlyChild != NULL) {
+ return NULL;
+ }
+ onlyChild = children[i];
+ }
+ }
+ return onlyChild;
+}
+
+/**
+ * Detects use of the `bundle' format and extracts nested resources into their own top level
+ * resources. The bundle format looks like this:
+ *
+ * <!-- res/drawable/bundle.xml -->
+ * <animated-vector xmlns:aapt="http://schemas.android.com/aapt">
+ * <aapt:attr name="android:drawable">
+ * <vector android:width="60dp"
+ * android:height="60dp">
+ * <path android:name="v"
+ * android:fillColor="#000000"
+ * android:pathData="M300,70 l 0,-70 70,..." />
+ * </vector>
+ * </aapt:attr>
+ * </animated-vector>
+ *
+ * When AAPT sees the <aapt:attr> tag, it will extract its single element and its children
+ * into a new high-level resource, assigning it a name and ID. Then value of the `name`
+ * attribute must be a resource attribute. That resource attribute is inserted into the parent
+ * with the reference to the extracted resource as the value.
+ *
+ * <!-- res/drawable/bundle.xml -->
+ * <animated-vector android:drawable="@drawable/bundle_1.xml">
+ * </animated-vector>
+ *
+ * <!-- res/drawable/bundle_1.xml -->
+ * <vector android:width="60dp"
+ * android:height="60dp">
+ * <path android:name="v"
+ * android:fillColor="#000000"
+ * android:pathData="M300,70 l 0,-70 70,..." />
+ * </vector>
+ */
+status_t ResourceTable::processBundleFormat(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& target,
+ const sp<XMLNode>& root) {
+ Vector<sp<XMLNode> > namespaces;
+ if (root->getType() == XMLNode::TYPE_NAMESPACE) {
+ namespaces.push(root);
+ }
+ return processBundleFormatImpl(bundle, resourceName, target, root, &namespaces);
+}
+
+status_t ResourceTable::processBundleFormatImpl(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& target,
+ const sp<XMLNode>& parent,
+ Vector<sp<XMLNode> >* namespaces) {
+ const String16 kAaptNamespaceUri16("http://schemas.android.com/aapt");
+ const String16 kName16("name");
+ const String16 kAttr16("attr");
+ const String16 kAssetPackage16(mAssets->getPackage());
+
+ Vector<sp<XMLNode> >& children = parent->getChildren();
+ for (size_t i = 0; i < children.size(); i++) {
+ const sp<XMLNode>& child = children[i];
+
+ if (child->getType() == XMLNode::TYPE_CDATA) {
+ continue;
+ } else if (child->getType() == XMLNode::TYPE_NAMESPACE) {
+ namespaces->push(child);
+ }
+
+ if (child->getElementNamespace() != kAaptNamespaceUri16 ||
+ child->getElementName() != kAttr16) {
+ status_t result = processBundleFormatImpl(bundle, resourceName, target, child,
+ namespaces);
+ if (result != NO_ERROR) {
+ return result;
+ }
+
+ if (child->getType() == XMLNode::TYPE_NAMESPACE) {
+ namespaces->pop();
+ }
+ continue;
+ }
+
+ // This is the <aapt:attr> tag. Look for the 'name' attribute.
+ SourcePos source(child->getFilename(), child->getStartLineNumber());
+
+ sp<XMLNode> nestedRoot = findOnlyChildElement(child);
+ if (nestedRoot == NULL) {
+ source.error("<%s:%s> must have exactly one child element",
+ String8(child->getElementNamespace()).string(),
+ String8(child->getElementName()).string());
+ return UNKNOWN_ERROR;
+ }
+
+ // Find the special attribute 'parent-attr'. This attribute's value contains
+ // the resource attribute for which this element should be assigned in the parent.
+ const XMLNode::attribute_entry* attr = child->getAttribute(String16(), kName16);
+ if (attr == NULL) {
+ source.error("inline resource definition must specify an attribute via 'name'");
+ return UNKNOWN_ERROR;
+ }
+
+ // Parse the attribute name.
+ const char* errorMsg = NULL;
+ String16 attrPackage, attrType, attrName;
+ bool result = ResTable::expandResourceRef(attr->string.string(),
+ attr->string.size(),
+ &attrPackage, &attrType, &attrName,
+ &kAttr16, &kAssetPackage16,
+ &errorMsg, NULL);
+ if (!result) {
+ source.error("invalid attribute name for 'name': %s", errorMsg);
+ return UNKNOWN_ERROR;
+ }
+
+ if (attrType != kAttr16) {
+ // The value of the 'name' attribute must be an attribute reference.
+ source.error("value of 'name' must be an attribute reference.");
+ return UNKNOWN_ERROR;
+ }
+
+ // Generate a name for this nested resource and try to add it to the table.
+ // We do this in a loop because the name may be taken, in which case we will
+ // increment a suffix until we succeed.
+ String8 nestedResourceName;
+ String8 nestedResourcePath;
+ int suffix = 1;
+ while (true) {
+ // This child element will be extracted into its own resource file.
+ // Generate a name and path for it from its parent.
+ nestedResourceName = String8::format("%s_%d",
+ String8(resourceName).string(), suffix++);
+ nestedResourcePath = String8::format("res/%s/%s.xml",
+ target->getGroupEntry().toDirName(target->getResourceType())
+ .string(),
+ nestedResourceName.string());
+
+ // Lookup or create the entry for this name.
+ sp<Entry> entry = getEntry(kAssetPackage16,
+ String16(target->getResourceType()),
+ String16(nestedResourceName),
+ source,
+ false,
+ &target->getGroupEntry().toParams(),
+ true);
+ if (entry == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ if (entry->getType() == Entry::TYPE_UNKNOWN) {
+ // The value for this resource has never been set,
+ // meaning we're good!
+ entry->setItem(source, String16(nestedResourcePath));
+ break;
+ }
+
+ // We failed (name already exists), so try with a different name
+ // (increment the suffix).
+ }
+
+ if (bundle->getVerbose()) {
+ source.printf("generating nested resource %s:%s/%s",
+ mAssets->getPackage().string(), target->getResourceType().string(),
+ nestedResourceName.string());
+ }
+
+ // Build the attribute reference and assign it to the parent.
+ String16 nestedResourceRef = String16(String8::format("@%s:%s/%s",
+ mAssets->getPackage().string(), target->getResourceType().string(),
+ nestedResourceName.string()));
+
+ String16 attrNs = buildNamespace(attrPackage);
+ if (parent->getAttribute(attrNs, attrName) != NULL) {
+ SourcePos(parent->getFilename(), parent->getStartLineNumber())
+ .error("parent of nested resource already defines attribute '%s:%s'",
+ String8(attrPackage).string(), String8(attrName).string());
+ return UNKNOWN_ERROR;
+ }
+
+ // Add the reference to the inline resource.
+ parent->addAttribute(attrNs, attrName, nestedResourceRef);
+
+ // Remove the <aapt:attr> child element from here.
+ children.removeAt(i);
+ i--;
+
+ // Append all namespace declarations that we've seen on this branch in the XML tree
+ // to this resource.
+ // We do this because the order of namespace declarations and prefix usage is determined
+ // by the developer and we do not want to override any decisions. Be conservative.
+ for (size_t nsIndex = namespaces->size(); nsIndex > 0; nsIndex--) {
+ const sp<XMLNode>& ns = namespaces->itemAt(nsIndex - 1);
+ sp<XMLNode> newNs = XMLNode::newNamespace(ns->getFilename(), ns->getNamespacePrefix(),
+ ns->getNamespaceUri());
+ newNs->addChild(nestedRoot);
+ nestedRoot = newNs;
+ }
+
+ // Schedule compilation of the nested resource.
+ CompileResourceWorkItem workItem;
+ workItem.resPath = nestedResourcePath;
+ workItem.resourceName = String16(nestedResourceName);
+ workItem.xmlRoot = nestedRoot;
+ workItem.file = new AaptFile(target->getSourceFile(), target->getGroupEntry(),
+ target->getResourceType());
+ mWorkQueue.push(workItem);
+ }
+ return NO_ERROR;
+}
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index c4bdf09d8b19..4b7b3cdcef2b 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -23,13 +23,14 @@ class ResourceTable;
enum {
XML_COMPILE_STRIP_COMMENTS = 1<<0,
XML_COMPILE_ASSIGN_ATTRIBUTE_IDS = 1<<1,
- XML_COMPILE_COMPACT_WHITESPACE = 1<<2,
- XML_COMPILE_STRIP_WHITESPACE = 1<<3,
- XML_COMPILE_STRIP_RAW_VALUES = 1<<4,
- XML_COMPILE_UTF8 = 1<<5,
+ XML_COMPILE_PARSE_VALUES = 1 << 2,
+ XML_COMPILE_COMPACT_WHITESPACE = 1<<3,
+ XML_COMPILE_STRIP_WHITESPACE = 1<<4,
+ XML_COMPILE_STRIP_RAW_VALUES = 1<<5,
+ XML_COMPILE_UTF8 = 1<<6,
XML_COMPILE_STANDARD_RESOURCE =
- XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
+ XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS | XML_COMPILE_PARSE_VALUES
| XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES
};
@@ -83,6 +84,8 @@ struct CompileResourceWorkItem {
String16 resourceName;
String8 resPath;
sp<AaptFile> file;
+ sp<XMLNode> xmlRoot;
+ bool needsCompiling = true;
};
class ResourceTable : public ResTable::Accessor
@@ -206,6 +209,12 @@ public:
const sp<AaptFile>& file,
const sp<XMLNode>& root);
+ status_t processBundleFormat(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& file,
+ const sp<XMLNode>& parent);
+
+
sp<AaptFile> flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
const bool isBase);
@@ -586,6 +595,11 @@ private:
Res_value* outValue);
int getPublicAttributeSdkLevel(uint32_t attrId) const;
+ status_t processBundleFormatImpl(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& file,
+ const sp<XMLNode>& parent,
+ Vector<sp<XMLNode> >* namespaces);
String16 mAssetsPackage;
PackageType mPackageType;
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index dc08eb806356..5b215daeb494 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -693,6 +693,12 @@ const Vector<sp<XMLNode> >& XMLNode::getChildren() const
return mChildren;
}
+
+Vector<sp<XMLNode> >& XMLNode::getChildren()
+{
+ return mChildren;
+}
+
const String8& XMLNode::getFilename() const
{
return mFilename;
@@ -717,6 +723,18 @@ const XMLNode::attribute_entry* XMLNode::getAttribute(const String16& ns,
return NULL;
}
+bool XMLNode::removeAttribute(const String16& ns, const String16& name)
+{
+ for (size_t i = 0; i < mAttributes.size(); i++) {
+ const attribute_entry& ae(mAttributes.itemAt(i));
+ if (ae.ns == ns && ae.name == name) {
+ removeAttribute(i);
+ return true;
+ }
+ }
+ return false;
+}
+
XMLNode::attribute_entry* XMLNode::editAttribute(const String16& ns,
const String16& name)
{
diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h
index b9e5cd574cdc..749bf9f59bf7 100644
--- a/tools/aapt/XMLNode.h
+++ b/tools/aapt/XMLNode.h
@@ -55,7 +55,7 @@ public:
sp<XMLNode> newCData(const String8& filename) {
return new XMLNode(filename);
}
-
+
enum type {
TYPE_NAMESPACE,
TYPE_ELEMENT,
@@ -70,6 +70,7 @@ public:
const String16& getElementNamespace() const;
const String16& getElementName() const;
const Vector<sp<XMLNode> >& getChildren() const;
+ Vector<sp<XMLNode> >& getChildren();
const String8& getFilename() const;
@@ -97,6 +98,7 @@ public:
const Vector<attribute_entry>& getAttributes() const;
const attribute_entry* getAttribute(const String16& ns, const String16& name) const;
+ bool removeAttribute(const String16& ns, const String16& name);
attribute_entry* editAttribute(const String16& ns, const String16& name);
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 6951ede681e1..eea254bb8fcf 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -541,4 +541,8 @@ public class IWindowManagerImpl implements IWindowManager {
@Override
public void endProlongedAnimations() {
}
+
+ @Override
+ public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 0fcfa7888a21..ff15f3badb38 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1438,6 +1438,14 @@ public final class BridgeContext extends Context {
}
@Override
+ public SharedPreferences getSharedPreferences(File arg0, int arg1) {
+ if (mSharedPreferences == null) {
+ mSharedPreferences = new BridgeSharedPreferences();
+ }
+ return mSharedPreferences;
+ }
+
+ @Override
public Drawable getWallpaper() {
// pass
return null;