summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--api/current.txt17
-rw-r--r--api/system-current.txt18
-rw-r--r--api/test-current.txt19
-rw-r--r--cmds/am/src/com/android/commands/am/Am.java17
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java11
-rw-r--r--core/java/android/app/ActivityManager.java15
-rw-r--r--core/java/android/app/ActivityManagerNative.java7
-rw-r--r--core/java/android/app/AlarmManager.java13
-rw-r--r--core/java/android/app/BackStackRecord.java76
-rw-r--r--core/java/android/app/ContextImpl.java44
-rw-r--r--core/java/android/app/IActivityManager.java2
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/NativeActivity.java4
-rw-r--r--core/java/android/app/Notification.java579
-rw-r--r--core/java/android/app/NotificationManager.java14
-rw-r--r--core/java/android/app/SearchDialog.java6
-rw-r--r--core/java/android/content/ClipDescription.java58
-rw-r--r--core/java/android/content/Context.java40
-rw-r--r--core/java/android/content/ContextWrapper.java5
-rw-r--r--core/java/android/content/Intent.java65
-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/PackageManager.java10
-rw-r--r--core/java/android/content/pm/PackageManagerInternal.java9
-rw-r--r--core/java/android/content/pm/PackageParser.java32
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java4
-rw-r--r--core/java/android/hardware/input/InputManagerInternal.java13
-rw-r--r--core/java/android/net/ConnectivityManager.java26
-rw-r--r--core/java/android/os/AsyncTask.java19
-rw-r--r--core/java/android/os/BatteryStats.java36
-rw-r--r--core/java/android/os/Build.java12
-rw-r--r--core/java/android/os/IProcessInfoService.aidl7
-rw-r--r--core/java/android/os/RemoteCallback.java120
-rw-r--r--core/java/android/os/UserManager.java33
-rw-r--r--core/java/android/print/IPrintSpooler.aidl17
-rw-r--r--core/java/android/print/PrintJobInfo.java124
-rw-r--r--core/java/android/printservice/IPrintServiceClient.aidl16
-rw-r--r--core/java/android/printservice/PrintJob.java81
-rw-r--r--core/java/android/provider/DocumentsContract.java28
-rw-r--r--core/java/android/provider/DocumentsProvider.java170
-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.java28
-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/service/notification/ZenModeConfig.java2
-rw-r--r--core/java/android/speech/tts/FileSynthesisCallback.java22
-rw-r--r--core/java/android/speech/tts/TextToSpeechService.java3
-rw-r--r--core/java/android/text/Hyphenator.java2
-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.java9
-rw-r--r--core/java/android/view/ViewRootImpl.java14
-rw-r--r--core/java/android/webkit/FindActionModeCallback.java3
-rw-r--r--core/java/android/widget/AbsListView.java4
-rw-r--r--core/java/android/widget/Editor.java417
-rw-r--r--core/java/android/widget/RemoteViews.java58
-rw-r--r--core/java/android/widget/SearchView.java10
-rw-r--r--core/java/android/widget/TextView.java11
-rw-r--r--core/java/com/android/internal/logging/MetricsLogger.java1
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java126
-rw-r--r--core/java/com/android/internal/widget/FloatingToolbar.java1430
-rw-r--r--core/java/com/android/internal/widget/ImageFloatingTextView.java89
-rw-r--r--core/java/com/android/internal/widget/PointerLocationView.java2
-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.xml11
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_1_animation.xml43
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_1_pivot_0_animation.xml26
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_1_pivot_animation.xml26
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_2_animation.xml26
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_2_pivot_0_animation.xml26
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_2_pivot_animation.xml26
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_3_animation.xml43
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_3_pivot_0_animation.xml26
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_3_pivot_animation.xml26
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_4_animation.xml33
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_5_animation.xml26
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_6_animation.xml33
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_path_1_animation.xml86
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_path_2_animation.xml86
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_path_3_animation.xml86
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_path_4_animation.xml58
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_path_5_animation.xml72
-rw-r--r--core/res/res/anim/ft_avd_toarrow_rectangle_path_6_animation.xml58
-rw-r--r--core/res/res/anim/ft_avd_tooverflow_rectangle_1_animation.xml33
-rw-r--r--core/res/res/anim/ft_avd_tooverflow_rectangle_1_pivot_animation.xml26
-rw-r--r--core/res/res/anim/ft_avd_tooverflow_rectangle_2_animation.xml26
-rw-r--r--core/res/res/anim/ft_avd_tooverflow_rectangle_2_pivot_animation.xml26
-rw-r--r--core/res/res/anim/ft_avd_tooverflow_rectangle_3_animation.xml33
-rw-r--r--core/res/res/anim/ft_avd_tooverflow_rectangle_3_pivot_animation.xml26
-rw-r--r--core/res/res/anim/ft_avd_tooverflow_rectangle_path_1_animation.xml79
-rw-r--r--core/res/res/anim/ft_avd_tooverflow_rectangle_path_2_animation.xml79
-rw-r--r--core/res/res/anim/ft_avd_tooverflow_rectangle_path_3_animation.xml86
-rw-r--r--core/res/res/drawable/ft_avd_toarrow.xml62
-rw-r--r--core/res/res/drawable/ft_avd_toarrow_animation.xml48
-rw-r--r--core/res/res/drawable/ft_avd_tooverflow.xml68
-rw-r--r--core/res/res/drawable/ft_avd_tooverflow_animation.xml48
-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/interpolator/ft_avd_toarrow_animation_interpolator_0.xml20
-rw-r--r--core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_1.xml20
-rw-r--r--core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_2.xml20
-rw-r--r--core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_3.xml20
-rw-r--r--core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_4.xml20
-rw-r--r--core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_5.xml20
-rw-r--r--core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_6.xml20
-rw-r--r--core/res/res/layout/floating_popup_container.xml7
-rw-r--r--core/res/res/layout/floating_popup_overflow_button.xml28
-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-af/strings.xml2
-rw-r--r--core/res/res/values-am/strings.xml2
-rw-r--r--core/res/res/values-ar/strings.xml5
-rw-r--r--core/res/res/values-az-rAZ/strings.xml1
-rw-r--r--core/res/res/values-bg/strings.xml5
-rw-r--r--core/res/res/values-bn-rBD/strings.xml2
-rw-r--r--core/res/res/values-ca/strings.xml2
-rw-r--r--core/res/res/values-cs/strings.xml4
-rw-r--r--core/res/res/values-da/strings.xml4
-rw-r--r--core/res/res/values-de/strings.xml2
-rw-r--r--core/res/res/values-el/strings.xml5
-rw-r--r--core/res/res/values-en-rAU/strings.xml1
-rw-r--r--core/res/res/values-en-rGB/strings.xml1
-rw-r--r--core/res/res/values-en-rIN/strings.xml1
-rw-r--r--core/res/res/values-es-rUS/strings.xml5
-rw-r--r--core/res/res/values-es/strings.xml7
-rw-r--r--core/res/res/values-et-rEE/strings.xml5
-rw-r--r--core/res/res/values-eu-rES/strings.xml2
-rw-r--r--core/res/res/values-fa/strings.xml15
-rw-r--r--core/res/res/values-fi/strings.xml5
-rw-r--r--core/res/res/values-fr-rCA/strings.xml5
-rw-r--r--core/res/res/values-fr/strings.xml5
-rw-r--r--core/res/res/values-gl-rES/strings.xml5
-rw-r--r--core/res/res/values-gu-rIN/strings.xml5
-rw-r--r--core/res/res/values-hi/strings.xml2
-rw-r--r--core/res/res/values-hr/strings.xml5
-rw-r--r--core/res/res/values-hu/strings.xml2
-rw-r--r--core/res/res/values-hy-rAM/strings.xml5
-rw-r--r--core/res/res/values-in/strings.xml5
-rw-r--r--core/res/res/values-is-rIS/strings.xml5
-rw-r--r--core/res/res/values-it/strings.xml4
-rw-r--r--core/res/res/values-iw/strings.xml5
-rw-r--r--core/res/res/values-ja/strings.xml4
-rw-r--r--core/res/res/values-ka-rGE/strings.xml5
-rw-r--r--core/res/res/values-kk-rKZ/strings.xml4
-rw-r--r--core/res/res/values-km-rKH/strings.xml5
-rw-r--r--core/res/res/values-kn-rIN/strings.xml2
-rw-r--r--core/res/res/values-ko/strings.xml9
-rw-r--r--core/res/res/values-ky-rKG/strings.xml5
-rw-r--r--core/res/res/values-lo-rLA/strings.xml7
-rw-r--r--core/res/res/values-lt/strings.xml5
-rw-r--r--core/res/res/values-lv/strings.xml2
-rw-r--r--core/res/res/values-mk-rMK/strings.xml4
-rw-r--r--core/res/res/values-ml-rIN/strings.xml5
-rw-r--r--core/res/res/values-mn-rMN/strings.xml5
-rw-r--r--core/res/res/values-mr-rIN/strings.xml2
-rw-r--r--core/res/res/values-ms-rMY/strings.xml5
-rw-r--r--core/res/res/values-my-rMM/strings.xml2
-rw-r--r--core/res/res/values-nb/strings.xml7
-rw-r--r--core/res/res/values-ne-rNP/strings.xml7
-rw-r--r--core/res/res/values-nl/strings.xml3
-rw-r--r--core/res/res/values-pa-rIN/strings.xml2
-rw-r--r--core/res/res/values-pl/strings.xml5
-rw-r--r--core/res/res/values-pt-rBR/strings.xml5
-rw-r--r--core/res/res/values-pt-rPT/strings.xml5
-rw-r--r--core/res/res/values-pt/strings.xml5
-rw-r--r--core/res/res/values-ro/strings.xml5
-rw-r--r--core/res/res/values-ru/strings.xml5
-rw-r--r--core/res/res/values-si-rLK/strings.xml5
-rw-r--r--core/res/res/values-sk/strings.xml5
-rw-r--r--core/res/res/values-sl/strings.xml5
-rw-r--r--core/res/res/values-sq-rAL/strings.xml4
-rw-r--r--core/res/res/values-sr/strings.xml5
-rw-r--r--core/res/res/values-sv/strings.xml5
-rw-r--r--core/res/res/values-sw/strings.xml2
-rw-r--r--core/res/res/values-ta-rIN/strings.xml2
-rw-r--r--core/res/res/values-te-rIN/strings.xml2
-rw-r--r--core/res/res/values-th/strings.xml5
-rw-r--r--core/res/res/values-tl/strings.xml5
-rw-r--r--core/res/res/values-tr/strings.xml5
-rw-r--r--core/res/res/values-uk/strings.xml1
-rw-r--r--core/res/res/values-ur-rPK/strings.xml5
-rw-r--r--core/res/res/values-uz-rUZ/strings.xml5
-rw-r--r--core/res/res/values-vi/strings.xml5
-rw-r--r--core/res/res/values-zh-rCN/strings.xml8
-rw-r--r--core/res/res/values-zh-rHK/strings.xml2
-rw-r--r--core/res/res/values-zh-rTW/strings.xml5
-rw-r--r--core/res/res/values-zu/strings.xml1
-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.xml37
-rw-r--r--core/tests/coretests/apks/install_complete_package_info/Android.mk6
-rw-r--r--core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml7
-rw-r--r--core/tests/coretests/src/android/content/pm/PackageManagerTests.java211
-rw-r--r--core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java104
-rw-r--r--core/tests/coretests/src/android/widget/TextViewActivityTest.java37
-rw-r--r--core/tests/coretests/src/android/widget/espresso/DragAction.java133
-rw-r--r--core/tests/coretests/src/android/widget/espresso/MouseClickAction.java55
-rw-r--r--core/tests/coretests/src/android/widget/espresso/TextViewActions.java109
-rw-r--r--data/etc/platform.xml1
-rw-r--r--data/sounds/AllAudio.mk1
-rw-r--r--data/sounds/AudioPackage10.mk5
-rw-r--r--data/sounds/AudioPackage11.mk5
-rw-r--r--data/sounds/AudioPackage12.mk2
-rw-r--r--data/sounds/AudioPackage12_48.mk2
-rw-r--r--data/sounds/AudioPackage13.mk2
-rw-r--r--data/sounds/AudioPackage13_48.mk2
-rw-r--r--data/sounds/AudioPackage2.mk5
-rw-r--r--data/sounds/AudioPackage3.mk5
-rw-r--r--data/sounds/AudioPackage4.mk5
-rw-r--r--data/sounds/AudioPackage5.mk5
-rw-r--r--data/sounds/AudioPackage6.mk5
-rw-r--r--data/sounds/AudioPackage7.mk5
-rw-r--r--data/sounds/AudioPackage7alt.mk1
-rw-r--r--data/sounds/AudioPackage8.mk5
-rw-r--r--data/sounds/AudioPackage9.mk1
-rw-r--r--data/sounds/OriginalAudio.mk5
-rw-r--r--data/sounds/effects/VideoStop.oggbin0 -> 5582 bytes
-rw-r--r--data/sounds/effects/VideoStop.wavbin0 -> 33728 bytes
-rw-r--r--data/sounds/effects/material/ogg/VideoStop.oggbin0 -> 13422 bytes
-rw-r--r--data/sounds/effects/material/ogg/VideoStop_48k.oggbin0 -> 12304 bytes
-rw-r--r--graphics/java/android/graphics/Canvas.java2
-rw-r--r--graphics/java/android/graphics/drawable/RippleBackground.java1
-rw-r--r--media/java/android/media/MediaPlayer.java8
-rw-r--r--media/java/android/media/browse/MediaBrowser.java4
-rw-r--r--media/jni/soundpool/SoundPool.cpp41
-rw-r--r--media/jni/soundpool/SoundPool.h4
-rw-r--r--packages/DocumentsUI/AndroidManifest.xml2
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java3
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/CopyService.java81
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java7
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java (renamed from packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java)13
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java2
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java64
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java1
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java26
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java16
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java (renamed from packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java)132
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java154
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java16
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java4
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java56
-rw-r--r--packages/ExternalStorageProvider/res/values-uz-rUZ/strings.xml2
-rw-r--r--packages/Keyguard/res/values/config.xml8
-rw-r--r--packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java37
-rw-r--r--packages/MtpDocumentsProvider/res/drawable-hdpi/ic_root_mtp.pngbin0 -> 171 bytes
-rw-r--r--packages/MtpDocumentsProvider/res/drawable-mdpi/ic_root_mtp.pngbin0 -> 123 bytes
-rw-r--r--packages/MtpDocumentsProvider/res/drawable-xhdpi/ic_root_mtp.pngbin0 -> 180 bytes
-rw-r--r--packages/MtpDocumentsProvider/res/drawable-xxhdpi/ic_root_mtp.pngbin0 -> 244 bytes
-rw-r--r--packages/MtpDocumentsProvider/res/drawable-xxxhdpi/ic_root_mtp.pngbin0 -> 327 bytes
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/CursorHelper.java91
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java23
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java40
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java (renamed from packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java)472
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java480
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java6
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java175
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java34
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java1
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java2
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java7
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java8
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java286
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java265
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java87
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java10
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java16
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java134
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java81
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java81
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java3
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java57
-rw-r--r--packages/SettingsLib/res/values-pl/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-th/strings.xml6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawer/DashboardTile.java7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java91
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java107
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java39
-rw-r--r--packages/SettingsProvider/res/values-it/strings.xml2
-rw-r--r--packages/Shell/Android.mk4
-rw-r--r--packages/Shell/AndroidManifest.xml7
-rw-r--r--packages/Shell/res/values-af/strings.xml1
-rw-r--r--packages/Shell/res/values-am/strings.xml1
-rw-r--r--packages/Shell/res/values-ar/strings.xml1
-rw-r--r--packages/Shell/res/values-az-rAZ/strings.xml1
-rw-r--r--packages/Shell/res/values-bg/strings.xml2
-rw-r--r--packages/Shell/res/values-bn-rBD/strings.xml1
-rw-r--r--packages/Shell/res/values-ca/strings.xml1
-rw-r--r--packages/Shell/res/values-cs/strings.xml1
-rw-r--r--packages/Shell/res/values-da/strings.xml1
-rw-r--r--packages/Shell/res/values-de/strings.xml1
-rw-r--r--packages/Shell/res/values-el/strings.xml1
-rw-r--r--packages/Shell/res/values-en-rAU/strings.xml1
-rw-r--r--packages/Shell/res/values-en-rGB/strings.xml1
-rw-r--r--packages/Shell/res/values-en-rIN/strings.xml1
-rw-r--r--packages/Shell/res/values-es-rUS/strings.xml1
-rw-r--r--packages/Shell/res/values-es/strings.xml1
-rw-r--r--packages/Shell/res/values-et-rEE/strings.xml1
-rw-r--r--packages/Shell/res/values-eu-rES/strings.xml1
-rw-r--r--packages/Shell/res/values-fa/strings.xml1
-rw-r--r--packages/Shell/res/values-fi/strings.xml1
-rw-r--r--packages/Shell/res/values-fr-rCA/strings.xml1
-rw-r--r--packages/Shell/res/values-fr/strings.xml1
-rw-r--r--packages/Shell/res/values-gl-rES/strings.xml1
-rw-r--r--packages/Shell/res/values-gu-rIN/strings.xml1
-rw-r--r--packages/Shell/res/values-hi/strings.xml1
-rw-r--r--packages/Shell/res/values-hr/strings.xml1
-rw-r--r--packages/Shell/res/values-hu/strings.xml1
-rw-r--r--packages/Shell/res/values-hy-rAM/strings.xml2
-rw-r--r--packages/Shell/res/values-in/strings.xml1
-rw-r--r--packages/Shell/res/values-is-rIS/strings.xml1
-rw-r--r--packages/Shell/res/values-it/strings.xml1
-rw-r--r--packages/Shell/res/values-iw/strings.xml1
-rw-r--r--packages/Shell/res/values-ja/strings.xml1
-rw-r--r--packages/Shell/res/values-ka-rGE/strings.xml2
-rw-r--r--packages/Shell/res/values-kk-rKZ/strings.xml2
-rw-r--r--packages/Shell/res/values-km-rKH/strings.xml1
-rw-r--r--packages/Shell/res/values-kn-rIN/strings.xml1
-rw-r--r--packages/Shell/res/values-ko/strings.xml2
-rw-r--r--packages/Shell/res/values-ky-rKG/strings.xml2
-rw-r--r--packages/Shell/res/values-lo-rLA/strings.xml1
-rw-r--r--packages/Shell/res/values-lt/strings.xml2
-rw-r--r--packages/Shell/res/values-lv/strings.xml2
-rw-r--r--packages/Shell/res/values-mk-rMK/strings.xml1
-rw-r--r--packages/Shell/res/values-ml-rIN/strings.xml1
-rw-r--r--packages/Shell/res/values-mn-rMN/strings.xml1
-rw-r--r--packages/Shell/res/values-mr-rIN/strings.xml1
-rw-r--r--packages/Shell/res/values-ms-rMY/strings.xml2
-rw-r--r--packages/Shell/res/values-my-rMM/strings.xml1
-rw-r--r--packages/Shell/res/values-nb/strings.xml1
-rw-r--r--packages/Shell/res/values-ne-rNP/strings.xml2
-rw-r--r--packages/Shell/res/values-nl/strings.xml1
-rw-r--r--packages/Shell/res/values-pa-rIN/strings.xml1
-rw-r--r--packages/Shell/res/values-pl/strings.xml1
-rw-r--r--packages/Shell/res/values-pt-rBR/strings.xml1
-rw-r--r--packages/Shell/res/values-pt-rPT/strings.xml1
-rw-r--r--packages/Shell/res/values-pt/strings.xml1
-rw-r--r--packages/Shell/res/values-ro/strings.xml1
-rw-r--r--packages/Shell/res/values-ru/strings.xml1
-rw-r--r--packages/Shell/res/values-si-rLK/strings.xml1
-rw-r--r--packages/Shell/res/values-sk/strings.xml1
-rw-r--r--packages/Shell/res/values-sl/strings.xml1
-rw-r--r--packages/Shell/res/values-sq-rAL/strings.xml1
-rw-r--r--packages/Shell/res/values-sr/strings.xml1
-rw-r--r--packages/Shell/res/values-sv/strings.xml1
-rw-r--r--packages/Shell/res/values-sw/strings.xml1
-rw-r--r--packages/Shell/res/values-ta-rIN/strings.xml1
-rw-r--r--packages/Shell/res/values-te-rIN/strings.xml1
-rw-r--r--packages/Shell/res/values-th/strings.xml1
-rw-r--r--packages/Shell/res/values-tl/strings.xml1
-rw-r--r--packages/Shell/res/values-tr/strings.xml1
-rw-r--r--packages/Shell/res/values-uk/strings.xml1
-rw-r--r--packages/Shell/res/values-ur-rPK/strings.xml1
-rw-r--r--packages/Shell/res/values-uz-rUZ/strings.xml1
-rw-r--r--packages/Shell/res/values-vi/strings.xml1
-rw-r--r--packages/Shell/res/values-zh-rCN/strings.xml2
-rw-r--r--packages/Shell/res/values-zh-rHK/strings.xml2
-rw-r--r--packages/Shell/res/values-zh-rTW/strings.xml2
-rw-r--r--packages/Shell/res/values-zu/strings.xml1
-rw-r--r--packages/Shell/res/values/strings.xml2
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java640
-rw-r--r--packages/Shell/src/com/android/shell/BugreportReceiver.java258
-rw-r--r--packages/Shell/tests/Android.mk20
-rw-r--r--packages/Shell/tests/AndroidManifest.xml43
-rw-r--r--packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java126
-rw-r--r--packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java293
-rw-r--r--packages/Shell/tests/src/com/android/shell/UiBot.java147
-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-af/strings.xml8
-rw-r--r--packages/SystemUI/res/values-am/strings.xml8
-rw-r--r--packages/SystemUI/res/values-ar/strings.xml8
-rw-r--r--packages/SystemUI/res/values-az-rAZ/strings.xml8
-rw-r--r--packages/SystemUI/res/values-bg/strings.xml8
-rw-r--r--packages/SystemUI/res/values-bn-rBD/strings.xml8
-rw-r--r--packages/SystemUI/res/values-ca/strings.xml8
-rw-r--r--packages/SystemUI/res/values-cs/strings.xml8
-rw-r--r--packages/SystemUI/res/values-da/strings.xml8
-rw-r--r--packages/SystemUI/res/values-de/strings.xml8
-rw-r--r--packages/SystemUI/res/values-el/strings.xml8
-rw-r--r--packages/SystemUI/res/values-en-rAU/strings.xml8
-rw-r--r--packages/SystemUI/res/values-en-rGB/strings.xml8
-rw-r--r--packages/SystemUI/res/values-en-rIN/strings.xml8
-rw-r--r--packages/SystemUI/res/values-es-rUS/strings.xml8
-rw-r--r--packages/SystemUI/res/values-es/strings.xml8
-rw-r--r--packages/SystemUI/res/values-et-rEE/strings.xml8
-rw-r--r--packages/SystemUI/res/values-eu-rES/strings.xml8
-rw-r--r--packages/SystemUI/res/values-fa/strings.xml8
-rw-r--r--packages/SystemUI/res/values-fi/strings.xml8
-rw-r--r--packages/SystemUI/res/values-fr-rCA/strings.xml8
-rw-r--r--packages/SystemUI/res/values-fr/strings.xml8
-rw-r--r--packages/SystemUI/res/values-gl-rES/strings.xml8
-rw-r--r--packages/SystemUI/res/values-gu-rIN/strings.xml8
-rw-r--r--packages/SystemUI/res/values-hi/strings.xml8
-rw-r--r--packages/SystemUI/res/values-hr/strings.xml8
-rw-r--r--packages/SystemUI/res/values-hu/strings.xml8
-rw-r--r--packages/SystemUI/res/values-hy-rAM/strings.xml8
-rw-r--r--packages/SystemUI/res/values-in/strings.xml8
-rw-r--r--packages/SystemUI/res/values-is-rIS/strings.xml8
-rw-r--r--packages/SystemUI/res/values-it/strings.xml8
-rw-r--r--packages/SystemUI/res/values-iw/strings.xml8
-rw-r--r--packages/SystemUI/res/values-ja/strings.xml8
-rw-r--r--packages/SystemUI/res/values-ka-rGE/strings.xml8
-rw-r--r--packages/SystemUI/res/values-kk-rKZ/strings.xml8
-rw-r--r--packages/SystemUI/res/values-km-rKH/strings.xml8
-rw-r--r--packages/SystemUI/res/values-kn-rIN/strings.xml8
-rw-r--r--packages/SystemUI/res/values-ko/strings.xml8
-rw-r--r--packages/SystemUI/res/values-ky-rKG/strings.xml8
-rw-r--r--packages/SystemUI/res/values-lo-rLA/strings.xml8
-rw-r--r--packages/SystemUI/res/values-lt/strings.xml8
-rw-r--r--packages/SystemUI/res/values-lv/strings.xml8
-rw-r--r--packages/SystemUI/res/values-mk-rMK/strings.xml8
-rw-r--r--packages/SystemUI/res/values-ml-rIN/strings.xml8
-rw-r--r--packages/SystemUI/res/values-mn-rMN/strings.xml8
-rw-r--r--packages/SystemUI/res/values-mr-rIN/strings.xml8
-rw-r--r--packages/SystemUI/res/values-ms-rMY/strings.xml8
-rw-r--r--packages/SystemUI/res/values-my-rMM/strings.xml8
-rw-r--r--packages/SystemUI/res/values-nb/strings.xml8
-rw-r--r--packages/SystemUI/res/values-ne-rNP/strings.xml8
-rw-r--r--packages/SystemUI/res/values-nl/strings.xml10
-rw-r--r--packages/SystemUI/res/values-pa-rIN/strings.xml8
-rw-r--r--packages/SystemUI/res/values-pl/strings.xml8
-rw-r--r--packages/SystemUI/res/values-pt-rBR/strings.xml8
-rw-r--r--packages/SystemUI/res/values-pt-rPT/strings.xml8
-rw-r--r--packages/SystemUI/res/values-pt/strings.xml8
-rw-r--r--packages/SystemUI/res/values-ro/strings.xml8
-rw-r--r--packages/SystemUI/res/values-ru/strings.xml8
-rw-r--r--packages/SystemUI/res/values-si-rLK/strings.xml8
-rw-r--r--packages/SystemUI/res/values-sk/strings.xml8
-rw-r--r--packages/SystemUI/res/values-sl/strings.xml8
-rw-r--r--packages/SystemUI/res/values-sq-rAL/strings.xml8
-rw-r--r--packages/SystemUI/res/values-sr/strings.xml8
-rw-r--r--packages/SystemUI/res/values-sv/strings.xml8
-rw-r--r--packages/SystemUI/res/values-sw/strings.xml8
-rw-r--r--packages/SystemUI/res/values-ta-rIN/strings.xml8
-rw-r--r--packages/SystemUI/res/values-te-rIN/strings.xml8
-rw-r--r--packages/SystemUI/res/values-th/strings.xml8
-rw-r--r--packages/SystemUI/res/values-tl/strings.xml8
-rw-r--r--packages/SystemUI/res/values-tr/strings.xml8
-rw-r--r--packages/SystemUI/res/values-uk/strings.xml8
-rw-r--r--packages/SystemUI/res/values-ur-rPK/strings.xml8
-rw-r--r--packages/SystemUI/res/values-uz-rUZ/strings.xml8
-rw-r--r--packages/SystemUI/res/values-vi/strings.xml8
-rw-r--r--packages/SystemUI/res/values-zh-rCN/strings.xml13
-rw-r--r--packages/SystemUI/res/values-zh-rHK/strings.xml8
-rw-r--r--packages/SystemUI/res/values-zh-rTW/strings.xml8
-rw-r--r--packages/SystemUI/res/values-zu/strings.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/keyboard/KeyboardUI.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java5
-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/ActivatableNotificationView.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java144
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java12
-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.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java1
-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/NavigationBarView.java2
-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.java43
-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.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.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.java34
-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--packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java9
-rw-r--r--preloaded-classes4
-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/TextServicesManagerService.java3
-rw-r--r--services/core/java/com/android/server/VibratorService.java2
-rwxr-xr-xservices/core/java/com/android/server/am/ActiveServices.java165
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerDebugConfig.java1
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java279
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java66
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java68
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java8
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java78
-rw-r--r--services/core/java/com/android/server/am/EventLogTags.logtags2
-rw-r--r--services/core/java/com/android/server/am/UserController.java209
-rw-r--r--services/core/java/com/android/server/audio/MediaFocusControl.java11
-rw-r--r--services/core/java/com/android/server/connectivity/Tethering.java169
-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/notification/NotificationManagerService.java13
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java28
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java173
-rw-r--r--services/core/java/com/android/server/pm/PermissionsState.java50
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java16
-rw-r--r--services/core/java/com/android/server/pm/UserRestrictionsUtils.java34
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java2
-rw-r--r--services/core/java/com/android/server/wm/AppWindowAnimator.java2
-rw-r--r--services/core/java/com/android/server/wm/AppWindowToken.java12
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java13
-rw-r--r--services/core/java/com/android/server/wm/DockedStackDividerController.java32
-rw-r--r--services/core/java/com/android/server/wm/Task.java53
-rw-r--r--services/core/java/com/android/server/wm/TaskStack.java29
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java117
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java41
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java26
-rw-r--r--services/core/java/com/android/server/wm/WindowSurfaceController.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowSurfacePlacer.java20
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java95
-rw-r--r--services/print/java/com/android/server/print/PrintManagerService.java5
-rw-r--r--services/print/java/com/android/server/print/RemotePrintService.java30
-rw-r--r--services/print/java/com/android/server/print/RemotePrintSpooler.java58
-rw-r--r--services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java11
-rw-r--r--telecomm/java/android/telecom/Connection.java7
-rw-r--r--telecomm/java/android/telecom/ConnectionService.java14
-rw-r--r--telecomm/java/com/android/internal/telecom/IConnectionService.aidl2
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java2
-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/Package.cpp2
-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/aapt2/compile/Compile.cpp219
-rw-r--r--tools/aapt2/flatten/Archive.cpp175
-rw-r--r--tools/aapt2/flatten/Archive.h15
-rw-r--r--tools/aapt2/io/Data.h85
-rw-r--r--tools/aapt2/io/File.h72
-rw-r--r--tools/aapt2/io/FileSystem.h78
-rw-r--r--tools/aapt2/io/ZipArchive.h143
-rw-r--r--tools/aapt2/link/Link.cpp307
-rw-r--r--tools/layoutlib/bridge/src/android/animation/FakeAnimator.java52
-rw-r--r--tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java125
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java2
-rw-r--r--tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java14
-rw-r--r--tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java34
-rw-r--r--tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java2
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java16
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java8
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java14
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java24
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.pngbin0 -> 3274 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.pngbin0 -> 2816 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml14
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java43
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java46
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java11
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java7
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java52
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java30
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java34
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java20
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java2
635 files changed, 15102 insertions, 6609 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 aea7f67b4b2b..d54b7fd11708 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7441,12 +7441,16 @@ package android.content {
method public static boolean compareMimeTypes(java.lang.String, java.lang.String);
method public int describeContents();
method public java.lang.String[] filterMimeTypes(java.lang.String);
+ method public android.os.PersistableBundle getExtras();
method public java.lang.CharSequence getLabel();
method public java.lang.String getMimeType(int);
method public int getMimeTypeCount();
method public boolean hasMimeType(java.lang.String);
+ method public void setExtras(android.os.PersistableBundle);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.ClipDescription> CREATOR;
+ field public static final java.lang.String EXTRA_TARGET_COMPONENT_NAME = "android.content.extra.TARGET_COMPONENT_NAME";
+ field public static final java.lang.String EXTRA_USER_SERIAL_NUMBER = "android.content.extra.USER_SERIAL_NUMBER";
field public static final java.lang.String MIMETYPE_TEXT_HTML = "text/html";
field public static final java.lang.String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
field public static final java.lang.String MIMETYPE_TEXT_PLAIN = "text/plain";
@@ -7809,6 +7813,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);
@@ -7907,6 +7912,7 @@ package android.content {
field public static final int MODE_APPEND = 32768; // 0x8000
field public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 8; // 0x8
field public static final deprecated int MODE_MULTI_PROCESS = 4; // 0x4
+ field public static final int MODE_NO_LOCALIZED_COLLATORS = 16; // 0x10
field public static final int MODE_PRIVATE = 0; // 0x0
field public static final deprecated int MODE_WORLD_READABLE = 1; // 0x1
field public static final deprecated int MODE_WORLD_WRITEABLE = 2; // 0x2
@@ -7991,6 +7997,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 +8351,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";
@@ -13880,7 +13888,7 @@ package android.hardware.camera2 {
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
field public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> SCALER_CROP_REGION;
- field public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_DYNAMIC_BLACK_LEVEL;
+ field public static final android.hardware.camera2.CaptureResult.Key<float[]> SENSOR_DYNAMIC_BLACK_LEVEL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_DYNAMIC_WHITE_LEVEL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_EXPOSURE_TIME;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_FRAME_DURATION;
@@ -28057,6 +28065,7 @@ package android.os {
method public boolean isUserAGoat();
method public boolean isUserRunning(android.os.UserHandle);
method public boolean isUserRunningOrStopping(android.os.UserHandle);
+ method public boolean isUserRunningUnlocked(android.os.UserHandle);
method public deprecated boolean setRestrictionsChallenge(java.lang.String);
method public deprecated void setUserRestriction(java.lang.String, boolean);
method public deprecated void setUserRestrictions(android.os.Bundle);
@@ -28861,6 +28870,8 @@ package android.printservice {
method public boolean isFailed();
method public boolean isQueued();
method public boolean isStarted();
+ method public void setProgress(float);
+ method public void setStatus(java.lang.CharSequence);
method public boolean setTag(java.lang.String);
method public boolean start();
}
@@ -31403,7 +31414,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 +36211,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 a8b054ae7fd4..2ea5ebb5a297 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -100,6 +100,7 @@ package android {
field public static final java.lang.String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS";
field public static final java.lang.String GET_PACKAGE_IMPORTANCE = "android.permission.GET_PACKAGE_IMPORTANCE";
field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
+ field public static final java.lang.String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE";
field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS";
field public static final java.lang.String GET_TOP_ACTIVITY_INFO = "android.permission.GET_TOP_ACTIVITY_INFO";
field public static final java.lang.String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH";
@@ -7682,12 +7683,16 @@ package android.content {
method public static boolean compareMimeTypes(java.lang.String, java.lang.String);
method public int describeContents();
method public java.lang.String[] filterMimeTypes(java.lang.String);
+ method public android.os.PersistableBundle getExtras();
method public java.lang.CharSequence getLabel();
method public java.lang.String getMimeType(int);
method public int getMimeTypeCount();
method public boolean hasMimeType(java.lang.String);
+ method public void setExtras(android.os.PersistableBundle);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.ClipDescription> CREATOR;
+ field public static final java.lang.String EXTRA_TARGET_COMPONENT_NAME = "android.content.extra.TARGET_COMPONENT_NAME";
+ field public static final java.lang.String EXTRA_USER_SERIAL_NUMBER = "android.content.extra.USER_SERIAL_NUMBER";
field public static final java.lang.String MIMETYPE_TEXT_HTML = "text/html";
field public static final java.lang.String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
field public static final java.lang.String MIMETYPE_TEXT_PLAIN = "text/plain";
@@ -8052,6 +8057,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);
@@ -8154,6 +8160,7 @@ package android.content {
field public static final int MODE_APPEND = 32768; // 0x8000
field public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 8; // 0x8
field public static final deprecated int MODE_MULTI_PROCESS = 4; // 0x4
+ field public static final int MODE_NO_LOCALIZED_COLLATORS = 16; // 0x10
field public static final int MODE_PRIVATE = 0; // 0x0
field public static final deprecated int MODE_WORLD_READABLE = 1; // 0x1
field public static final deprecated int MODE_WORLD_WRITEABLE = 2; // 0x2
@@ -8243,6 +8250,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 +8610,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";
@@ -14228,7 +14237,7 @@ package android.hardware.camera2 {
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
field public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> SCALER_CROP_REGION;
- field public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_DYNAMIC_BLACK_LEVEL;
+ field public static final android.hardware.camera2.CaptureResult.Key<float[]> SENSOR_DYNAMIC_BLACK_LEVEL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_DYNAMIC_WHITE_LEVEL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_EXPOSURE_TIME;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_FRAME_DURATION;
@@ -30040,6 +30049,7 @@ package android.os {
method public boolean isUserAGoat();
method public boolean isUserRunning(android.os.UserHandle);
method public boolean isUserRunningOrStopping(android.os.UserHandle);
+ method public boolean isUserRunningUnlocked(android.os.UserHandle);
method public deprecated boolean setRestrictionsChallenge(java.lang.String);
method public deprecated void setUserRestriction(java.lang.String, boolean);
method public deprecated void setUserRestrictions(android.os.Bundle);
@@ -30844,6 +30854,8 @@ package android.printservice {
method public boolean isFailed();
method public boolean isQueued();
method public boolean isStarted();
+ method public void setProgress(float);
+ method public void setStatus(java.lang.CharSequence);
method public boolean setTag(java.lang.String);
method public boolean start();
}
@@ -33519,7 +33531,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 +38527,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 aea7f67b4b2b..904347de1259 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -7441,12 +7441,16 @@ package android.content {
method public static boolean compareMimeTypes(java.lang.String, java.lang.String);
method public int describeContents();
method public java.lang.String[] filterMimeTypes(java.lang.String);
+ method public android.os.PersistableBundle getExtras();
method public java.lang.CharSequence getLabel();
method public java.lang.String getMimeType(int);
method public int getMimeTypeCount();
method public boolean hasMimeType(java.lang.String);
+ method public void setExtras(android.os.PersistableBundle);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.content.ClipDescription> CREATOR;
+ field public static final java.lang.String EXTRA_TARGET_COMPONENT_NAME = "android.content.extra.TARGET_COMPONENT_NAME";
+ field public static final java.lang.String EXTRA_USER_SERIAL_NUMBER = "android.content.extra.USER_SERIAL_NUMBER";
field public static final java.lang.String MIMETYPE_TEXT_HTML = "text/html";
field public static final java.lang.String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
field public static final java.lang.String MIMETYPE_TEXT_PLAIN = "text/plain";
@@ -7809,6 +7813,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);
@@ -7907,6 +7912,7 @@ package android.content {
field public static final int MODE_APPEND = 32768; // 0x8000
field public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 8; // 0x8
field public static final deprecated int MODE_MULTI_PROCESS = 4; // 0x4
+ field public static final int MODE_NO_LOCALIZED_COLLATORS = 16; // 0x10
field public static final int MODE_PRIVATE = 0; // 0x0
field public static final deprecated int MODE_WORLD_READABLE = 1; // 0x1
field public static final deprecated int MODE_WORLD_WRITEABLE = 2; // 0x2
@@ -7991,6 +7997,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 +8351,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";
@@ -13880,7 +13888,7 @@ package android.hardware.camera2 {
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
field public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> SCALER_CROP_REGION;
- field public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_DYNAMIC_BLACK_LEVEL;
+ field public static final android.hardware.camera2.CaptureResult.Key<float[]> SENSOR_DYNAMIC_BLACK_LEVEL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_DYNAMIC_WHITE_LEVEL;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_EXPOSURE_TIME;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Long> SENSOR_FRAME_DURATION;
@@ -28057,6 +28065,7 @@ package android.os {
method public boolean isUserAGoat();
method public boolean isUserRunning(android.os.UserHandle);
method public boolean isUserRunningOrStopping(android.os.UserHandle);
+ method public boolean isUserRunningUnlocked(android.os.UserHandle);
method public deprecated boolean setRestrictionsChallenge(java.lang.String);
method public deprecated void setUserRestriction(java.lang.String, boolean);
method public deprecated void setUserRestrictions(android.os.Bundle);
@@ -28742,7 +28751,9 @@ package android.print {
method public java.lang.String getLabel();
method public android.print.PageRange[] getPages();
method public android.print.PrinterId getPrinterId();
+ method public float getProgress();
method public int getState();
+ method public java.lang.CharSequence getStatus();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.print.PrintJobInfo> CREATOR;
field public static final int STATE_BLOCKED = 4; // 0x4
@@ -28861,6 +28872,8 @@ package android.printservice {
method public boolean isFailed();
method public boolean isQueued();
method public boolean isStarted();
+ method public void setProgress(float);
+ method public void setStatus(java.lang.CharSequence);
method public boolean setTag(java.lang.String);
method public boolean start();
}
@@ -31403,7 +31416,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 +36213,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/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index daf01ec06d0e..2ad63b53efc6 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -154,7 +154,7 @@ public class Am extends BaseCommand {
" am switch-user <USER_ID>\n" +
" am start-user <USER_ID>\n" +
" am unlock-user <USER_ID> [TOKEN_HEX]\n" +
- " am stop-user [-w] <USER_ID>\n" +
+ " am stop-user [-w] [-f] <USER_ID>\n" +
" am stack start <DISPLAY_ID> <INTENT>\n" +
" am stack movetask <TASK_ID> <STACK_ID> [true|false]\n" +
" am stack resize <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" +
@@ -290,6 +290,7 @@ public class Am extends BaseCommand {
"am stop-user: stop execution of USER_ID, not allowing it to run any\n" +
" code until a later explicit start or switch to it.\n" +
" -w: wait for stop-user to complete.\n" +
+ " -f: force stop even if there are related users that cannot be stopped.\n" +
"\n" +
"am stack start: start a new activity on <DISPLAY_ID> using <INTENT>.\n" +
"\n" +
@@ -1131,10 +1132,13 @@ public class Am extends BaseCommand {
private void runStopUser() throws Exception {
boolean wait = false;
- String opt = null;
+ boolean force = false;
+ String opt;
while ((opt = nextOption()) != null) {
if ("-w".equals(opt)) {
wait = true;
+ } else if ("-f".equals(opt)) {
+ force = true;
} else {
System.err.println("Error: unknown option: " + opt);
return;
@@ -1143,7 +1147,7 @@ public class Am extends BaseCommand {
int user = Integer.parseInt(nextArgRequired());
StopUserCallback callback = wait ? new StopUserCallback() : null;
- int res = mAm.stopUser(user, callback);
+ int res = mAm.stopUser(user, force, callback);
if (res != ActivityManager.USER_OP_SUCCESS) {
String txt = "";
switch (res) {
@@ -1153,6 +1157,13 @@ public class Am extends BaseCommand {
case ActivityManager.USER_OP_UNKNOWN_USER:
txt = " (Unknown user " + user + ")";
break;
+ case ActivityManager.USER_OP_ERROR_IS_SYSTEM:
+ txt = " (System user cannot be stopped)";
+ break;
+ case ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP:
+ txt = " (Can't stop user " + user
+ + " - one of its related users can't be stopped)";
+ break;
}
System.err.println("Switch failed: " + res + txt);
} else if (callback != null) {
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..c2038543a4c0 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -265,6 +265,12 @@ public class ActivityManager {
/** @hide User operation call: given user id is the current user, can't be stopped. */
public static final int USER_OP_IS_CURRENT = -2;
+ /** @hide User operation call: system user can't be stopped. */
+ public static final int USER_OP_ERROR_IS_SYSTEM = -3;
+
+ /** @hide User operation call: one of related users cannot be stopped. */
+ public static final int USER_OP_ERROR_RELATED_USERS_CANNOT_STOP = -4;
+
/** @hide Process does not exist. */
public static final int PROCESS_STATE_NONEXISTENT = -1;
@@ -537,6 +543,11 @@ public class ActivityManager {
return stackId == FREEFORM_WORKSPACE_STACK_ID
|| stackId == FULLSCREEN_WORKSPACE_STACK_ID || stackId == DOCKED_STACK_ID;
}
+
+ /** Returns true if the windows in the stack can receive input keys. */
+ public static boolean canReceiveKeys(int stackId) {
+ return stackId != PINNED_STACK_ID;
+ }
}
/**
@@ -3084,7 +3095,9 @@ 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;
+ /** {@hide} */
+ public static final int FLAG_AND_UNLOCKED = 1 << 2;
/**
* Return whether the given user is actively running. This means that
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 19d9fc2a676e..c05d5e8d8295 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1981,9 +1981,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
case STOP_USER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int userid = data.readInt();
+ boolean force = data.readInt() != 0;
IStopUserCallback callback = IStopUserCallback.Stub.asInterface(
data.readStrongBinder());
- int result = stopUser(userid, callback);
+ int result = stopUser(userid, force, callback);
reply.writeNoException();
reply.writeInt(result);
return true;
@@ -5287,11 +5288,13 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
- public int stopUser(int userid, IStopUserCallback callback) throws RemoteException {
+ public int stopUser(int userid, boolean force, IStopUserCallback callback)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeInt(userid);
+ data.writeInt(force ? 1 : 0);
data.writeStrongInterface(callback);
mRemote.transact(STOP_USER_TRANSACTION, data, reply, 0);
reply.readException();
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index bf2e13af2820..b569416cdacb 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -25,7 +25,6 @@ import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.text.TextUtils;
@@ -869,13 +868,19 @@ public class AlarmManager {
* {@link Intent#filterEquals}), will be canceled.
*
* @param operation IntentSender which matches a previously added
- * IntentSender.
+ * IntentSender. This parameter must not be {@code null}.
*
* @see #set
*/
public void cancel(PendingIntent operation) {
if (operation == null) {
- throw new NullPointerException("operation");
+ final String msg = "cancel() called with a null PendingIntent";
+ if (mTargetSdkVersion >= Build.VERSION_CODES.N) {
+ throw new NullPointerException(msg);
+ } else {
+ Log.e(TAG, msg);
+ return;
+ }
}
try {
@@ -891,7 +896,7 @@ public class AlarmManager {
*/
public void cancel(OnAlarmListener listener) {
if (listener == null) {
- throw new NullPointerException("listener");
+ throw new NullPointerException("cancel() called with a null OnAlarmListener");
}
ListenerWrapper wrapper = null;
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..23c4198e348f 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;
}
}
@@ -584,6 +589,9 @@ class ContextImpl extends Context {
if ((mode & MODE_ENABLE_WRITE_AHEAD_LOGGING) != 0) {
flags |= SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING;
}
+ if ((mode & MODE_NO_LOCALIZED_COLLATORS) != 0) {
+ flags |= SQLiteDatabase.NO_LOCALIZED_COLLATORS;
+ }
SQLiteDatabase db = SQLiteDatabase.openDatabase(f.getPath(), factory, flags, errorHandler);
setFilePermissionsFromMode(f.getPath(), mode, 0);
return db;
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 09c6c0bd4d7c..38c795797bf8 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -391,7 +391,7 @@ public interface IActivityManager extends IInterface {
public boolean switchUser(int userid) throws RemoteException;
public boolean startUserInBackground(int userid) throws RemoteException;
public boolean unlockUser(int userid, byte[] token) throws RemoteException;
- public int stopUser(int userid, IStopUserCallback callback) throws RemoteException;
+ public int stopUser(int userid, boolean force, IStopUserCallback callback) throws RemoteException;
public UserInfo getCurrentUser() throws RemoteException;
public boolean isUserRunning(int userid, int flags) throws RemoteException;
public int[] getRunningUserIds() throws RemoteException;
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index c1d5b196563c..c504ce3b1a06 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -93,6 +93,7 @@ interface INotificationManager
AutomaticZenRule addAutomaticZenRule(in AutomaticZenRule automaticZenRule);
boolean updateAutomaticZenRule(in AutomaticZenRule automaticZenRule);
boolean removeAutomaticZenRule(String id);
+ boolean removeAutomaticZenRules(String packageName);
byte[] getBackupPayload(int user);
void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
index 2e05edb2e7d9..1370000fe092 100644
--- a/core/java/android/app/NativeActivity.java
+++ b/core/java/android/app/NativeActivity.java
@@ -129,8 +129,8 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
String libname = "main";
String funcname = "ANativeActivity_onCreate";
ActivityInfo ai;
-
- mIMM = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ mIMM = getSystemService(InputMethodManager.class);
getWindow().takeSurface(this);
getWindow().takeInputQueue(this);
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/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 3eb3e0f3b69f..89610e99a718 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -470,6 +470,20 @@ public class NotificationManager
}
/**
+ * Deletes all automatic zen rules owned by the given package.
+ *
+ * @hide
+ */
+ public boolean removeAutomaticZenRules(String packageName) {
+ INotificationManager service = getService();
+ try {
+ return service.removeAutomaticZenRules(packageName);
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
* Checks the ability to read/modify notification policy for the calling package.
*
* <p>
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index fa11221cc841..9c18df8c6c2a 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -505,8 +505,7 @@ public class SearchDialog extends Dialog {
// We made sure the IME was displayed, so also make sure it is closed
// when we go away.
- InputMethodManager imm = (InputMethodManager)getContext()
- .getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
if (imm != null) {
imm.hideSoftInputFromWindow(
getWindow().getDecorView().getWindowToken(), 0);
@@ -643,8 +642,7 @@ public class SearchDialog extends Dialog {
public void onBackPressed() {
// If the input method is covering the search dialog completely,
// e.g. in landscape mode with no hard keyboard, dismiss just the input method
- InputMethodManager imm = (InputMethodManager)getContext()
- .getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
if (imm != null && imm.isFullscreenMode() &&
imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0)) {
return;
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index e9885169291e..1b024e2a7cb0 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -18,6 +18,7 @@ package android.content;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import android.text.TextUtils;
import java.util.ArrayList;
@@ -59,8 +60,35 @@ public class ClipDescription implements Parcelable {
*/
public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
+ /**
+ * The name of the extra used to define a component name when copying/dragging
+ * an app icon from Launcher.
+ * <p>
+ * Type: String
+ * </p>
+ * <p>
+ * Use {@link ComponentName#unflattenFromString(String)}
+ * and {@link ComponentName#flattenToString()} to convert the extra value
+ * to/from {@link ComponentName}.
+ * </p>
+ */
+ public static final String EXTRA_TARGET_COMPONENT_NAME =
+ "android.content.extra.TARGET_COMPONENT_NAME";
+
+ /**
+ * The name of the extra used to define a user serial number when copying/dragging
+ * an app icon from Launcher.
+ * <p>
+ * Type: long
+ * </p>
+ */
+ public static final String EXTRA_USER_SERIAL_NUMBER =
+ "android.content.extra.USER_SERIAL_NUMBER";
+
+
final CharSequence mLabel;
final String[] mMimeTypes;
+ private PersistableBundle mExtras;
/**
* Create a new clip.
@@ -173,6 +201,27 @@ public class ClipDescription implements Parcelable {
return mMimeTypes[index];
}
+ /**
+ * Retrieve extended data from the clip description.
+ *
+ * @return the bundle containing extended data previously set with
+ * {@link #setExtras(PersistableBundle)}, or null if no extras have been set.
+ *
+ * @see #setExtras(PersistableBundle)
+ */
+ public PersistableBundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Add extended data to the clip description.
+ *
+ * @see #getExtras()
+ */
+ public void setExtras(PersistableBundle extras) {
+ mExtras = new PersistableBundle(extras);
+ }
+
/** @hide */
public void validate() {
if (mMimeTypes == null) {
@@ -211,6 +260,13 @@ public class ClipDescription implements Parcelable {
b.append(mLabel);
b.append('"');
}
+ if (mExtras != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append(mExtras.toString());
+ }
return !first;
}
@@ -236,11 +292,13 @@ public class ClipDescription implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
TextUtils.writeToParcel(mLabel, dest, flags);
dest.writeStringArray(mMimeTypes);
+ dest.writePersistableBundle(mExtras);
}
ClipDescription(Parcel in) {
mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mMimeTypes = in.createStringArray();
+ mExtras = in.readPersistableBundle();
}
public static final Parcelable.Creator<ClipDescription> CREATOR =
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a0102b630b3b..6cc549745f66 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -159,6 +159,16 @@ public abstract class Context {
*/
public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 0x0008;
+ /**
+ * Database open flag: when set, the database is opened without support for
+ * localized collators.
+ *
+ * @see #openOrCreateDatabase(String, int, CursorFactory)
+ * @see #openOrCreateDatabase(String, int, CursorFactory, DatabaseErrorHandler)
+ * @see SQLiteDatabase#NO_LOCALIZED_COLLATORS
+ */
+ public static final int MODE_NO_LOCALIZED_COLLATORS = 0x0010;
+
/** @hide */
@IntDef(flag = true,
value = {
@@ -625,8 +635,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
@@ -1249,6 +1281,7 @@ public abstract class Context {
* default operation, {@link #MODE_WORLD_READABLE}
* and {@link #MODE_WORLD_WRITEABLE} to control permissions.
* Use {@link #MODE_ENABLE_WRITE_AHEAD_LOGGING} to enable write-ahead logging by default.
+ * Use {@link #MODE_NO_LOCALIZED_COLLATORS} to disable localized collators.
* @param factory An optional factory class that is called to instantiate a
* cursor when query is called.
*
@@ -1259,6 +1292,7 @@ public abstract class Context {
* @see #MODE_WORLD_READABLE
* @see #MODE_WORLD_WRITEABLE
* @see #MODE_ENABLE_WRITE_AHEAD_LOGGING
+ * @see #MODE_NO_LOCALIZED_COLLATORS
* @see #deleteDatabase
*/
public abstract SQLiteDatabase openOrCreateDatabase(String name,
@@ -1276,6 +1310,7 @@ public abstract class Context {
* default operation, {@link #MODE_WORLD_READABLE}
* and {@link #MODE_WORLD_WRITEABLE} to control permissions.
* Use {@link #MODE_ENABLE_WRITE_AHEAD_LOGGING} to enable write-ahead logging by default.
+ * Use {@link #MODE_NO_LOCALIZED_COLLATORS} to disable localized collators.
* @param factory An optional factory class that is called to instantiate a
* cursor when query is called.
* @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
@@ -1287,6 +1322,7 @@ public abstract class Context {
* @see #MODE_WORLD_READABLE
* @see #MODE_WORLD_WRITEABLE
* @see #MODE_ENABLE_WRITE_AHEAD_LOGGING
+ * @see #MODE_NO_LOCALIZED_COLLATORS
* @see #deleteDatabase
*/
public abstract SQLiteDatabase openOrCreateDatabase(String name,
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..2178c38efd44 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1609,6 +1609,53 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.MANAGE_PERMISSIONS";
/**
+ * Activity action: Launch UI to review permissions for an app.
+ * The system uses this intent if permission review for apps not
+ * supporting the new runtime permissions model is enabled. In
+ * this mode a permission review is required before any of the
+ * app components can run.
+ * <p>
+ * Input: {@link #EXTRA_PACKAGE_NAME} specifies the package whose
+ * permissions will be reviewed (mandatory).
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_INTENT} specifies a pending intent to
+ * be fired after the permission review (optional).
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_REMOTE_CALLBACK} specifies a callback to
+ * be invoked after the permission review (optional).
+ * </p>
+ * <p>
+ * Input: {@link #EXTRA_RESULT_NEEDED} specifies whether the intent
+ * passed via {@link #EXTRA_INTENT} needs a result (optional).
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @see #EXTRA_PACKAGE_NAME
+ * @see #EXTRA_INTENT
+ * @see #EXTRA_REMOTE_CALLBACK
+ * @see #EXTRA_RESULT_NEEDED
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REVIEW_PERMISSIONS =
+ "android.intent.action.REVIEW_PERMISSIONS";
+
+ /**
+ * Intent extra: A callback for reporting remote result as a bundle.
+ * <p>
+ * Type: IRemoteCallback
+ * </p>
+ *
+ * @hide
+ */
+ public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
+
+ /**
* Intent extra: An app package name.
* <p>
* Type: String
@@ -1620,6 +1667,16 @@ public class Intent implements Parcelable, Cloneable {
public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
/**
+ * Intent extra: An extra for specifying whether a result is needed.
+ * <p>
+ * Type: boolean
+ * </p>
+ *
+ * @hide
+ */
+ public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED";
+
+ /**
* Broadcast action that requests current permission granted information. It will respond
* to the request by sending a broadcast with action defined by
* {@link #EXTRA_GET_PERMISSIONS_RESPONSE_INTENT}. The response will contain
@@ -2877,6 +2934,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/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 0c28008b1b87..aa960a400ebb 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2018,7 +2018,6 @@ public abstract class PackageManager {
*/
public static final int FLAG_PERMISSION_SYSTEM_FIXED = 1 << 4;
-
/**
* Permission flag: The permission is granted by default because it
* enables app functionality that is expected to work out-of-the-box
@@ -2030,6 +2029,14 @@ public abstract class PackageManager {
public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 1 << 5;
/**
+ * Permission flag: The permission has to be reviewed before any of
+ * the app components can run.
+ *
+ * @hide
+ */
+ public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 1 << 6;
+
+ /**
* Mask for all permission flags.
*
* @hide
@@ -4808,6 +4815,7 @@ public abstract class PackageManager {
case FLAG_PERMISSION_USER_SET: return "USER_SET";
case FLAG_PERMISSION_REVOKE_ON_UPGRADE: return "REVOKE_ON_UPGRADE";
case FLAG_PERMISSION_USER_FIXED: return "USER_FIXED";
+ case FLAG_PERMISSION_REVIEW_REQUIRED: return "REVIEW_REQUIRED";
default: return Integer.toString(flag);
}
}
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 905ac5e093d8..8bf20bfa5207 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -122,4 +122,13 @@ public abstract class PackageManagerInternal {
* @param packageList List of package names to keep cached.
*/
public abstract void setKeepUninstalledPackages(List<String> packageList);
+
+ /**
+ * Gets whether some of the permissions used by this package require a user
+ * review before any of the app components can run.
+ * @param packageName The package name for which to check.
+ * @param userId The user under which to check.
+ * @return True a permissions review is required.
+ */
+ public abstract boolean isPermissionsReviewRequired(String packageName, int userId);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 838da37c365d..fd1e57ba3321 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
@@ -1067,19 +1068,20 @@ public class PackageParser {
pkg.mSignatures = null;
pkg.mSigningKeys = null;
- collectCertificates(pkg, new File(pkg.baseCodePath), parseFlags);
+ collectCertificates(pkg, new File(pkg.baseCodePath), pkg.applicationInfo.flags, parseFlags);
if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
- for (String splitCodePath : pkg.splitCodePaths) {
- collectCertificates(pkg, new File(splitCodePath), parseFlags);
+ for (int i = 0; i < pkg.splitCodePaths.length; i++) {
+ collectCertificates(pkg, new File(pkg.splitCodePaths[i]), pkg.splitFlags[i],
+ parseFlags);
}
}
}
- private static void collectCertificates(Package pkg, File apkFile, int parseFlags)
+ private static void collectCertificates(Package pkg, File apkFile, int apkFlags, int parseFlags)
throws PackageParserException {
- final boolean requireCode = ((parseFlags & PARSE_ENFORCE_CODE) != 0)
- && ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0);
+ final boolean hasCode = (apkFlags & ApplicationInfo.FLAG_HAS_CODE) != 0;
+ final boolean requireCode = ((parseFlags & PARSE_ENFORCE_CODE) != 0) && hasCode;
final String apkPath = apkFile.getAbsolutePath();
final boolean skipVerification = Build.IS_DEBUGGABLE
&& ((parseFlags & PARSE_SKIP_VERIFICATION) != 0);
@@ -1216,7 +1218,8 @@ public class PackageParser {
// TODO: factor signature related items out of Package object
final Package tempPkg = new Package(null);
// TODO: fix b/25118622; pass in '0' for parse flags
- collectCertificates(tempPkg, apkFile, flags & PARSE_SKIP_VERIFICATION);
+ collectCertificates(tempPkg, apkFile, 0 /*apkFlags*/,
+ flags & PARSE_SKIP_VERIFICATION);
signatures = tempPkg.mSignatures;
} else {
signatures = null;
@@ -3262,6 +3265,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 +3671,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 +3959,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/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 5f27bcaabd61..3c2d5030c432 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -3329,8 +3329,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @see CaptureRequest#SENSOR_SENSITIVITY
*/
@PublicKey
- public static final Key<android.hardware.camera2.params.BlackLevelPattern> SENSOR_DYNAMIC_BLACK_LEVEL =
- new Key<android.hardware.camera2.params.BlackLevelPattern>("android.sensor.dynamicBlackLevel", android.hardware.camera2.params.BlackLevelPattern.class);
+ public static final Key<float[]> SENSOR_DYNAMIC_BLACK_LEVEL =
+ new Key<float[]>("android.sensor.dynamicBlackLevel", float[].class);
/**
* <p>Maximum raw value output by sensor for this frame.</p>
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/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 10307e4b3de3..515e9a27eefc 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1028,13 +1028,25 @@ public class ConnectivityManager {
* Guess what the network request was trying to say so that the resulting
* network is accessible via the legacy (deprecated) API such as
* requestRouteToHost.
- * This means we should try to be fairly preceise about transport and
+ *
+ * This means we should try to be fairly precise about transport and
* capability but ignore things such as networkSpecifier.
* If the request has more than one transport or capability it doesn't
* match the old legacy requests (they selected only single transport/capability)
* so this function cannot map the request to a single legacy type and
* the resulting network will not be available to the legacy APIs.
*
+ * This code is only called from the requestNetwork API (L and above).
+ *
+ * Setting a legacy type causes CONNECTIVITY_ACTION broadcasts, which are expensive
+ * because they wake up lots of apps - see http://b/23350688 . So we currently only
+ * do this for SUPL requests, which are the only ones that we know need it. If
+ * omitting these broadcasts causes unacceptable app breakage, then for backwards
+ * compatibility we can send them:
+ *
+ * if (targetSdkVersion < Build.VERSION_CODES.M) && // legacy API unsupported >= M
+ * targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP)) // requestNetwork not present < L
+ *
* TODO - This should be removed when the legacy APIs are removed.
*/
private int inferLegacyTypeForNetworkCapabilities(NetworkCapabilities netCap) {
@@ -1046,6 +1058,14 @@ public class ConnectivityManager {
return TYPE_NONE;
}
+ // Do this only for SUPL, until GpsLocationProvider is fixed. http://b/25876485 .
+ if (!netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+ // NOTE: if this causes app breakage, we should not just comment out this early return;
+ // instead, we should make this early return conditional on the requesting app's target
+ // SDK version, as described in the comment above.
+ return TYPE_NONE;
+ }
+
String type = null;
int result = TYPE_NONE;
@@ -1062,7 +1082,7 @@ public class ConnectivityManager {
type = "enableDUN";
result = TYPE_MOBILE_DUN;
} else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
- type = "enableSUPL";
+ type = "enableSUPL";
result = TYPE_MOBILE_SUPL;
// back out this hack for mms as they no longer need this and it's causing
// device slowdowns - b/23350688 (note, supl still needs this)
@@ -2541,7 +2561,7 @@ public class ConnectivityManager {
* This function behaves identically to the non-timedout version, but if a suitable
* network is not found within the given time (in milliseconds) the
* {@link NetworkCallback#unavailable} callback is called. The request must
- * still be released normally by calling {@link releaseNetworkRequest}.
+ * still be released normally by calling {@link unregisterNetworkCallback}.
*
* <p>This method requires the caller to hold either the
* {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
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/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 1aa5c66202f2..bce38f40682a 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -169,7 +169,7 @@ public abstract class BatteryStats implements Parcelable {
/**
* Current version of checkin data format.
*/
- static final String CHECKIN_VERSION = "16";
+ static final String CHECKIN_VERSION = "17";
/**
* Old version, we hit 9 and ran out of room, need to remove.
@@ -407,17 +407,23 @@ public abstract class BatteryStats implements Parcelable {
public abstract Timer getCameraTurnedOnTimer();
public abstract Timer getForegroundActivityTimer();
- // Time this uid has any processes in foreground state.
- public static final int PROCESS_STATE_FOREGROUND = 0;
- // Time this uid has any process in active state (not cached).
- public static final int PROCESS_STATE_ACTIVE = 1;
+ // Time this uid has any processes in the top state.
+ public static final int PROCESS_STATE_TOP = 0;
+ // Time this uid has any process with a started out bound foreground service.
+ public static final int PROCESS_STATE_FOREGROUND_SERVICE = 1;
+ // Time this uid has any process that is top while the device is sleeping.
+ public static final int PROCESS_STATE_TOP_SLEEPING = 2;
+ // Time this uid has any process in an active foreground state.
+ public static final int PROCESS_STATE_FOREGROUND = 3;
+ // Time this uid has any process in an active background state.
+ public static final int PROCESS_STATE_BACKGROUND = 4;
// Time this uid has any processes running at all.
- public static final int PROCESS_STATE_RUNNING = 2;
+ public static final int PROCESS_STATE_CACHED = 5;
// Total number of process states we track.
- public static final int NUM_PROCESS_STATE = 3;
+ public static final int NUM_PROCESS_STATE = 6;
static final String[] PROCESS_STATE_NAMES = {
- "Foreground", "Active", "Running"
+ "Top", "Fg Service", "Top Sleeping", "Foreground", "Background", "Cached"
};
public abstract long getProcessStateTime(int state, long elapsedRealtimeUs, int which);
@@ -2954,8 +2960,9 @@ public abstract class BatteryStats implements Parcelable {
final Object[] stateTimes = new Object[Uid.NUM_PROCESS_STATE];
long totalStateTime = 0;
for (int ips=0; ips<Uid.NUM_PROCESS_STATE; ips++) {
- totalStateTime += u.getProcessStateTime(ips, rawRealtime, which);
- stateTimes[ips] = (totalStateTime + 500) / 1000;
+ final long time = u.getProcessStateTime(ips, rawRealtime, which);
+ totalStateTime += time;
+ stateTimes[ips] = (time + 500) / 1000;
}
if (totalStateTime > 0) {
dumpLine(pw, uid, category, STATE_TIME_DATA, stateTimes);
@@ -4122,11 +4129,18 @@ public abstract class BatteryStats implements Parcelable {
sb.append(" ");
sb.append(Uid.PROCESS_STATE_NAMES[ips]);
sb.append(" for: ");
- formatTimeMs(sb, (totalStateTime + 500) / 1000);
+ formatTimeMs(sb, (time + 500) / 1000);
pw.println(sb.toString());
uidActivity = true;
}
}
+ if (totalStateTime > 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Total running: ");
+ formatTimeMs(sb, (totalStateTime + 500) / 1000);
+ pw.println(sb.toString());
+ }
final long userCpuTimeUs = u.getUserCpuTimeUs(which);
final long systemCpuTimeUs = u.getSystemCpuTimeUs(which);
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index f7c8662515fc..de8b6905c954 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -788,6 +788,18 @@ public class Build {
SystemProperties.getInt("ro.debuggable", 0) == 1;
/**
+ * Specifies whether the permissions needed by a legacy app should be
+ * reviewed before any of its components can run. A legacy app is one
+ * with targetSdkVersion < 23, i.e apps using the old permission model.
+ * If review is not required, permissions are reviewed before the app
+ * is installed.
+ *
+ * @hide
+ */
+ public static final boolean PERMISSIONS_REVIEW_REQUIRED =
+ SystemProperties.getInt("ro.permission_review_required", 0) == 1;
+
+ /**
* Returns the version string for the radio firmware. May return
* null (if, for instance, the radio is not currently on).
*/
diff --git a/core/java/android/os/IProcessInfoService.aidl b/core/java/android/os/IProcessInfoService.aidl
index c98daa282ec4..62237f567841 100644
--- a/core/java/android/os/IProcessInfoService.aidl
+++ b/core/java/android/os/IProcessInfoService.aidl
@@ -25,5 +25,12 @@ interface IProcessInfoService
* to indicate that no process with the given PID exists.
*/
void getProcessStatesFromPids(in int[] pids, out int[] states);
+
+ /**
+ * For each PID in the given input array, write the current process state and OOM score
+ * for that process into the output arrays, or ActivityManager.PROCESS_STATE_NONEXISTENT
+ * in the states array to indicate that no process with the given PID exists.
+ */
+ void getProcessStatesAndOomScoresFromPids(in int[] pids, out int[] states, out int[] scores);
}
diff --git a/core/java/android/os/RemoteCallback.java b/core/java/android/os/RemoteCallback.java
index ca95bdf529cd..89e30a96f65d 100644
--- a/core/java/android/os/RemoteCallback.java
+++ b/core/java/android/os/RemoteCallback.java
@@ -16,88 +16,84 @@
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
/**
- * TODO: Make this a public API? Let's see how it goes with a few use
- * cases first.
* @hide
*/
-public abstract class RemoteCallback implements Parcelable {
- final Handler mHandler;
- final IRemoteCallback mTarget;
-
- class DeliverResult implements Runnable {
- final Bundle mResult;
-
- DeliverResult(Bundle result) {
- mResult = result;
- }
-
- public void run() {
- onResult(mResult);
- }
+public final class RemoteCallback implements Parcelable {
+
+ public interface OnResultListener {
+ public void onResult(Bundle result);
}
-
- class LocalCallback extends IRemoteCallback.Stub {
- public void sendResult(Bundle bundle) {
- mHandler.post(new DeliverResult(bundle));
- }
+
+ private final OnResultListener mListener;
+ private final Handler mHandler;
+ private final IRemoteCallback mCallback;
+
+ public RemoteCallback(OnResultListener listener) {
+ this(listener, null);
}
-
- static class RemoteCallbackProxy extends RemoteCallback {
- RemoteCallbackProxy(IRemoteCallback target) {
- super(target);
- }
-
- protected void onResult(Bundle bundle) {
+
+ public RemoteCallback(@NonNull OnResultListener listener, @Nullable Handler handler) {
+ if (listener == null) {
+ throw new NullPointerException("listener cannot be null");
}
- }
-
- public RemoteCallback(Handler handler) {
+ mListener = listener;
mHandler = handler;
- mTarget = new LocalCallback();
+ mCallback = new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) {
+ RemoteCallback.this.sendResult(data);
+ }
+ };
}
-
- RemoteCallback(IRemoteCallback target) {
+
+ RemoteCallback(Parcel parcel) {
+ mListener = null;
mHandler = null;
- mTarget = target;
- }
-
- public void sendResult(Bundle bundle) throws RemoteException {
- mTarget.sendResult(bundle);
+ mCallback = IRemoteCallback.Stub.asInterface(
+ parcel.readStrongBinder());
}
-
- protected abstract void onResult(Bundle bundle);
-
- public boolean equals(Object otherObj) {
- if (otherObj == null) {
- return false;
- }
- try {
- return mTarget.asBinder().equals(((RemoteCallback)otherObj)
- .mTarget.asBinder());
- } catch (ClassCastException e) {
+
+ public void sendResult(@Nullable final Bundle result) {
+ // Do local dispatch
+ if (mListener != null) {
+ if (mHandler != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onResult(result);
+ }
+ });
+ } else {
+ mListener.onResult(result);
+ }
+ // Do remote dispatch
+ } else {
+ try {
+ mCallback.sendResult(result);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
}
- return false;
}
-
- public int hashCode() {
- return mTarget.asBinder().hashCode();
- }
-
+
+ @Override
public int describeContents() {
return 0;
}
- public void writeToParcel(Parcel out, int flags) {
- out.writeStrongBinder(mTarget.asBinder());
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeStrongBinder(mCallback.asBinder());
}
public static final Parcelable.Creator<RemoteCallback> CREATOR
= new Parcelable.Creator<RemoteCallback>() {
- public RemoteCallback createFromParcel(Parcel in) {
- IBinder target = in.readStrongBinder();
- return target != null ? new RemoteCallbackProxy(
- IRemoteCallback.Stub.asInterface(target)) : null;
+ public RemoteCallback createFromParcel(Parcel parcel) {
+ return new RemoteCallback(parcel);
}
public RemoteCallback[] newArray(int size) {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 0a149bbac713..79390d4696d7 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -487,6 +487,19 @@ public class UserManager {
public static final String DISALLOW_RECORD_AUDIO = "no_record_audio";
/**
+ * Specifies if a user is not allowed to run in the background and should be stopped during
+ * user switch. The default value is <code>false</code>.
+ *
+ * <p>This restriction can be set by device owners and profile owners.
+ *
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ * @hide
+ */
+ public static final String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background";
+
+ /**
* Specifies if a user is not allowed to use the camera.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -742,6 +755,23 @@ public class UserManager {
}
/**
+ * Return whether the given user is running in an "unlocked" state. A user
+ * is unlocked only after they've entered their credentials (such as a lock
+ * pattern or PIN), and credential-encrypted private app data storage is
+ * available.
+ *
+ * @param user to retrieve the unlocked state for.
+ */
+ public boolean isUserRunningUnlocked(UserHandle user) {
+ try {
+ return ActivityManagerNative.getDefault().isUserRunning(
+ user.getIdentifier(), ActivityManager.FLAG_AND_UNLOCKED);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
* Returns the UserInfo object describing a specific user.
* Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
* @param userHandle the user handle of the user whose information is being requested.
@@ -929,9 +959,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/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl
index db2bf1a65e13..b7cfbea43646 100644
--- a/core/java/android/print/IPrintSpooler.aidl
+++ b/core/java/android/print/IPrintSpooler.aidl
@@ -41,6 +41,23 @@ oneway interface IPrintSpooler {
void createPrintJob(in PrintJobInfo printJob);
void setPrintJobState(in PrintJobId printJobId, int status, String stateReason,
IPrintSpoolerCallbacks callback, int sequence);
+
+ /**
+ * Set the progress of this print job
+ *
+ * @param printJobId The print job to update
+ * @param progress The new progress
+ */
+ void setProgress(in PrintJobId printJobId, in float progress);
+
+ /**
+ * Set the status of this print job
+ *
+ * @param printJobId The print job to update
+ * @param status The new status, can be null
+ */
+ void setStatus(in PrintJobId printJobId, in CharSequence status);
+
void setPrintJobTag(in PrintJobId printJobId, String tag, IPrintSpoolerCallbacks callback,
int sequence);
void writePrintJobData(in ParcelFileDescriptor fd, in PrintJobId printJobId);
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 63f94fe642fb..7148c8757830 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -16,10 +16,15 @@
package android.print;
+import android.annotation.FloatRange;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
+
import java.util.Arrays;
/**
@@ -149,9 +154,6 @@ public final class PrintJobInfo implements Parcelable {
/** How many copies to print. */
private int mCopies;
- /** Reason for the print job being in its current state. */
- private String mStateReason;
-
/** The pages to print */
private PageRange[] mPageRanges;
@@ -161,6 +163,12 @@ public final class PrintJobInfo implements Parcelable {
/** Information about the printed document. */
private PrintDocumentInfo mDocumentInfo;
+ /** The progress made on printing this job or -1 if not set. */
+ private float mProgress;
+
+ /** A short string describing the status of this job. */
+ private CharSequence mStatus;
+
/** Advanced printer specific options. */
private Bundle mAdvancedOptions;
@@ -169,7 +177,7 @@ public final class PrintJobInfo implements Parcelable {
/** @hide*/
public PrintJobInfo() {
- /* do nothing */
+ mProgress = -1;
}
/** @hide */
@@ -183,10 +191,11 @@ public final class PrintJobInfo implements Parcelable {
mTag = other.mTag;
mCreationTime = other.mCreationTime;
mCopies = other.mCopies;
- mStateReason = other.mStateReason;
mPageRanges = other.mPageRanges;
mAttributes = other.mAttributes;
mDocumentInfo = other.mDocumentInfo;
+ mProgress = other.mProgress;
+ mStatus = other.mStatus;
mCanceling = other.mCanceling;
mAdvancedOptions = other.mAdvancedOptions;
}
@@ -201,7 +210,6 @@ public final class PrintJobInfo implements Parcelable {
mTag = parcel.readString();
mCreationTime = parcel.readLong();
mCopies = parcel.readInt();
- mStateReason = parcel.readString();
Parcelable[] parcelables = parcel.readParcelableArray(null);
if (parcelables != null) {
mPageRanges = new PageRange[parcelables.length];
@@ -211,6 +219,8 @@ public final class PrintJobInfo implements Parcelable {
}
mAttributes = (PrintAttributes) parcel.readParcelable(null);
mDocumentInfo = (PrintDocumentInfo) parcel.readParcelable(null);
+ mProgress = parcel.readFloat();
+ mStatus = parcel.readCharSequence();
mCanceling = (parcel.readInt() == 1);
mAdvancedOptions = parcel.readBundle();
}
@@ -227,7 +237,7 @@ public final class PrintJobInfo implements Parcelable {
/**
* Sets the unique print job id.
*
- * @param The job id.
+ * @param id The job id.
*
* @hide
*/
@@ -265,7 +275,7 @@ public final class PrintJobInfo implements Parcelable {
}
/**
- * Sets the unique target pritner id.
+ * Sets the unique target printer id.
*
* @param printerId The target printer id.
*
@@ -326,6 +336,30 @@ public final class PrintJobInfo implements Parcelable {
}
/**
+ * Sets the progress of the print job.
+ *
+ * @param progress the progress of the job
+ *
+ * @hide
+ */
+ public void setProgress(@FloatRange(from=0.0, to=1.0) float progress) {
+ Preconditions.checkArgumentInRange(progress, 0, 1, "progress");
+
+ mProgress = progress;
+ }
+
+ /**
+ * Sets the status of the print job.
+ *
+ * @param status the status of the job, can be null
+ *
+ * @hide
+ */
+ public void setStatus(@Nullable CharSequence status) {
+ mStatus = status;
+ }
+
+ /**
* Sets the owning application id.
*
* @return The owning app id.
@@ -416,30 +450,6 @@ public final class PrintJobInfo implements Parcelable {
}
/**
- * Gets the reason for the print job being in the current state.
- *
- * @return The reason, or null if there is no reason or the
- * reason is unknown.
- *
- * @hide
- */
- public String getStateReason() {
- return mStateReason;
- }
-
- /**
- * Sets the reason for the print job being in the current state.
- *
- * @param stateReason The reason, or null if there is no reason
- * or the reason is unknown.
- *
- * @hide
- */
- public void setStateReason(String stateReason) {
- mStateReason = stateReason;
- }
-
- /**
* Gets the included pages.
*
* @return The included pages or <code>null</code> if not set.
@@ -604,10 +614,11 @@ public final class PrintJobInfo implements Parcelable {
parcel.writeString(mTag);
parcel.writeLong(mCreationTime);
parcel.writeInt(mCopies);
- parcel.writeString(mStateReason);
parcel.writeParcelableArray(mPageRanges, flags);
parcel.writeParcelable(mAttributes, flags);
parcel.writeParcelable(mDocumentInfo, 0);
+ parcel.writeFloat(mProgress);
+ parcel.writeCharSequence(mStatus);
parcel.writeInt(mCanceling ? 1 : 0);
parcel.writeBundle(mAdvancedOptions);
}
@@ -631,6 +642,9 @@ public final class PrintJobInfo implements Parcelable {
builder.append(", pages: " + (mPageRanges != null
? Arrays.toString(mPageRanges) : null));
builder.append(", hasAdvancedOptions: " + (mAdvancedOptions != null));
+ builder.append(", progress: " + mProgress);
+ builder.append(", status: " + (mStatus != null
+ ? mStatus.toString() : null));
builder.append("}");
return builder.toString();
}
@@ -666,6 +680,28 @@ public final class PrintJobInfo implements Parcelable {
}
/**
+ * Get the progress that has been made printing this job.
+ *
+ * @return the print progress or -1 if not set
+ * @hide
+ */
+ @TestApi
+ public float getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Get the status of this job.
+ *
+ * @return the status of this job or null if not set
+ * @hide
+ */
+ @TestApi
+ public @Nullable CharSequence getStatus() {
+ return mStatus;
+ }
+
+ /**
* Builder for creating a {@link PrintJobInfo}.
*/
public static final class Builder {
@@ -711,6 +747,28 @@ public final class PrintJobInfo implements Parcelable {
}
/**
+ * Sets the progress of the print job.
+ *
+ * @param progress the progress of the job
+ * @hide
+ */
+ public void setProgress(@FloatRange(from=0.0, to=1.0) float progress) {
+ Preconditions.checkArgumentInRange(progress, 0, 1, "progress");
+
+ mPrototype.mProgress = progress;
+ }
+
+ /**
+ * Sets the status of the print job.
+ *
+ * @param status the status of the job, can be null
+ * @hide
+ */
+ public void setStatus(@Nullable CharSequence status) {
+ mPrototype.mStatus = status;
+ }
+
+ /**
* Puts an advanced (printer specific) option.
*
* @param key The option key.
diff --git a/core/java/android/printservice/IPrintServiceClient.aidl b/core/java/android/printservice/IPrintServiceClient.aidl
index c2dfc30a4e25..b4baa4851392 100644
--- a/core/java/android/printservice/IPrintServiceClient.aidl
+++ b/core/java/android/printservice/IPrintServiceClient.aidl
@@ -35,6 +35,22 @@ interface IPrintServiceClient {
boolean setPrintJobTag(in PrintJobId printJobId, String tag);
oneway void writePrintJobData(in ParcelFileDescriptor fd, in PrintJobId printJobId);
+ /**
+ * Set the progress of this print job
+ *
+ * @param printJobId The print job to update
+ * @param progress The new progress
+ */
+ void setProgress(in PrintJobId printJobId, in float progress);
+
+ /**
+ * Set the status of this print job
+ *
+ * @param printJobId The print job to update
+ * @param status The new status, can be null
+ */
+ void setStatus(in PrintJobId printJobId, in CharSequence status);
+
void onPrintersAdded(in ParceledListSlice printers);
void onPrintersRemoved(in ParceledListSlice printerIds);
}
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index 6fa0bddb43f4..86fc292cb003 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -16,6 +16,10 @@
package android.printservice;
+import android.annotation.FloatRange;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.RemoteException;
import android.print.PrintJobId;
import android.print.PrintJobInfo;
@@ -41,7 +45,7 @@ public final class PrintJob {
private PrintJobInfo mCachedInfo;
- PrintJob(PrintJobInfo jobInfo, IPrintServiceClient client) {
+ PrintJob(@NonNull PrintJobInfo jobInfo, @NonNull IPrintServiceClient client) {
mCachedInfo = jobInfo;
mPrintServiceClient = client;
mDocument = new PrintDocument(mCachedInfo.getId(), client,
@@ -53,6 +57,7 @@ public final class PrintJob {
*
* @return The id.
*/
+ @MainThread
public PrintJobId getId() {
PrintService.throwIfNotCalledOnMainThread();
return mCachedInfo.getId();
@@ -68,7 +73,8 @@ public final class PrintJob {
*
* @return The print job info.
*/
- public PrintJobInfo getInfo() {
+ @MainThread
+ public @NonNull PrintJobInfo getInfo() {
PrintService.throwIfNotCalledOnMainThread();
if (isInImmutableState()) {
return mCachedInfo;
@@ -90,7 +96,8 @@ public final class PrintJob {
*
* @return The document.
*/
- public PrintDocument getDocument() {
+ @MainThread
+ public @NonNull PrintDocument getDocument() {
PrintService.throwIfNotCalledOnMainThread();
return mDocument;
}
@@ -104,6 +111,7 @@ public final class PrintJob {
* @see #start()
* @see #cancel()
*/
+ @MainThread
public boolean isQueued() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_QUEUED;
@@ -117,8 +125,9 @@ public final class PrintJob {
*
* @see #complete()
* @see #cancel()
- * @see #fail(CharSequence)
+ * @see #fail(String)
*/
+ @MainThread
public boolean isStarted() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_STARTED;
@@ -132,8 +141,9 @@ public final class PrintJob {
*
* @see #start()
* @see #cancel()
- * @see #fail(CharSequence)
+ * @see #fail(String)
*/
+ @MainThread
public boolean isBlocked() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_BLOCKED;
@@ -147,6 +157,7 @@ public final class PrintJob {
*
* @see #complete()
*/
+ @MainThread
public boolean isCompleted() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_COMPLETED;
@@ -158,8 +169,9 @@ public final class PrintJob {
*
* @return Whether the print job is failed.
*
- * @see #fail(CharSequence)
+ * @see #fail(String)
*/
+ @MainThread
public boolean isFailed() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_FAILED;
@@ -173,6 +185,7 @@ public final class PrintJob {
*
* @see #cancel()
*/
+ @MainThread
public boolean isCancelled() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_CANCELED;
@@ -182,12 +195,16 @@ public final class PrintJob {
* Starts the print job. You should call this method if {@link
* #isQueued()} or {@link #isBlocked()} returns true and you started
* resumed printing.
+ * <p>
+ * This resets the print status to null. Set the new status by using {@link #setStatus}.
+ * </p>
*
* @return Whether the job was started.
*
* @see #isQueued()
* @see #isBlocked()
*/
+ @MainThread
public boolean start() {
PrintService.throwIfNotCalledOnMainThread();
final int state = getInfo().getState();
@@ -205,18 +222,20 @@ public final class PrintJob {
* paper to continue printing. To resume the print job call {@link
* #start()}.
*
+ * @param reason The human readable, short, and translated reason why the print job is blocked.
* @return Whether the job was blocked.
*
* @see #isStarted()
* @see #isBlocked()
*/
- public boolean block(String reason) {
+ @MainThread
+ public boolean block(@Nullable String reason) {
PrintService.throwIfNotCalledOnMainThread();
PrintJobInfo info = getInfo();
final int state = info.getState();
if (state == PrintJobInfo.STATE_STARTED
|| (state == PrintJobInfo.STATE_BLOCKED
- && !TextUtils.equals(info.getStateReason(), reason))) {
+ && !TextUtils.equals(info.getStatus(), reason))) {
return setState(PrintJobInfo.STATE_BLOCKED, reason);
}
return false;
@@ -230,6 +249,7 @@ public final class PrintJob {
*
* @see #isStarted()
*/
+ @MainThread
public boolean complete() {
PrintService.throwIfNotCalledOnMainThread();
if (isStarted()) {
@@ -251,7 +271,8 @@ public final class PrintJob {
* @see #isStarted()
* @see #isBlocked()
*/
- public boolean fail(String error) {
+ @MainThread
+ public boolean fail(@Nullable String error) {
PrintService.throwIfNotCalledOnMainThread();
if (!isInImmutableState()) {
return setState(PrintJobInfo.STATE_FAILED, error);
@@ -271,6 +292,7 @@ public final class PrintJob {
* @see #isQueued()
* @see #isBlocked()
*/
+ @MainThread
public boolean cancel() {
PrintService.throwIfNotCalledOnMainThread();
if (!isInImmutableState()) {
@@ -280,6 +302,39 @@ public final class PrintJob {
}
/**
+ * Sets the progress of this print job as a fraction of 1.
+ *
+ * @param progress The new progress
+ */
+ @MainThread
+ public void setProgress(@FloatRange(from=0.0, to=1.0) float progress) {
+ PrintService.throwIfNotCalledOnMainThread();
+
+ try {
+ mPrintServiceClient.setProgress(mCachedInfo.getId(), progress);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error setting progress for job: " + mCachedInfo.getId(), re);
+ }
+ }
+
+ /**
+ * Sets the status of this print job. This should be a human readable, short, and translated
+ * description of the current state of the print job.
+ *
+ * @param status The new status. If null the status will be empty.
+ */
+ @MainThread
+ public void setStatus(@Nullable CharSequence status) {
+ PrintService.throwIfNotCalledOnMainThread();
+
+ try {
+ mPrintServiceClient.setStatus(mCachedInfo.getId(), status);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error setting status for job: " + mCachedInfo.getId(), re);
+ }
+ }
+
+ /**
* Sets a tag that is valid in the context of a {@link PrintService}
* and is not interpreted by the system. For example, a print service
* may set as a tag the key of the print job returned by a remote
@@ -288,6 +343,7 @@ public final class PrintJob {
* @param tag The tag.
* @return True if the tag was set, false otherwise.
*/
+ @MainThread
public boolean setTag(String tag) {
PrintService.throwIfNotCalledOnMainThread();
if (isInImmutableState()) {
@@ -319,6 +375,7 @@ public final class PrintJob {
* @param key The option key.
* @return The option value.
*/
+ @MainThread
public String getAdvancedStringOption(String key) {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getAdvancedStringOption(key);
@@ -331,6 +388,7 @@ public final class PrintJob {
* @param key The option key.
* @return Whether the option is present.
*/
+ @MainThread
public boolean hasAdvancedOption(String key) {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().hasAdvancedOption(key);
@@ -342,6 +400,7 @@ public final class PrintJob {
* @param key The option key.
* @return The option value.
*/
+ @MainThread
public int getAdvancedIntOption(String key) {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getAdvancedIntOption(key);
@@ -374,14 +433,14 @@ public final class PrintJob {
|| state == PrintJobInfo.STATE_FAILED;
}
- private boolean setState(int state, String error) {
+ private boolean setState(int state, @Nullable String error) {
try {
if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state, error)) {
// Best effort - update the state of the cached info since
// we may not be able to re-fetch it later if the job gets
// removed from the spooler as a result of the state change.
mCachedInfo.setState(state);
- mCachedInfo.setStateReason(error);
+ mCachedInfo.setStatus(error);
return true;
}
} catch (RemoteException re) {
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 8ae899f4d453..d53bb0d2aa18 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -67,7 +67,7 @@ import java.util.List;
* @see DocumentsProvider
*/
public final class DocumentsContract {
- private static final String TAG = "Documents";
+ private static final String TAG = "DocumentsContract";
// content://com.example/root/
// content://com.example/root/sdcard/
@@ -591,6 +591,12 @@ public final class DocumentsContract {
*/
public static final String EXTRA_ERROR = "error";
+ /**
+ * Optional result (I'm thinking boolean) answer to a question.
+ * {@hide}
+ */
+ public static final String EXTRA_RESULT = "result";
+
/** {@hide} */
public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
/** {@hide} */
@@ -601,6 +607,8 @@ public final class DocumentsContract {
public static final String METHOD_COPY_DOCUMENT = "android:copyDocument";
/** {@hide} */
public static final String METHOD_MOVE_DOCUMENT = "android:moveDocument";
+ /** {@hide} */
+ public static final String METHOD_IS_CHILD_DOCUMENT = "android:isChildDocument";
/** {@hide} */
public static final String EXTRA_URI = "uri";
@@ -1025,6 +1033,24 @@ public final class DocumentsContract {
return out.getParcelable(DocumentsContract.EXTRA_URI);
}
+ /** {@hide} */
+ public static boolean isChildDocument(ContentProviderClient client, Uri parentDocumentUri,
+ Uri childDocumentUri) throws RemoteException {
+
+ final Bundle in = new Bundle();
+ in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
+ in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, childDocumentUri);
+
+ final Bundle out = client.call(METHOD_IS_CHILD_DOCUMENT, null, in);
+ if (out == null) {
+ throw new RemoteException("Failed to get a reponse from isChildDocument query.");
+ }
+ if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
+ throw new RemoteException("Response did not include result field..");
+ }
+ return out.getBoolean(DocumentsContract.EXTRA_RESULT);
+ }
+
/**
* Change the display name of an existing document.
* <p>
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index f01073bbd43d..e25ba35c8bb6 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -16,11 +16,12 @@
package android.provider;
+import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
-import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
-import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_IS_CHILD_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
import static android.provider.DocumentsContract.buildDocumentUri;
import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree;
import static android.provider.DocumentsContract.buildTreeDocumentUri;
@@ -688,6 +689,16 @@ public abstract class DocumentsProvider extends ContentProvider {
return super.call(method, arg, extras);
}
+ try {
+ return callUnchecked(method, arg, extras);
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException("Failed call " + method, e);
+ }
+ }
+
+ private Bundle callUnchecked(String method, String arg, Bundle extras)
+ throws FileNotFoundException {
+
final Context context = getContext();
final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
final String authority = documentUri.getAuthority();
@@ -697,109 +708,120 @@ public abstract class DocumentsProvider extends ContentProvider {
throw new SecurityException(
"Requested authority " + authority + " doesn't match provider " + mAuthority);
}
- enforceTree(documentUri);
final Bundle out = new Bundle();
- try {
- if (METHOD_CREATE_DOCUMENT.equals(method)) {
- enforceWritePermissionInner(documentUri, getCallingPackage(), null);
- final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
- final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
- final String newDocumentId = createDocument(documentId, mimeType, displayName);
+ // If the URI is a tree URI performs some validation.
+ enforceTree(documentUri);
- // No need to issue new grants here, since caller either has
- // manage permission or a prefix grant. We might generate a
- // tree style URI if that's how they called us.
- final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
- newDocumentId);
- out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+ if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
+ enforceReadPermissionInner(documentUri, getCallingPackage(), null);
- } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
- enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+ final Uri childUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+ final String childAuthority = childUri.getAuthority();
+ final String childId = DocumentsContract.getDocumentId(childUri);
- final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
- final String newDocumentId = renameDocument(documentId, displayName);
+ out.putBoolean(
+ DocumentsContract.EXTRA_RESULT,
+ mAuthority.equals(childAuthority)
+ && isChildDocument(documentId, childId));
- if (newDocumentId != null) {
- final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
- newDocumentId);
+ } else if (METHOD_CREATE_DOCUMENT.equals(method)) {
+ enforceWritePermissionInner(documentUri, getCallingPackage(), null);
- // If caller came in with a narrow grant, issue them a
- // narrow grant for the newly renamed document.
- if (!isTreeUri(newDocumentUri)) {
- final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
- documentUri);
- context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
- }
+ final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
+ final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
+ final String newDocumentId = createDocument(documentId, mimeType, displayName);
+
+ // No need to issue new grants here, since caller either has
+ // manage permission or a prefix grant. We might generate a
+ // tree style URI if that's how they called us.
+ final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
+ newDocumentId);
+ out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+
+ } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
+ enforceWritePermissionInner(documentUri, getCallingPackage(), null);
- out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+ final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
+ final String newDocumentId = renameDocument(documentId, displayName);
+
+ if (newDocumentId != null) {
+ final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
+ newDocumentId);
- // Original document no longer exists, clean up any grants
- revokeDocumentPermission(documentId);
+ // If caller came in with a narrow grant, issue them a
+ // narrow grant for the newly renamed document.
+ if (!isTreeUri(newDocumentUri)) {
+ final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
+ documentUri);
+ context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
}
- } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
- enforceWritePermissionInner(documentUri, getCallingPackage(), null);
- deleteDocument(documentId);
+ out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
- // Document no longer exists, clean up any grants
+ // Original document no longer exists, clean up any grants
revokeDocumentPermission(documentId);
+ }
- } else if (METHOD_COPY_DOCUMENT.equals(method)) {
- final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
- final String targetId = DocumentsContract.getDocumentId(targetUri);
+ } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
+ enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+ deleteDocument(documentId);
- enforceReadPermissionInner(documentUri, getCallingPackage(), null);
- enforceWritePermissionInner(targetUri, getCallingPackage(), null);
+ // Document no longer exists, clean up any grants
+ revokeDocumentPermission(documentId);
- final String newDocumentId = copyDocument(documentId, targetId);
+ } else if (METHOD_COPY_DOCUMENT.equals(method)) {
+ final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+ final String targetId = DocumentsContract.getDocumentId(targetUri);
- if (newDocumentId != null) {
- final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
- newDocumentId);
+ enforceReadPermissionInner(documentUri, getCallingPackage(), null);
+ enforceWritePermissionInner(targetUri, getCallingPackage(), null);
- if (!isTreeUri(newDocumentUri)) {
- final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
- documentUri);
- context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
- }
+ final String newDocumentId = copyDocument(documentId, targetId);
+
+ if (newDocumentId != null) {
+ final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
+ newDocumentId);
- out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+ if (!isTreeUri(newDocumentUri)) {
+ final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
+ documentUri);
+ context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
}
- } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
- final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
- final String targetId = DocumentsContract.getDocumentId(targetUri);
+ out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+ }
- enforceReadPermissionInner(documentUri, getCallingPackage(), null);
- enforceWritePermissionInner(targetUri, getCallingPackage(), null);
+ } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
+ final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+ final String targetId = DocumentsContract.getDocumentId(targetUri);
- final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
- final String newDocumentId = moveDocument(documentId, targetId);
+ enforceReadPermissionInner(documentUri, getCallingPackage(), null);
+ enforceWritePermissionInner(targetUri, getCallingPackage(), null);
- if (newDocumentId != null) {
- final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
- newDocumentId);
+ final String newDocumentId = moveDocument(documentId, targetId);
- if (!isTreeUri(newDocumentUri)) {
- final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
- documentUri);
- context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
- }
+ if (newDocumentId != null) {
+ final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
+ newDocumentId);
- out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+ if (!isTreeUri(newDocumentUri)) {
+ final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
+ documentUri);
+ context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
}
- // Original document no longer exists, clean up any grants
- revokeDocumentPermission(documentId);
-
- } else {
- throw new UnsupportedOperationException("Method not supported " + method);
+ out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
}
- } catch (FileNotFoundException e) {
- throw new IllegalStateException("Failed call " + method, e);
+
+ // Original document no longer exists, clean up any grants
+ revokeDocumentPermission(documentId);
+
+ } else {
+ throw new UnsupportedOperationException("Method not supported " + method);
}
+
return out;
}
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 1973ef1825e9..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,27 +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();
- if (!mKeyStore.isCertificateEntry(alias)) {
- continue;
- }
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/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index b3399d063807..541623d736f1 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1110,7 +1110,7 @@ public class ZenModeConfig implements Parcelable {
if (!Objects.equals(id, to.id)) {
d.addLine(item, "id", id, to.id);
}
- if (creationTime == to.creationTime) {
+ if (creationTime != to.creationTime) {
d.addLine(item, "creationTime", creationTime, to.creationTime);
}
}
diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java
index 2b882d3154a6..c7a4ccc183e8 100644
--- a/core/java/android/speech/tts/FileSynthesisCallback.java
+++ b/core/java/android/speech/tts/FileSynthesisCallback.java
@@ -15,6 +15,7 @@
*/
package android.speech.tts;
+import android.annotation.NonNull;
import android.media.AudioFormat;
import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
import android.util.Log;
@@ -46,7 +47,6 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
private FileChannel mFileChannel;
private final UtteranceProgressDispatcher mDispatcher;
- private final Object mCallerIdentity;
private boolean mStarted = false;
private boolean mDone = false;
@@ -54,12 +54,11 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
/** Status code of synthesis */
protected int mStatusCode;
- FileSynthesisCallback(FileChannel fileChannel, UtteranceProgressDispatcher dispatcher,
- Object callerIdentity, boolean clientIsUsingV2) {
+ FileSynthesisCallback(@NonNull FileChannel fileChannel,
+ @NonNull UtteranceProgressDispatcher dispatcher, boolean clientIsUsingV2) {
super(clientIsUsingV2);
mFileChannel = fileChannel;
mDispatcher = dispatcher;
- mCallerIdentity = callerIdentity;
mStatusCode = TextToSpeech.SUCCESS;
}
@@ -75,9 +74,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
mStatusCode = TextToSpeech.STOPPED;
cleanUp();
- if (mDispatcher != null) {
- mDispatcher.dispatchOnStop();
- }
+ mDispatcher.dispatchOnStop();
}
}
@@ -134,9 +131,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
mAudioFormat = audioFormat;
mChannelCount = channelCount;
- if (mDispatcher != null) {
- mDispatcher.dispatchOnStart();
- }
+ mDispatcher.dispatchOnStart();
fileChannel = mFileChannel;
}
@@ -214,8 +209,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
if (DBG) Log.d(TAG, "Request has been aborted.");
return errorCodeOnStop();
}
- if (mDispatcher != null && mStatusCode != TextToSpeech.SUCCESS &&
- mStatusCode != TextToSpeech.STOPPED) {
+ if (mStatusCode != TextToSpeech.SUCCESS && mStatusCode != TextToSpeech.STOPPED) {
mDispatcher.dispatchOnError(mStatusCode);
return TextToSpeech.ERROR;
}
@@ -239,9 +233,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
synchronized (mStateLock) {
closeFile();
- if (mDispatcher != null) {
- mDispatcher.dispatchOnSuccess();
- }
+ mDispatcher.dispatchOnSuccess();
return TextToSpeech.SUCCESS;
}
} catch (IOException ex) {
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index c3aed753efbd..8c355d8d63de 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -1032,8 +1032,7 @@ public abstract class TextToSpeechService extends Service {
@Override
protected AbstractSynthesisCallback createSynthesisCallback() {
- return new FileSynthesisCallback(mFileOutputStream.getChannel(),
- this, getCallerIdentity(), false);
+ return new FileSynthesisCallback(mFileOutputStream.getChannel(), this, false);
}
@Override
diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java
index f2b6041457de..5d9d92910d1f 100644
--- a/core/java/android/text/Hyphenator.java
+++ b/core/java/android/text/Hyphenator.java
@@ -166,7 +166,7 @@ public class Hyphenator {
sMap.put(null, null);
// TODO: replace this with a discovery-based method that looks into /system/usr/hyphen-data
- String[] availableLanguages = {"en-US", "eu", "hu", "hy", "nb", "nn", "und-Ethi"};
+ String[] availableLanguages = {"en-US", "es", "eu", "hu", "hy", "nb", "nn", "und-Ethi"};
for (int i = 0; i < availableLanguages.length; i++) {
String languageTag = availableLanguages[i];
Hyphenator h = loadHyphenator(languageTag);
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 ab1943c49c6e..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();
@@ -22369,7 +22370,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Used to track views that need (at least) a partial relayout at their current size
* during the next traversal.
*/
- final List<View> mPartialLayoutViews = new ArrayList<View>();
+ List<View> mPartialLayoutViews = new ArrayList<>();
+
+ /**
+ * Swapped with mPartialLayoutViews during layout to avoid concurrent
+ * modification. Lazily assigned during ViewRootImpl layout.
+ */
+ List<View> mEmptyPartialLayoutViews;
/**
* Used to track the identity of the current drag operation.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b503e12a2d1d..3c9310da6f8b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1974,6 +1974,15 @@ public final class ViewRootImpl implements ViewParent,
final List<View> partialLayoutViews = mAttachInfo.mPartialLayoutViews;
final boolean didPartialLayout;
if (!partialLayoutViews.isEmpty()) {
+ // Measurement or layout of views may result in changes to the list
+ // of partial-layout views. Swap in an "empty" list to prevent
+ // concurrent modification of the list being traversed.
+ if (mAttachInfo.mEmptyPartialLayoutViews == null) {
+ mAttachInfo.mPartialLayoutViews = new ArrayList<>();
+ } else {
+ mAttachInfo.mPartialLayoutViews = mAttachInfo.mEmptyPartialLayoutViews;
+ }
+
final int count = partialLayoutViews.size();
mInLayout = true;
for (int i = 0; i < count; i++) {
@@ -1992,9 +2001,12 @@ public final class ViewRootImpl implements ViewParent,
}
}
mInLayout = false;
- partialLayoutViews.clear();
didPartialLayout = true;
triggerGlobalLayoutListener = true;
+
+ // The traversal list becomes the new empty list.
+ partialLayoutViews.clear();
+ mAttachInfo.mEmptyPartialLayoutViews = partialLayoutViews;
} else {
didPartialLayout = false;
}
diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java
index b3b95f8c9dd2..f31389350519 100644
--- a/core/java/android/webkit/FindActionModeCallback.java
+++ b/core/java/android/webkit/FindActionModeCallback.java
@@ -61,8 +61,7 @@ public class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
setText("");
mMatches = (TextView) mCustomView.findViewById(
com.android.internal.R.id.matches);
- mInput = (InputMethodManager)
- context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ mInput = context.getSystemService(InputMethodManager.class);
mResources = context.getResources();
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 90de0539df92..f050e49f0b05 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -5755,8 +5755,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// The editor is off in its own window; we need to be
// the one that does this.
if (editorAction == EditorInfo.IME_ACTION_DONE) {
- InputMethodManager imm = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager imm =
+ getContext().getSystemService(InputMethodManager.class);
if (imm != null) {
imm.hideSoftInputFromWindow(getWindowToken(), 0);
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index c3d0993b3b77..5146bc67c060 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -17,6 +17,7 @@
package android.widget;
import android.R;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
@@ -106,6 +107,8 @@ import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.EditableInputConnection;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.text.BreakIterator;
import java.util.Arrays;
import java.util.Comparator;
@@ -4087,7 +4090,17 @@ public class Editor {
}
}
- private class SelectionStartHandleView extends HandleView {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({HANDLE_TYPE_SELECTION_START, HANDLE_TYPE_SELECTION_END})
+ public @interface HandleType {}
+ public static final int HANDLE_TYPE_SELECTION_START = 0;
+ public static final int HANDLE_TYPE_SELECTION_END = 1;
+
+ private class SelectionHandleView extends HandleView {
+ // Indicates the handle type, selection start (HANDLE_TYPE_SELECTION_START) or selection
+ // end (HANDLE_TYPE_SELECTION_END).
+ @HandleType
+ private final int mHandleType;
// Indicates whether the cursor is making adjustments within a word.
private boolean mInWord = false;
// Difference between touch position and word boundary position.
@@ -4102,16 +4115,21 @@ public class Editor {
// Used to save text view location.
private final int[] mTextViewLocation = new int[2];
- public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
- super(drawableLtr, drawableRtl, com.android.internal.R.id.selection_start_handle);
- ViewConfiguration viewConfiguration = ViewConfiguration.get(
- mTextView.getContext());
+ public SelectionHandleView(Drawable drawableLtr, Drawable drawableRtl, int id,
+ @HandleType int handleType) {
+ super(drawableLtr, drawableRtl, id);
+ mHandleType = handleType;
+ ViewConfiguration viewConfiguration = ViewConfiguration.get(mTextView.getContext());
mTextViewEdgeSlop = viewConfiguration.getScaledTouchSlop() * 4;
}
+ private boolean isStartHandle() {
+ return mHandleType == HANDLE_TYPE_SELECTION_START;
+ }
+
@Override
protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
- if (isRtlRun) {
+ if (isRtlRun == isStartHandle()) {
return drawable.getIntrinsicWidth() / 4;
} else {
return (drawable.getIntrinsicWidth() * 3) / 4;
@@ -4120,18 +4138,23 @@ public class Editor {
@Override
protected int getHorizontalGravity(boolean isRtlRun) {
- return isRtlRun ? Gravity.LEFT : Gravity.RIGHT;
+ return (isRtlRun == isStartHandle()) ? Gravity.LEFT : Gravity.RIGHT;
}
@Override
public int getCurrentCursorOffset() {
- return mTextView.getSelectionStart();
+ return isStartHandle() ? mTextView.getSelectionStart() : mTextView.getSelectionEnd();
}
@Override
- public void updateSelection(int offset) {
- Selection.setSelection((Spannable) mTextView.getText(), offset,
- mTextView.getSelectionEnd());
+ protected void updateSelection(int offset) {
+ if (isStartHandle()) {
+ Selection.setSelection((Spannable) mTextView.getText(), offset,
+ mTextView.getSelectionEnd());
+ } else {
+ Selection.setSelection((Spannable) mTextView.getText(),
+ mTextView.getSelectionStart(), offset);
+ }
updateDrawable();
if (mTextActionMode != null) {
mTextActionMode.invalidate();
@@ -4153,35 +4176,36 @@ public class Editor {
}
boolean positionCursor = false;
- final int selectionEnd = mTextView.getSelectionEnd();
+ final int anotherHandleOffset =
+ isStartHandle() ? mTextView.getSelectionEnd() : mTextView.getSelectionStart();
int currLine = getCurrentLineAdjustedForSlop(layout, mPreviousLineTouched, y);
int initialOffset = mTextView.getOffsetAtCoordinate(currLine, x);
- if (initialOffset >= selectionEnd) {
- // Handles have crossed, bound it to the last selected line and
+ if (isStartHandle() && initialOffset >= anotherHandleOffset
+ || !isStartHandle() && initialOffset <= anotherHandleOffset) {
+ // Handles have crossed, bound it to the first selected line and
// adjust by word / char as normal.
- currLine = layout.getLineForOffset(selectionEnd);
+ currLine = layout.getLineForOffset(anotherHandleOffset);
initialOffset = mTextView.getOffsetAtCoordinate(currLine, x);
}
int offset = initialOffset;
- int end = getWordEnd(offset);
- int start = getWordStart(offset);
+ final int wordEnd = getWordEnd(offset);
+ final int wordStart = getWordStart(offset);
if (mPrevX == UNSET_X_VALUE) {
mPrevX = x;
}
- final int selectionStart = mTextView.getSelectionStart();
- final boolean selectionStartRtl = layout.isRtlCharAt(selectionStart);
+ final int currentOffset = getCurrentCursorOffset();
+ final boolean rtlAtCurrentOffset = layout.isRtlCharAt(currentOffset);
final boolean atRtl = layout.isRtlCharAt(offset);
final boolean isLvlBoundary = layout.isLevelBoundary(offset);
- boolean isExpanding;
// We can't determine if the user is expanding or shrinking the selection if they're
// on a bi-di boundary, so until they've moved past the boundary we'll just place
// the cursor at the current position.
- if (isLvlBoundary || (selectionStartRtl && !atRtl) || (!selectionStartRtl && atRtl)) {
+ if (isLvlBoundary || (rtlAtCurrentOffset && !atRtl) || (!rtlAtCurrentOffset && atRtl)) {
// We're on a boundary or this is the first direction change -- just update
// to the current position.
mLanguageDirectionChanged = true;
@@ -4195,24 +4219,30 @@ public class Editor {
mTouchWordDelta = 0.0f;
mLanguageDirectionChanged = false;
return;
+ }
+
+ boolean isExpanding;
+ final float xDiff = x - mPrevX;
+ if (atRtl == isStartHandle()) {
+ isExpanding = xDiff > 0 || currLine > mPreviousLineTouched;
} else {
- final float xDiff = x - mPrevX;
- if (atRtl) {
- isExpanding = xDiff > 0 || currLine > mPreviousLineTouched;
- } else {
- isExpanding = xDiff < 0 || currLine < mPreviousLineTouched;
- }
+ isExpanding = xDiff < 0 || currLine < mPreviousLineTouched;
}
if (mTextView.getHorizontallyScrolling()) {
if (positionNearEdgeOfScrollingView(x, atRtl)
- && (mTextView.getScrollX() != 0)
- && ((isExpanding && offset < selectionStart) || !isExpanding)) {
- // If we're expanding ensure that the offset is smaller than the
- // selection start, if the handle snapped to the word, the finger position
+ && ((isStartHandle() && mTextView.getScrollX() != 0)
+ || (!isStartHandle()
+ && mTextView.canScrollHorizontally(atRtl ? -1 : 1)))
+ && ((isExpanding && ((isStartHandle() && offset < currentOffset)
+ || (!isStartHandle() && offset > currentOffset)))
+ || !isExpanding)) {
+ // If we're expanding ensure that the offset is actually expanding compared to
+ // the current offset, if the handle snapped to the word, the finger position
// may be out of sync and we don't want the selection to jump back.
mTouchWordDelta = 0.0f;
- final int nextOffset = atRtl ? layout.getOffsetToRightOf(mPreviousOffset)
+ final int nextOffset = (atRtl == isStartHandle())
+ ? layout.getOffsetToRightOf(mPreviousOffset)
: layout.getOffsetToLeftOf(mPreviousOffset);
positionAndAdjustForCrossingHandles(nextOffset);
return;
@@ -4221,24 +4251,36 @@ public class Editor {
if (isExpanding) {
// User is increasing the selection.
- if (!mInWord || currLine < mPrevLine) {
+ final boolean snapToWord = !mInWord
+ || (isStartHandle() ? currLine < mPrevLine : currLine > mPrevLine);
+ if (snapToWord) {
// Sometimes words can be broken across lines (Chinese, hyphenation).
- // We still snap to the start of the word but we only use the letters on the
+ // We still snap to the word boundary but we only use the letters on the
// current line to determine if the user is far enough into the word to snap.
- int wordStartOnCurrLine = start;
- if (layout != null && layout.getLineForOffset(start) != currLine) {
- wordStartOnCurrLine = layout.getLineStart(currLine);
+ int wordBoundary = isStartHandle() ? wordStart : wordEnd;
+ if (layout != null && layout.getLineForOffset(wordBoundary) != currLine) {
+ wordBoundary = isStartHandle() ?
+ layout.getLineStart(currLine) : layout.getLineEnd(currLine);
}
- int offsetThresholdToSnap = end - ((end - wordStartOnCurrLine) / 2);
- if (offset <= offsetThresholdToSnap || currLine < mPrevLine) {
- // User is far enough into the word or on a different
- // line so we expand by word.
- offset = start;
+ final int offsetThresholdToSnap = isStartHandle()
+ ? wordEnd - ((wordEnd - wordBoundary) / 2)
+ : wordStart + ((wordBoundary - wordStart) / 2);
+ if (isStartHandle()
+ && (offset <= offsetThresholdToSnap || currLine < mPrevLine)) {
+ // User is far enough into the word or on a different line so we expand by
+ // word.
+ offset = wordStart;
+ } else if (!isStartHandle()
+ && (offset >= offsetThresholdToSnap || currLine > mPrevLine)) {
+ // User is far enough into the word or on a different line so we expand by
+ // word.
+ offset = wordEnd;
} else {
offset = mPreviousOffset;
}
}
- if (layout != null && offset < initialOffset) {
+ if (layout != null && (isStartHandle() && offset < initialOffset)
+ || (!isStartHandle() && offset > initialOffset)) {
final float adjustedX = layout.getPrimaryHorizontal(offset);
mTouchWordDelta =
mTextView.convertToLocalHorizontalCoordinate(x) - adjustedX;
@@ -4249,12 +4291,16 @@ public class Editor {
} else {
final int adjustedOffset =
mTextView.getOffsetAtCoordinate(currLine, x - mTouchWordDelta);
- if (adjustedOffset > mPreviousOffset || currLine > mPrevLine) {
+ final boolean shrinking = isStartHandle()
+ ? adjustedOffset > mPreviousOffset || currLine > mPrevLine
+ : adjustedOffset < mPreviousOffset || currLine < mPrevLine;
+ if (shrinking) {
// User is shrinking the selection.
- if (currLine > mPrevLine) {
+ if (currLine != mPrevLine) {
// We're on a different line, so we'll snap to word boundaries.
- offset = start;
- if (layout != null && offset < initialOffset) {
+ offset = isStartHandle() ? wordStart : wordEnd;
+ if (layout != null && (isStartHandle() && offset < initialOffset)
+ || (!isStartHandle() && offset > initialOffset)) {
final float adjustedX = layout.getPrimaryHorizontal(offset);
mTouchWordDelta =
mTextView.convertToLocalHorizontalCoordinate(x) - adjustedX;
@@ -4265,11 +4311,12 @@ public class Editor {
offset = adjustedOffset;
}
positionCursor = true;
- } else if (adjustedOffset < mPreviousOffset) {
- // Handle has jumped to the start of the word, and the user is moving
+ } else if ((isStartHandle() && adjustedOffset < mPreviousOffset)
+ || (!isStartHandle() && adjustedOffset > mPreviousOffset)) {
+ // Handle has jumped to the word boundary, and the user is moving
// their finger towards the handle, the delta should be updated.
- mTouchWordDelta = mTextView.convertToLocalHorizontalCoordinate(x)
- - layout.getPrimaryHorizontal(mPreviousOffset);
+ mTouchWordDelta = mTextView.convertToLocalHorizontalCoordinate(x) -
+ layout.getPrimaryHorizontal(mPreviousOffset);
}
}
@@ -4280,16 +4327,6 @@ public class Editor {
mPrevX = x;
}
- private void positionAndAdjustForCrossingHandles(int offset) {
- final int selectionEnd = mTextView.getSelectionEnd();
- if (offset >= selectionEnd) {
- // Handles can not cross and selection is at least one character.
- offset = getNextCursorOffset(selectionEnd, false);
- mTouchWordDelta = 0.0f;
- }
- positionAtCursorOffset(offset, false);
- }
-
/**
* @param offset Cursor offset. Must be in [-1, length].
* @param parentScrolled If the parent has been scrolled or not.
@@ -4312,256 +4349,28 @@ public class Editor {
return superResult;
}
- private boolean positionNearEdgeOfScrollingView(float x, boolean atRtl) {
- mTextView.getLocationOnScreen(mTextViewLocation);
- boolean nearEdge;
- if (atRtl) {
- int rightEdge = mTextViewLocation[0] + mTextView.getWidth()
- - mTextView.getPaddingRight();
- nearEdge = x > rightEdge - mTextViewEdgeSlop;
- } else {
- int leftEdge = mTextViewLocation[0] + mTextView.getPaddingLeft();
- nearEdge = x < leftEdge + mTextViewEdgeSlop;
- }
- return nearEdge;
- }
- }
-
- private class SelectionEndHandleView extends HandleView {
- // Indicates whether the cursor is making adjustments within a word.
- private boolean mInWord = false;
- // Difference between touch position and word boundary position.
- private float mTouchWordDelta;
- // X value of the previous updatePosition call.
- private float mPrevX;
- // Indicates if the handle has moved a boundary between LTR and RTL text.
- private boolean mLanguageDirectionChanged = false;
- // Distance from edge of horizontally scrolling text view
- // to use to switch to character mode.
- private final float mTextViewEdgeSlop;
- // Used to save the text view location.
- private final int[] mTextViewLocation = new int[2];
-
- public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
- super(drawableLtr, drawableRtl, com.android.internal.R.id.selection_end_handle);
- ViewConfiguration viewConfiguration = ViewConfiguration.get(
- mTextView.getContext());
- mTextViewEdgeSlop = viewConfiguration.getScaledTouchSlop() * 4;
- }
-
- @Override
- protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
- if (isRtlRun) {
- return (drawable.getIntrinsicWidth() * 3) / 4;
- } else {
- return drawable.getIntrinsicWidth() / 4;
- }
- }
-
- @Override
- protected int getHorizontalGravity(boolean isRtlRun) {
- return isRtlRun ? Gravity.RIGHT : Gravity.LEFT;
- }
-
- @Override
- public int getCurrentCursorOffset() {
- return mTextView.getSelectionEnd();
- }
-
- @Override
- public void updateSelection(int offset) {
- Selection.setSelection((Spannable) mTextView.getText(),
- mTextView.getSelectionStart(), offset);
- if (mTextActionMode != null) {
- mTextActionMode.invalidate();
- }
- updateDrawable();
- }
-
- @Override
- public void updatePosition(float x, float y) {
- final Layout layout = mTextView.getLayout();
- if (layout == null) {
- // HandleView will deal appropriately in positionAtCursorOffset when
- // layout is null.
- positionAndAdjustForCrossingHandles(mTextView.getOffsetForPosition(x, y));
- return;
- }
-
- if (mPreviousLineTouched == UNSET_LINE) {
- mPreviousLineTouched = mTextView.getLineAtCoordinate(y);
- }
-
- boolean positionCursor = false;
- final int selectionStart = mTextView.getSelectionStart();
- int currLine = getCurrentLineAdjustedForSlop(layout, mPreviousLineTouched, y);
- int initialOffset = mTextView.getOffsetAtCoordinate(currLine, x);
-
- if (initialOffset <= selectionStart) {
- // Handles have crossed, bound it to the first selected line and
- // adjust by word / char as normal.
- currLine = layout.getLineForOffset(selectionStart);
- initialOffset = mTextView.getOffsetAtCoordinate(currLine, x);
- }
-
- int offset = initialOffset;
- int end = getWordEnd(offset);
- int start = getWordStart(offset);
-
- if (mPrevX == UNSET_X_VALUE) {
- mPrevX = x;
- }
-
- final int selectionEnd = mTextView.getSelectionEnd();
- final boolean selectionEndRtl = layout.isRtlCharAt(selectionEnd);
- final boolean atRtl = layout.isRtlCharAt(offset);
- final boolean isLvlBoundary = layout.isLevelBoundary(offset);
- boolean isExpanding;
-
- // We can't determine if the user is expanding or shrinking the selection if they're
- // on a bi-di boundary, so until they've moved past the boundary we'll just place
- // the cursor at the current position.
- if (isLvlBoundary || (selectionEndRtl && !atRtl) || (!selectionEndRtl && atRtl)) {
- // We're on a boundary or this is the first direction change -- just update
- // to the current position.
- mLanguageDirectionChanged = true;
- mTouchWordDelta = 0.0f;
- positionAndAdjustForCrossingHandles(offset);
- return;
- } else if (mLanguageDirectionChanged && !isLvlBoundary) {
- // We've just moved past the boundary so update the position. After this we can
- // figure out if the user is expanding or shrinking to go by word or character.
- positionAndAdjustForCrossingHandles(offset);
- mTouchWordDelta = 0.0f;
- mLanguageDirectionChanged = false;
- return;
- } else {
- final float xDiff = x - mPrevX;
- if (atRtl) {
- isExpanding = xDiff < 0 || currLine < mPreviousLineTouched;
- } else {
- isExpanding = xDiff > 0 || currLine > mPreviousLineTouched;
- }
- }
-
- if (mTextView.getHorizontallyScrolling()) {
- if (positionNearEdgeOfScrollingView(x, atRtl)
- && mTextView.canScrollHorizontally(atRtl ? -1 : 1)
- && ((isExpanding && offset > selectionEnd) || !isExpanding)) {
- // If we're expanding ensure that the offset is actually greater than the
- // selection end, if the handle snapped to the word, the finger position
- // may be out of sync and we don't want the selection to jump back.
- mTouchWordDelta = 0.0f;
- final int nextOffset = atRtl ? layout.getOffsetToLeftOf(mPreviousOffset)
- : layout.getOffsetToRightOf(mPreviousOffset);
- positionAndAdjustForCrossingHandles(nextOffset);
- return;
- }
- }
-
- if (isExpanding) {
- // User is increasing the selection.
- if (!mInWord || currLine > mPrevLine) {
- // Sometimes words can be broken across lines (Chinese, hyphenation).
- // We still snap to the end of the word but we only use the letters on the
- // current line to determine if the user is far enough into the word to snap.
- int wordEndOnCurrLine = end;
- if (layout != null && layout.getLineForOffset(end) != currLine) {
- wordEndOnCurrLine = layout.getLineEnd(currLine);
- }
- final int offsetThresholdToSnap = start + ((wordEndOnCurrLine - start) / 2);
- if (offset >= offsetThresholdToSnap || currLine > mPrevLine) {
- // User is far enough into the word or on a different
- // line so we expand by word.
- offset = end;
- } else {
- offset = mPreviousOffset;
- }
- }
- if (offset > initialOffset) {
- final float adjustedX = layout.getPrimaryHorizontal(offset);
- mTouchWordDelta =
- adjustedX - mTextView.convertToLocalHorizontalCoordinate(x);
- } else {
- mTouchWordDelta = 0.0f;
- }
- positionCursor = true;
- } else {
- final int adjustedOffset =
- mTextView.getOffsetAtCoordinate(currLine, x + mTouchWordDelta);
- if (adjustedOffset < mPreviousOffset || currLine < mPrevLine) {
- // User is shrinking the selection.
- if (currLine < mPrevLine) {
- // We're on a different line, so we'll snap to word boundaries.
- offset = end;
- if (offset > initialOffset) {
- final float adjustedX = layout.getPrimaryHorizontal(offset);
- mTouchWordDelta =
- adjustedX - mTextView.convertToLocalHorizontalCoordinate(x);
- } else {
- mTouchWordDelta = 0.0f;
- }
- } else {
- offset = adjustedOffset;
- }
- positionCursor = true;
- } else if (adjustedOffset > mPreviousOffset) {
- // Handle has jumped to the end of the word, and the user is moving
- // their finger towards the handle, the delta should be updated.
- mTouchWordDelta = layout.getPrimaryHorizontal(mPreviousOffset)
- - mTextView.convertToLocalHorizontalCoordinate(x);
- }
- }
-
- if (positionCursor) {
- mPreviousLineTouched = currLine;
- positionAndAdjustForCrossingHandles(offset);
- }
- mPrevX = x;
- }
-
private void positionAndAdjustForCrossingHandles(int offset) {
- final int selectionStart = mTextView.getSelectionStart();
- if (offset <= selectionStart) {
+ final int anotherHandleOffset =
+ isStartHandle() ? mTextView.getSelectionEnd() : mTextView.getSelectionStart();
+ if ((isStartHandle() && offset >= anotherHandleOffset)
+ || (!isStartHandle() && offset <= anotherHandleOffset)) {
// Handles can not cross and selection is at least one character.
- offset = getNextCursorOffset(selectionStart, true);
+ offset = getNextCursorOffset(anotherHandleOffset, !isStartHandle());
mTouchWordDelta = 0.0f;
}
positionAtCursorOffset(offset, false);
}
- /**
- * @param offset Cursor offset. Must be in [-1, length].
- * @param parentScrolled If the parent has been scrolled or not.
- */
- @Override
- protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
- super.positionAtCursorOffset(offset, parentScrolled);
- mInWord = (offset != -1) && !getWordIteratorWithText().isBoundary(offset);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- boolean superResult = super.onTouchEvent(event);
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- // Reset the touch word offset and x value when the user
- // re-engages the handle.
- mTouchWordDelta = 0.0f;
- mPrevX = UNSET_X_VALUE;
- }
- return superResult;
- }
-
private boolean positionNearEdgeOfScrollingView(float x, boolean atRtl) {
mTextView.getLocationOnScreen(mTextViewLocation);
boolean nearEdge;
- if (atRtl) {
- int leftEdge = mTextViewLocation[0] + mTextView.getPaddingLeft();
- nearEdge = x < leftEdge + mTextViewEdgeSlop;
- } else {
+ if (atRtl == isStartHandle()) {
int rightEdge = mTextViewLocation[0] + mTextView.getWidth()
- mTextView.getPaddingRight();
nearEdge = x > rightEdge - mTextViewEdgeSlop;
+ } else {
+ int leftEdge = mTextViewLocation[0] + mTextView.getPaddingLeft();
+ nearEdge = x < leftEdge + mTextViewEdgeSlop;
}
return nearEdge;
}
@@ -4673,8 +4482,8 @@ public class Editor {
class SelectionModifierCursorController implements CursorController {
// The cursor controller handles, lazily created when shown.
- private SelectionStartHandleView mStartHandle;
- private SelectionEndHandleView mEndHandle;
+ private SelectionHandleView mStartHandle;
+ private SelectionHandleView mEndHandle;
// The offsets of that last touch down event. Remembered to start selection there.
private int mMinTouchOffset, mMaxTouchOffset;
@@ -4718,10 +4527,14 @@ public class Editor {
private void initHandles() {
// Lazy object creation has to be done before updatePosition() is called.
if (mStartHandle == null) {
- mStartHandle = new SelectionStartHandleView(mSelectHandleLeft, mSelectHandleRight);
+ mStartHandle = new SelectionHandleView(mSelectHandleLeft, mSelectHandleRight,
+ com.android.internal.R.id.selection_start_handle,
+ HANDLE_TYPE_SELECTION_START);
}
if (mEndHandle == null) {
- mEndHandle = new SelectionEndHandleView(mSelectHandleRight, mSelectHandleLeft);
+ mEndHandle = new SelectionHandleView(mSelectHandleRight, mSelectHandleLeft,
+ com.android.internal.R.id.selection_end_handle,
+ HANDLE_TYPE_SELECTION_END);
}
mStartHandle.show();
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index ce1c10863acd..0dd803a23d30 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1638,6 +1638,48 @@ 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);
+ target.setLayoutParams(layoutParams);
+ }
+ }
+
+ 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 +1984,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 +2594,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/SearchView.java b/core/java/android/widget/SearchView.java
index ad2b4a714b82..88f02d1fcd5f 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -152,8 +152,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
*/
private Runnable mShowImeRunnable = new Runnable() {
public void run() {
- InputMethodManager imm = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
if (imm != null) {
imm.showSoftInputUnchecked(0, null);
@@ -912,8 +911,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
post(mShowImeRunnable);
} else {
removeCallbacks(mShowImeRunnable);
- InputMethodManager imm = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
if (imm != null) {
imm.hideSoftInputFromWindow(getWindowToken(), 0);
@@ -1768,8 +1766,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus && mSearchView.hasFocus() && getVisibility() == VISIBLE) {
- InputMethodManager inputManager = (InputMethodManager) getContext()
- .getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager inputManager =
+ getContext().getSystemService(InputMethodManager.class);
inputManager.showSoftInput(this, 0);
// If in landscape mode, then make sure that
// the ime is in front of the dropdown.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 476c6a268c82..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;
@@ -7225,6 +7228,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// of the intended optimizations as part of requestLayoutForChild.
nullLayouts();
requestLayout();
+ invalidate();
}
@Override
@@ -9707,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/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 9391c6038fbc..4a969b28e784 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -105,7 +105,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 136 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 138 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -2621,10 +2621,9 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- public void noteProcessStateLocked(String name, int uid, int state) {
+ public void noteUidProcessStateLocked(int uid, int state) {
uid = mapUid(uid);
- final long elapsedRealtime = SystemClock.elapsedRealtime();
- getUidStatsLocked(uid).updateProcessStateLocked(name, state, elapsedRealtime);
+ getUidStatsLocked(uid).updateUidProcessStateLocked(state);
}
public void noteProcessFinishLocked(String name, int uid) {
@@ -2632,13 +2631,11 @@ public final class BatteryStatsImpl extends BatteryStats {
if (!mActiveEvents.updateState(HistoryItem.EVENT_PROC_FINISH, name, uid, 0)) {
return;
}
- final long elapsedRealtime = SystemClock.elapsedRealtime();
- final long uptime = SystemClock.uptimeMillis();
- getUidStatsLocked(uid).updateProcessStateLocked(name, Uid.PROCESS_STATE_NONE,
- elapsedRealtime);
if (!mRecordAllHistory) {
return;
}
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_PROC_FINISH, name, uid);
}
@@ -4446,8 +4443,7 @@ public final class BatteryStatsImpl extends BatteryStats {
StopwatchTimer mForegroundActivityTimer;
- static final int PROCESS_STATE_NONE = NUM_PROCESS_STATE;
- int mProcessState = PROCESS_STATE_NONE;
+ int mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
StopwatchTimer[] mProcessStateTimer;
BatchTimer mVibratorOnTimer;
@@ -4812,21 +4808,6 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- void updateUidProcessStateLocked(int state, long elapsedRealtimeMs) {
- if (mProcessState == state) return;
-
- if (mProcessState != PROCESS_STATE_NONE) {
- mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
- }
- mProcessState = state;
- if (state != PROCESS_STATE_NONE) {
- if (mProcessStateTimer[state] == null) {
- makeProcessState(state, null);
- }
- mProcessStateTimer[state].startRunningLocked(elapsedRealtimeMs);
- }
- }
-
public BatchTimer createVibratorOnTimerLocked() {
if (mVibratorOnTimer == null) {
mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON, mOnBatteryTimeBase);
@@ -5167,7 +5148,7 @@ public final class BatteryStatsImpl extends BatteryStats {
active |= !mProcessStateTimer[i].reset(false);
}
}
- active |= (mProcessState != PROCESS_STATE_NONE);
+ active |= (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT);
}
if (mVibratorOnTimer != null) {
if (mVibratorOnTimer.reset(false)) {
@@ -5261,14 +5242,9 @@ public final class BatteryStatsImpl extends BatteryStats {
}
for (int ip=mProcessStats.size()-1; ip>=0; ip--) {
Proc proc = mProcessStats.valueAt(ip);
- if (proc.mProcessState == PROCESS_STATE_NONE) {
- proc.detach();
- mProcessStats.removeAt(ip);
- } else {
- proc.reset();
- active = true;
- }
+ proc.detach();
}
+ mProcessStats.clear();
if (mPids.size() > 0) {
for (int i=mPids.size()-1; i>=0; i--) {
Pid pid = mPids.valueAt(i);
@@ -5697,7 +5673,7 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
mForegroundActivityTimer = null;
}
- mProcessState = PROCESS_STATE_NONE;
+ mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
for (int i = 0; i < NUM_PROCESS_STATE; i++) {
if (in.readInt() != 0) {
makeProcessState(i, in);
@@ -6080,11 +6056,6 @@ public final class BatteryStatsImpl extends BatteryStats {
*/
int mUnpluggedNumAnrs;
- /**
- * Current process state.
- */
- int mProcessState = PROCESS_STATE_NONE;
-
ArrayList<ExcessivePower> mExcessivePower;
Proc(String name) {
@@ -6104,16 +6075,6 @@ public final class BatteryStatsImpl extends BatteryStats {
public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
}
- void reset() {
- mUserTime = mSystemTime = mForegroundTime = 0;
- mStarts = mNumCrashes = mNumAnrs = 0;
- mLoadedUserTime = mLoadedSystemTime = mLoadedForegroundTime = 0;
- mLoadedStarts = mLoadedNumCrashes = mLoadedNumAnrs = 0;
- mUnpluggedUserTime = mUnpluggedSystemTime = mUnpluggedForegroundTime = 0;
- mUnpluggedStarts = mUnpluggedNumCrashes = mUnpluggedNumAnrs = 0;
- mExcessivePower = null;
- }
-
void detach() {
mActive = false;
mOnBatteryTimeBase.remove(this);
@@ -6668,46 +6629,39 @@ public final class BatteryStatsImpl extends BatteryStats {
return ps;
}
- public void updateProcessStateLocked(String procName, int state, long elapsedRealtimeMs) {
- int procState;
- if (state <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- procState = PROCESS_STATE_FOREGROUND;
- } else if (state <= ActivityManager.PROCESS_STATE_RECEIVER) {
- procState = PROCESS_STATE_ACTIVE;
+ public void updateUidProcessStateLocked(int procState) {
+ int uidRunningState;
+ if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ uidRunningState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+ } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
+ uidRunningState = PROCESS_STATE_TOP;
+ } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ // Persistent and other foreground states go here.
+ uidRunningState = PROCESS_STATE_FOREGROUND_SERVICE;
+ } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+ uidRunningState = PROCESS_STATE_TOP_SLEEPING;
+ } else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ // Persistent and other foreground states go here.
+ uidRunningState = PROCESS_STATE_FOREGROUND;
+ } else if (procState <= ActivityManager.PROCESS_STATE_RECEIVER) {
+ uidRunningState = PROCESS_STATE_BACKGROUND;
} else {
- procState = PROCESS_STATE_RUNNING;
+ uidRunningState = PROCESS_STATE_CACHED;
}
- updateRealProcessStateLocked(procName, procState, elapsedRealtimeMs);
- }
- public void updateRealProcessStateLocked(String procName, int procState,
- long elapsedRealtimeMs) {
- Proc proc = getProcessStatsLocked(procName);
- if (proc.mProcessState != procState) {
- boolean changed;
- if (procState < proc.mProcessState) {
- // Has this process become more important? If so,
- // we may need to change the uid if the currrent uid proc state
- // is not as important as what we are now setting.
- changed = mProcessState > procState;
- } else {
- // Has this process become less important? If so,
- // we may need to change the uid if the current uid proc state
- // is the same importance as the old setting.
- changed = mProcessState == proc.mProcessState;
- }
- proc.mProcessState = procState;
- if (changed) {
- // uid's state may have changed; compute what the new state should be.
- int uidProcState = PROCESS_STATE_NONE;
- for (int ip=mProcessStats.size()-1; ip>=0; ip--) {
- proc = mProcessStats.valueAt(ip);
- if (proc.mProcessState < uidProcState) {
- uidProcState = proc.mProcessState;
- }
- }
- updateUidProcessStateLocked(uidProcState, elapsedRealtimeMs);
+ if (mProcessState == uidRunningState) return;
+
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+
+ if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtime);
+ }
+ mProcessState = uidRunningState;
+ if (uidRunningState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ if (mProcessStateTimer[uidRunningState] == null) {
+ makeProcessState(uidRunningState, null);
}
+ mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtime);
}
}
@@ -9423,7 +9377,7 @@ public final class BatteryStatsImpl extends BatteryStats {
if (in.readInt() != 0) {
u.createForegroundActivityTimerLocked().readSummaryFromParcelLocked(in);
}
- u.mProcessState = Uid.PROCESS_STATE_NONE;
+ u.mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
for (int i = 0; i < Uid.NUM_PROCESS_STATE; i++) {
if (in.readInt() != 0) {
u.makeProcessState(i, null);
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index 7bab446d4705..3a004690d2cb 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -20,6 +20,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
@@ -28,7 +29,9 @@ import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.Size;
import android.view.ContextThemeWrapper;
@@ -36,6 +39,7 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
@@ -45,6 +49,8 @@ import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.Transformation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
@@ -277,70 +283,56 @@ public final class FloatingToolbar {
* A popup window used by the floating toolbar.
*
* This class is responsible for the rendering/animation of the floating toolbar.
- * It can hold one of 2 panels (i.e. main panel and overflow panel) at a time.
- * It delegates specific panel functionality to the appropriate panel.
+ * It holds 2 panels (i.e. main panel and overflow panel) and an overflow button
+ * to transition between panels.
*/
private static final class FloatingToolbarPopup {
- public static final int OVERFLOW_DIRECTION_UP = 0;
- public static final int OVERFLOW_DIRECTION_DOWN = 1;
+ /* Minimum and maximum number of items allowed in the overflow. */
+ private static final int MIN_OVERFLOW_SIZE = 2;
+ private static final int MAX_OVERFLOW_SIZE = 4;
+
+ /* The duration of the overflow button vector animation duration. */
+ private static final int OVERFLOW_BUTTON_ANIMATION_DELAY = 400;
private final Context mContext;
- private final View mParent;
+ private final View mParent; // Parent for the popup window.
private final PopupWindow mPopupWindow;
- private final ViewGroup mContentContainer;
+
+ /* Margins between the popup window and it's content. */
private final int mMarginHorizontal;
private final int mMarginVertical;
- private final Animation.AnimationListener mOnOverflowOpened =
- new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) {}
-
- @Override
- public void onAnimationEnd(Animation animation) {
- setOverflowPanelAsContent();
- mOverflowPanel.fadeIn(true);
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {}
- };
- private final Animation.AnimationListener mOnOverflowClosed =
- new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) {}
-
- @Override
- public void onAnimationEnd(Animation animation) {
- setMainPanelAsContent();
- mMainPanel.fadeIn(true);
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {
- }
- };
+ /* View components */
+ private final ViewGroup mContentContainer; // holds all contents.
+ private final ViewGroup mMainPanel; // holds menu items that are initially displayed.
+ private final ListView mOverflowPanel; // holds menu items hidden in the overflow.
+ private final ImageButton mOverflowButton; // opens/closes the overflow.
+ /* overflow button drawables. */
+ private final Drawable mArrow;
+ private final Drawable mOverflow;
+ private final AnimatedVectorDrawable mToArrow;
+ private final AnimatedVectorDrawable mToOverflow;
+
+ private final OverflowPanelViewHelper mOverflowPanelViewHelper;
+
+ /* Animation interpolators. */
+ private final Interpolator mLogAccelerateInterpolator;
+ private final Interpolator mFastOutSlowInInterpolator;
+ private final Interpolator mLinearOutSlowInInterpolator;
+ private final Interpolator mFastOutLinearInInterpolator;
+
+ /* Animations. */
+ private final AnimatorSet mShowAnimation;
private final AnimatorSet mDismissAnimation;
private final AnimatorSet mHideAnimation;
- private final AnimationSet mOpenOverflowAnimation = new AnimationSet(true);
- private final AnimationSet mCloseOverflowAnimation = new AnimationSet(true);
-
- private final Runnable mOpenOverflow = new Runnable() {
- @Override
- public void run() {
- openOverflow();
- }
- };
- private final Runnable mCloseOverflow = new Runnable() {
- @Override
- public void run() {
- closeOverflow();
- }
- };
+ private final AnimationSet mOpenOverflowAnimation;
+ private final AnimationSet mCloseOverflowAnimation;
+ private final Animation.AnimationListener mOverflowAnimationListener;
- private final Rect mViewPortOnScreen = new Rect();
- private final Point mCoordsOnWindow = new Point();
+ private final Rect mViewPortOnScreen = new Rect(); // portion of screen we can draw in.
+ private final Point mCoordsOnWindow = new Point(); // popup window coordinates.
+ /* Temporary data holders. Reset values before using. */
private final int[] mTmpCoords = new int[2];
private final Rect mTmpRect = new Rect();
@@ -357,12 +349,56 @@ public final class FloatingToolbar {
}
};
+ /**
+ * @see OverflowPanelViewHelper#preparePopupContent().
+ */
+ private final Runnable mPreparePopupContentRTLHelper = new Runnable() {
+ @Override
+ public void run() {
+ setPanelsStatesAtRestingPosition();
+ setContentAreaAsTouchableSurface();
+ mContentContainer.setAlpha(1);
+ }
+ };
+
+ /* Runnable to reset the overflow button's drawable after an overflow transition. */
+ private final Runnable mResetOverflowButtonDrawable = new Runnable() {
+ @Override
+ public void run() {
+ if (mIsOverflowOpen) {
+ mOverflowButton.setImageDrawable(mArrow);
+ } else {
+ mOverflowButton.setImageDrawable(mOverflow);
+ }
+ }
+ };
+
private boolean mDismissed = true; // tracks whether this popup is dismissed or dismissing.
private boolean mHidden; // tracks whether this popup is hidden or hiding.
- private FloatingToolbarOverflowPanel mOverflowPanel;
- private FloatingToolbarMainPanel mMainPanel;
- private int mOverflowDirection;
+ /* Calculated sizes for panels and overflow button. */
+ private final Size mOverflowButtonSize;
+ private Size mOverflowPanelSize; // Should be null when there is no overflow.
+ private Size mMainPanelSize;
+
+ /* Item click listeners */
+ private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
+ private final View.OnClickListener mMenuItemButtonOnClickListener =
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (v.getTag() instanceof MenuItem) {
+ if (mOnMenuItemClickListener != null) {
+ mOnMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag());
+ }
+ }
+ }
+ };
+
+ private boolean mOpenOverflowUpwards; // Whether the overflow opens upwards or downwards.
+ private boolean mIsOverflowOpen;
+
+ private int mTransitionDurationScale; // Used to scale the toolbar transition duration.
/**
* Initializes a new floating toolbar popup.
@@ -375,6 +411,48 @@ public final class FloatingToolbar {
mContext = Preconditions.checkNotNull(context);
mContentContainer = createContentContainer(context);
mPopupWindow = createPopupWindow(mContentContainer);
+ mMarginHorizontal = parent.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
+ mMarginVertical = parent.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin);
+
+ // Interpolators
+ mLogAccelerateInterpolator = new LogAccelerateInterpolator();
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_slow_in);
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.linear_out_slow_in);
+ mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_linear_in);
+
+ // Drawables. Needed for views.
+ mArrow = mContext.getResources()
+ .getDrawable(R.drawable.ft_avd_tooverflow, mContext.getTheme());
+ mArrow.setAutoMirrored(true);
+ mOverflow = mContext.getResources()
+ .getDrawable(R.drawable.ft_avd_toarrow, mContext.getTheme());
+ mOverflow.setAutoMirrored(true);
+ mToArrow = (AnimatedVectorDrawable) mContext.getResources()
+ .getDrawable(R.drawable.ft_avd_toarrow_animation, mContext.getTheme());
+ mToArrow.setAutoMirrored(true);
+ mToOverflow = (AnimatedVectorDrawable) mContext.getResources()
+ .getDrawable(R.drawable.ft_avd_tooverflow_animation, mContext.getTheme());
+ mToOverflow.setAutoMirrored(true);
+
+ // Views
+ mOverflowButton = createOverflowButton();
+ mOverflowButtonSize = measure(mOverflowButton);
+ mMainPanel = createMainPanel();
+ mOverflowPanelViewHelper = new OverflowPanelViewHelper(mContext);
+ mOverflowPanel = createOverflowPanel();
+
+ // Animation. Need views.
+ mOverflowAnimationListener = createOverflowAnimationListener();
+ mOpenOverflowAnimation = new AnimationSet(true);
+ mOpenOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
+ mCloseOverflowAnimation = new AnimationSet(true);
+ mCloseOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
+ mShowAnimation = createEnterAnimation(mContentContainer);
mDismissAnimation = createExitAnimation(
mContentContainer,
150, // startDelay
@@ -394,35 +472,23 @@ public final class FloatingToolbar {
mPopupWindow.dismiss();
}
});
- mMarginHorizontal = parent.getResources()
- .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
- mMarginVertical = parent.getResources()
- .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin);
}
/**
* Lays out buttons for the specified menu items.
+ * Requires a subsequent call to {@link #show()} to show the items.
*/
public void layoutMenuItems(
List<MenuItem> menuItems,
MenuItem.OnMenuItemClickListener menuItemClickListener,
int suggestedWidth) {
- Preconditions.checkNotNull(menuItems);
-
- mContentContainer.removeAllViews();
- if (mMainPanel == null) {
- mMainPanel = new FloatingToolbarMainPanel(mContext, mOpenOverflow);
- }
- List<MenuItem> overflowMenuItems =
- mMainPanel.layoutMenuItems(menuItems, getToolbarWidth(suggestedWidth));
- mMainPanel.setOnMenuItemClickListener(menuItemClickListener);
- if (!overflowMenuItems.isEmpty()) {
- if (mOverflowPanel == null) {
- mOverflowPanel =
- new FloatingToolbarOverflowPanel(mContext, mCloseOverflow);
- }
- mOverflowPanel.setMenuItems(overflowMenuItems);
- mOverflowPanel.setOnMenuItemClickListener(menuItemClickListener);
+ mOnMenuItemClickListener = menuItemClickListener;
+ cancelOverflowAnimations();
+ clearPanels();
+ menuItems = layoutMainPanelItems(menuItems, getAdjustedToolbarWidth(suggestedWidth));
+ if (!menuItems.isEmpty()) {
+ // Add remaining items to the overflow.
+ layoutOverflowPanelItems(menuItems);
}
updatePopupSize();
}
@@ -443,20 +509,13 @@ public final class FloatingToolbar {
cancelDismissAndHideAnimations();
cancelOverflowAnimations();
- // Make sure a panel is set as the content.
- if (mContentContainer.getChildCount() == 0) {
- setMainPanelAsContent();
- // If we're yet to show the popup, set the container visibility to zero.
- // The "show" animation will make this visible.
- mContentContainer.setAlpha(0);
- }
refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
preparePopupContent();
// We need to specify the position in window coordinates.
// TODO: Consider to use PopupWindow.setLayoutInScreenEnabled(true) so that we can
- // specify the popup poision in screen coordinates.
- mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, mCoordsOnWindow.x,
- mCoordsOnWindow.y);
+ // specify the popup position in screen coordinates.
+ mPopupWindow.showAtLocation(
+ mParent, Gravity.NO_GRAVITY, mCoordsOnWindow.x, mCoordsOnWindow.y);
setTouchableSurfaceInsetsComputer();
runShowAnimation();
}
@@ -472,6 +531,7 @@ public final class FloatingToolbar {
mHidden = false;
mDismissed = true;
mHideAnimation.cancel();
+
runDismissAnimation();
setZeroTouchableSurface();
}
@@ -521,104 +581,90 @@ public final class FloatingToolbar {
preparePopupContent();
// We need to specify the position in window coordinates.
// TODO: Consider to use PopupWindow.setLayoutInScreenEnabled(true) so that we can
- // specify the popup poision in screen coordinates.
- mPopupWindow.update(mCoordsOnWindow.x, mCoordsOnWindow.y, getWidth(), getHeight());
- }
-
- /**
- * Returns the width of this popup.
- */
- public int getWidth() {
- return mPopupWindow.getWidth();
- }
-
- /**
- * Returns the height of this popup.
- */
- public int getHeight() {
- return mPopupWindow.getHeight();
- }
-
- /**
- * Returns the context this popup is running in.
- */
- public Context getContext() {
- return mContext;
+ // specify the popup position in screen coordinates.
+ mPopupWindow.update(
+ mCoordsOnWindow.x, mCoordsOnWindow.y,
+ mPopupWindow.getWidth(), mPopupWindow.getHeight());
}
private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) {
refreshViewPort();
- int x = contentRectOnScreen.centerX() - getWidth() / 2;
+ int x = contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2;
// Update x so that the toolbar isn't rendered behind the nav bar in landscape.
- x = Math.max(0, Math.min(x, mViewPortOnScreen.right - getWidth()));
+ x = Math.max(0, Math.min(x, mViewPortOnScreen.right - mPopupWindow.getWidth()));
+
+ final int y;
- int y;
+ final int availableHeightAboveContent =
+ contentRectOnScreen.top - mViewPortOnScreen.top;
+ final int availableHeightBelowContent =
+ mViewPortOnScreen.bottom - contentRectOnScreen.bottom;
- int availableHeightAboveContent = contentRectOnScreen.top - mViewPortOnScreen.top;
- int availableHeightBelowContent = mViewPortOnScreen.bottom - contentRectOnScreen.bottom;
+ final int margin = 2 * mMarginVertical;
+ final int toolbarHeightWithVerticalMargin = getLineHeight(mContext) + margin;
- if (mOverflowPanel == null) { // There is no overflow.
- if (availableHeightAboveContent >= getToolbarHeightWithVerticalMargin()) {
+ if (!hasOverflow()) {
+ if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin) {
// There is enough space at the top of the content.
- y = contentRectOnScreen.top - getToolbarHeightWithVerticalMargin();
- } else if (availableHeightBelowContent >= getToolbarHeightWithVerticalMargin()) {
+ y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
+ } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin) {
// There is enough space at the bottom of the content.
y = contentRectOnScreen.bottom;
- } else if (availableHeightBelowContent >= getEstimatedToolbarHeight(mContext)) {
+ } else if (availableHeightBelowContent >= getLineHeight(mContext)) {
// Just enough space to fit the toolbar with no vertical margins.
y = contentRectOnScreen.bottom - mMarginVertical;
} else {
// Not enough space. Prefer to position as high as possible.
y = Math.max(
mViewPortOnScreen.top,
- contentRectOnScreen.top - getToolbarHeightWithVerticalMargin());
+ contentRectOnScreen.top - toolbarHeightWithVerticalMargin);
}
- } else { // There is an overflow.
- int margin = 2 * mMarginVertical;
- int minimumOverflowHeightWithMargin = mOverflowPanel.getMinimumHeight() + margin;
- int availableHeightThroughContentDown = mViewPortOnScreen.bottom -
- contentRectOnScreen.top + getToolbarHeightWithVerticalMargin();
- int availableHeightThroughContentUp = contentRectOnScreen.bottom -
- mViewPortOnScreen.top + getToolbarHeightWithVerticalMargin();
+ } else {
+ // Has an overflow.
+ final int minimumOverflowHeightWithMargin =
+ calculateOverflowHeight(MIN_OVERFLOW_SIZE) + margin;
+ final int availableHeightThroughContentDown = mViewPortOnScreen.bottom -
+ contentRectOnScreen.top + toolbarHeightWithVerticalMargin;
+ final int availableHeightThroughContentUp = contentRectOnScreen.bottom -
+ mViewPortOnScreen.top + toolbarHeightWithVerticalMargin;
if (availableHeightAboveContent >= minimumOverflowHeightWithMargin) {
// There is enough space at the top of the content rect for the overflow.
// Position above and open upwards.
updateOverflowHeight(availableHeightAboveContent - margin);
- y = contentRectOnScreen.top - getHeight();
- mOverflowDirection = OVERFLOW_DIRECTION_UP;
- } else if (availableHeightAboveContent >= getToolbarHeightWithVerticalMargin()
+ y = contentRectOnScreen.top - mPopupWindow.getHeight();
+ mOpenOverflowUpwards = true;
+ } else if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin
&& availableHeightThroughContentDown >= minimumOverflowHeightWithMargin) {
// There is enough space at the top of the content rect for the main panel
// but not the overflow.
// Position above but open downwards.
updateOverflowHeight(availableHeightThroughContentDown - margin);
- y = contentRectOnScreen.top - getToolbarHeightWithVerticalMargin();
- mOverflowDirection = OVERFLOW_DIRECTION_DOWN;
+ y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
+ mOpenOverflowUpwards = false;
} else if (availableHeightBelowContent >= minimumOverflowHeightWithMargin) {
// There is enough space at the bottom of the content rect for the overflow.
// Position below and open downwards.
updateOverflowHeight(availableHeightBelowContent - margin);
y = contentRectOnScreen.bottom;
- mOverflowDirection = OVERFLOW_DIRECTION_DOWN;
- } else if (availableHeightBelowContent >= getToolbarHeightWithVerticalMargin()
+ mOpenOverflowUpwards = false;
+ } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin
&& mViewPortOnScreen.height() >= minimumOverflowHeightWithMargin) {
// There is enough space at the bottom of the content rect for the main panel
// but not the overflow.
// Position below but open upwards.
updateOverflowHeight(availableHeightThroughContentUp - margin);
- y = contentRectOnScreen.bottom + getToolbarHeightWithVerticalMargin() -
- getHeight();
- mOverflowDirection = OVERFLOW_DIRECTION_UP;
+ y = contentRectOnScreen.bottom + toolbarHeightWithVerticalMargin -
+ mPopupWindow.getHeight();
+ mOpenOverflowUpwards = true;
} else {
// Not enough space.
// Position at the top of the view port and open downwards.
updateOverflowHeight(mViewPortOnScreen.height() - margin);
y = mViewPortOnScreen.top;
- mOverflowDirection = OVERFLOW_DIRECTION_DOWN;
+ mOpenOverflowUpwards = false;
}
- mOverflowPanel.setOverflowDirection(mOverflowDirection);
}
// We later specify the location of PopupWindow relative to the attached window.
@@ -639,15 +685,11 @@ public final class FloatingToolbar {
mCoordsOnWindow.set(x - windowLeftOnScreen, y - windowTopOnScreen);
}
- private int getToolbarHeightWithVerticalMargin() {
- return getEstimatedToolbarHeight(mContext) + mMarginVertical * 2;
- }
-
/**
* Performs the "show" animation on the floating popup.
*/
private void runShowAnimation() {
- createEnterAnimation(mContentContainer).start();
+ mShowAnimation.start();
}
/**
@@ -670,42 +712,16 @@ public final class FloatingToolbar {
}
private void cancelOverflowAnimations() {
- if (mOpenOverflowAnimation.hasStarted()
- && !mOpenOverflowAnimation.hasEnded()) {
- // Remove the animation listener, stop the animation,
- // then trigger the lister explicitly so it is not posted
- // to the message queue.
- mOpenOverflowAnimation.setAnimationListener(null);
- mContentContainer.clearAnimation();
- mOnOverflowOpened.onAnimationEnd(null);
- }
- if (mCloseOverflowAnimation.hasStarted()
- && !mCloseOverflowAnimation.hasEnded()) {
- // Remove the animation listener, stop the animation,
- // then trigger the lister explicitly so it is not posted
- // to the message queue.
- mCloseOverflowAnimation.setAnimationListener(null);
- mContentContainer.clearAnimation();
- mOnOverflowClosed.onAnimationEnd(null);
- }
+ mContentContainer.clearAnimation();
+ mMainPanel.animate().cancel();
+ mOverflowPanel.animate().cancel();
+ mToArrow.stop();
+ mToOverflow.stop();
}
- /**
- * Opens the floating toolbar overflow.
- * This method should not be called if menu items have not been laid out with
- * {@link #layoutMenuItems(java.util.List, MenuItem.OnMenuItemClickListener, int)}.
- *
- * @throws IllegalStateException if called when menu items have not been laid out.
- */
private void openOverflow() {
- Preconditions.checkState(mMainPanel != null);
- Preconditions.checkState(mOverflowPanel != null);
-
- mMainPanel.fadeOut(true);
- Size overflowPanelSize = mOverflowPanel.measure();
- final int targetWidth = overflowPanelSize.getWidth();
- final int targetHeight = overflowPanelSize.getHeight();
- final boolean morphUpwards = (mOverflowDirection == OVERFLOW_DIRECTION_UP);
+ final int targetWidth = mOverflowPanelSize.getWidth();
+ final int targetHeight = mOverflowPanelSize.getHeight();
final int startWidth = mContentContainer.getWidth();
final int startHeight = mContentContainer.getHeight();
final float startY = mContentContainer.getY();
@@ -714,230 +730,281 @@ public final class FloatingToolbar {
Animation widthAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
- ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
- params.width = startWidth + deltaWidth;
- mContentContainer.setLayoutParams(params);
+ setWidth(mContentContainer, startWidth + deltaWidth);
if (isRTL()) {
mContentContainer.setX(left);
+
+ // Lock the panels in place.
+ mMainPanel.setX(0);
+ mOverflowPanel.setX(0);
} else {
mContentContainer.setX(right - mContentContainer.getWidth());
+
+ // Offset the panels' positions so they look like they're locked in place
+ // on the screen.
+ mMainPanel.setX(mContentContainer.getWidth() - startWidth);
+ mOverflowPanel.setX(mContentContainer.getWidth() - targetWidth);
}
}
};
Animation heightAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
- ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
- params.height = startHeight + deltaHeight;
- mContentContainer.setLayoutParams(params);
- if (morphUpwards) {
- float y = startY - (mContentContainer.getHeight() - startHeight);
- mContentContainer.setY(y);
+ setHeight(mContentContainer, startHeight + deltaHeight);
+ if (mOpenOverflowUpwards) {
+ mContentContainer.setY(
+ startY - (mContentContainer.getHeight() - startHeight));
+ positionContentYCoordinatesIfOpeningOverflowUpwards();
}
}
};
- widthAnimation.setDuration(240);
- heightAnimation.setDuration(180);
- heightAnimation.setStartOffset(60);
+ final float overflowButtonStartX = mOverflowButton.getX();
+ final float overflowButtonTargetX = isRTL() ?
+ overflowButtonStartX + targetWidth - mOverflowButton.getWidth() :
+ overflowButtonStartX - targetWidth + mOverflowButton.getWidth();
+ Animation overflowButtonAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ float overflowButtonX = overflowButtonStartX
+ + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
+ float deltaContainerWidth = isRTL() ?
+ 0 :
+ mContentContainer.getWidth() - startWidth;
+ float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
+ mOverflowButton.setX(actualOverflowButtonX);
+ }
+ };
+ widthAnimation.setInterpolator(mLogAccelerateInterpolator);
+ widthAnimation.setDuration(getAdjustedDuration(250));
+ heightAnimation.setInterpolator(mFastOutSlowInInterpolator);
+ heightAnimation.setDuration(getAdjustedDuration(250));
+ overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
+ overflowButtonAnimation.setDuration(getAdjustedDuration(250));
+ mOpenOverflowAnimation.getAnimations().clear();
mOpenOverflowAnimation.getAnimations().clear();
- mOpenOverflowAnimation.setAnimationListener(mOnOverflowOpened);
mOpenOverflowAnimation.addAnimation(widthAnimation);
mOpenOverflowAnimation.addAnimation(heightAnimation);
+ mOpenOverflowAnimation.addAnimation(overflowButtonAnimation);
mContentContainer.startAnimation(mOpenOverflowAnimation);
+ mIsOverflowOpen = true;
+ mMainPanel.animate()
+ .alpha(0).withLayer()
+ .setInterpolator(mLinearOutSlowInInterpolator)
+ .setDuration(250)
+ .start();
+ mOverflowPanel.setAlpha(1); // fadeIn in 0ms.
}
- /**
- * Opens the floating toolbar overflow.
- * This method should not be called if menu items have not been laid out with
- * {@link #layoutMenuItems(java.util.List, MenuItem.OnMenuItemClickListener, int)}.
- *
- * @throws IllegalStateException if called when menu items have not been laid out.
- */
private void closeOverflow() {
- Preconditions.checkState(mMainPanel != null);
- Preconditions.checkState(mOverflowPanel != null);
-
- mOverflowPanel.fadeOut(true);
- Size mainPanelSize = mMainPanel.measure();
- final int targetWidth = mainPanelSize.getWidth();
- final int targetHeight = mainPanelSize.getHeight();
+ final int targetWidth = mMainPanelSize.getWidth();
final int startWidth = mContentContainer.getWidth();
- final int startHeight = mContentContainer.getHeight();
- final float bottom = mContentContainer.getY() + mContentContainer.getHeight();
- final boolean morphedUpwards = (mOverflowDirection == OVERFLOW_DIRECTION_UP);
final float left = mContentContainer.getX();
final float right = left + mContentContainer.getWidth();
Animation widthAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
- ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
- params.width = startWidth + deltaWidth;
- mContentContainer.setLayoutParams(params);
+ setWidth(mContentContainer, startWidth + deltaWidth);
if (isRTL()) {
mContentContainer.setX(left);
+
+ // Lock the panels in place.
+ mMainPanel.setX(0);
+ mOverflowPanel.setX(0);
} else {
mContentContainer.setX(right - mContentContainer.getWidth());
+
+ // Offset the panels' positions so they look like they're locked in place
+ // on the screen.
+ mMainPanel.setX(mContentContainer.getWidth() - targetWidth);
+ mOverflowPanel.setX(mContentContainer.getWidth() - startWidth);
}
}
};
+ final int targetHeight = mMainPanelSize.getHeight();
+ final int startHeight = mContentContainer.getHeight();
+ final float bottom = mContentContainer.getY() + mContentContainer.getHeight();
Animation heightAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
- ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
- params.height = startHeight + deltaHeight;
- mContentContainer.setLayoutParams(params);
- if (morphedUpwards) {
+ setHeight(mContentContainer, startHeight + deltaHeight);
+ if (mOpenOverflowUpwards) {
mContentContainer.setY(bottom - mContentContainer.getHeight());
+ positionContentYCoordinatesIfOpeningOverflowUpwards();
}
}
};
- widthAnimation.setDuration(150);
- widthAnimation.setStartOffset(150);
- heightAnimation.setDuration(210);
+ final float overflowButtonStartX = mOverflowButton.getX();
+ final float overflowButtonTargetX = isRTL() ?
+ overflowButtonStartX - startWidth + mOverflowButton.getWidth() :
+ overflowButtonStartX + startWidth - mOverflowButton.getWidth();
+ Animation overflowButtonAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ float overflowButtonX = overflowButtonStartX
+ + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
+ float deltaContainerWidth = isRTL() ?
+ 0 :
+ mContentContainer.getWidth() - startWidth;
+ float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
+ mOverflowButton.setX(actualOverflowButtonX);
+ }
+ };
+ widthAnimation.setInterpolator(mFastOutSlowInInterpolator);
+ widthAnimation.setDuration(getAdjustedDuration(250));
+ heightAnimation.setInterpolator(mLogAccelerateInterpolator);
+ heightAnimation.setDuration(getAdjustedDuration(250));
+ overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
+ overflowButtonAnimation.setDuration(getAdjustedDuration(250));
mCloseOverflowAnimation.getAnimations().clear();
- mCloseOverflowAnimation.setAnimationListener(mOnOverflowClosed);
mCloseOverflowAnimation.addAnimation(widthAnimation);
mCloseOverflowAnimation.addAnimation(heightAnimation);
+ mCloseOverflowAnimation.addAnimation(overflowButtonAnimation);
mContentContainer.startAnimation(mCloseOverflowAnimation);
- }
-
- /**
- * Prepares the content container for show and update calls.
- */
- private void preparePopupContent() {
- // Reset visibility.
- if (mMainPanel != null) {
- mMainPanel.fadeIn(false);
- }
- if (mOverflowPanel != null) {
- mOverflowPanel.fadeIn(false);
- }
-
- // Reset position.
- if (isMainPanelContent()) {
- positionMainPanel();
- }
- if (isOverflowPanelContent()) {
- positionOverflowPanel();
- }
- }
-
- private boolean isMainPanelContent() {
- return mMainPanel != null
- && mContentContainer.getChildAt(0) == mMainPanel.getView();
- }
-
- private boolean isOverflowPanelContent() {
- return mOverflowPanel != null
- && mContentContainer.getChildAt(0) == mOverflowPanel.getView();
- }
-
- /**
- * Sets the current content to be the main view panel.
- */
- private void setMainPanelAsContent() {
- // This should never be called if the main panel has not been initialized.
- Preconditions.checkNotNull(mMainPanel);
- mContentContainer.removeAllViews();
- Size mainPanelSize = mMainPanel.measure();
- ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
- params.width = mainPanelSize.getWidth();
- params.height = mainPanelSize.getHeight();
- mContentContainer.setLayoutParams(params);
- mContentContainer.addView(mMainPanel.getView());
- setContentAreaAsTouchableSurface();
- }
-
- /**
- * Sets the current content to be the overflow view panel.
- */
- private void setOverflowPanelAsContent() {
- // This should never be called if the overflow panel has not been initialized.
- Preconditions.checkNotNull(mOverflowPanel);
- mContentContainer.removeAllViews();
- Size overflowPanelSize = mOverflowPanel.measure();
- ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
- params.width = overflowPanelSize.getWidth();
- params.height = overflowPanelSize.getHeight();
- mContentContainer.setLayoutParams(params);
- mContentContainer.addView(mOverflowPanel.getView());
- setContentAreaAsTouchableSurface();
- }
-
- /**
- * Places the main view panel at the appropriate resting coordinates.
- */
- private void positionMainPanel() {
- Preconditions.checkNotNull(mMainPanel);
- mContentContainer.setX(mMarginHorizontal);
-
- float y = mMarginVertical;
- if (mOverflowDirection == OVERFLOW_DIRECTION_UP) {
- y = getHeight()
- - (mMainPanel.getView().getMeasuredHeight() + mMarginVertical);
- }
- mContentContainer.setY(y);
- setContentAreaAsTouchableSurface();
- }
+ mIsOverflowOpen = false;
+ mMainPanel.animate()
+ .alpha(1).withLayer()
+ .setInterpolator(mFastOutLinearInInterpolator)
+ .setDuration(100)
+ .start();
+ mOverflowPanel.animate()
+ .alpha(0).withLayer()
+ .setInterpolator(mLinearOutSlowInInterpolator)
+ .setDuration(150)
+ .start();
+ }
+
+ private void setPanelsStatesAtRestingPosition() {
+ mOverflowButton.setEnabled(true);
+
+ if (mIsOverflowOpen) {
+ // Set open state.
+ final Size containerSize = mOverflowPanelSize;
+ setSize(mContentContainer, containerSize);
+ mMainPanel.setAlpha(0);
+ mOverflowPanel.setAlpha(1);
+ mOverflowButton.setImageDrawable(mArrow);
+
+ // Update x-coordinates depending on RTL state.
+ if (isRTL()) {
+ mContentContainer.setX(mMarginHorizontal); // align left
+ mMainPanel.setX(0); // align left
+ mOverflowButton.setX( // align right
+ containerSize.getWidth() - mOverflowButtonSize.getWidth());
+ mOverflowPanel.setX(0); // align left
+ } else {
+ mContentContainer.setX( // align right
+ mMarginHorizontal +
+ mMainPanelSize.getWidth() - containerSize.getWidth());
+ mMainPanel.setX(-mContentContainer.getX()); // align right
+ mOverflowButton.setX(0); // align left
+ mOverflowPanel.setX(0); // align left
+ }
- /**
- * Places the main view panel at the appropriate resting coordinates.
- */
- private void positionOverflowPanel() {
- Preconditions.checkNotNull(mOverflowPanel);
- float x;
- if (isRTL()) {
- x = mMarginHorizontal;
+ // Update y-coordinates depending on overflow's open direction.
+ if (mOpenOverflowUpwards) {
+ mContentContainer.setY(mMarginVertical); // align top
+ mMainPanel.setY( // align bottom
+ containerSize.getHeight() - mContentContainer.getHeight());
+ mOverflowButton.setY( // align bottom
+ containerSize.getHeight() - mOverflowButtonSize.getHeight());
+ mOverflowPanel.setY(0); // align top
+ } else {
+ // opens downwards.
+ mContentContainer.setY(mMarginVertical); // align top
+ mMainPanel.setY(0); // align top
+ mOverflowButton.setY(0); // align top
+ mOverflowPanel.setY(mOverflowButtonSize.getHeight()); // align bottom
+ }
} else {
- x = mPopupWindow.getWidth()
- - (mOverflowPanel.getView().getMeasuredWidth() + mMarginHorizontal);
+ if (hasOverflow()) {
+ // overflow not open. Set closed state.
+ final Size containerSize = mMainPanelSize;
+ setSize(mContentContainer, containerSize);
+ mMainPanel.setAlpha(1);
+ mOverflowPanel.setAlpha(0);
+ mOverflowButton.setImageDrawable(mOverflow);
+
+ // Update x-coordinates depending on RTL state.
+ if (isRTL()) {
+ mContentContainer.setX(mMarginHorizontal); // align left
+ mMainPanel.setX(0); // align left
+ mOverflowButton.setX(0); // align left
+ mOverflowPanel.setX(0); // align left
+ } else {
+ mContentContainer.setX(mMarginHorizontal); // align left
+ mMainPanel.setX(0); // align left
+ mOverflowButton.setX( // align right
+ containerSize.getWidth() - mOverflowButtonSize.getWidth());
+ mOverflowPanel.setX( // align right
+ containerSize.getWidth() - mOverflowPanelSize.getWidth());
+ }
+
+ // Update y-coordinates depending on overflow's open direction.
+ if (mOpenOverflowUpwards) {
+ mContentContainer.setY( // align bottom
+ mMarginVertical +
+ mOverflowPanelSize.getHeight() - containerSize.getHeight());
+ mMainPanel.setY(0); // align top
+ mOverflowButton.setY(0); // align top
+ mOverflowPanel.setY( // align bottom
+ containerSize.getHeight() - mOverflowPanelSize.getHeight());
+ } else {
+ // opens downwards.
+ mContentContainer.setY(mMarginVertical); // align top
+ mMainPanel.setY(0); // align top
+ mOverflowButton.setY(0); // align top
+ mOverflowPanel.setY(mOverflowButtonSize.getHeight()); // align bottom
+ }
+ } else {
+ mContentContainer.setX(mMarginHorizontal);
+ mContentContainer.setY(mMarginVertical);
+ }
}
- mContentContainer.setX(x);
- mContentContainer.setY(mMarginVertical);
- setContentAreaAsTouchableSurface();
}
- private void updateOverflowHeight(int height) {
- if (mOverflowPanel != null) {
- mOverflowPanel.setSuggestedHeight(height);
-
- // Re-measure the popup and it's contents.
- boolean mainPanelContent = isMainPanelContent();
- boolean overflowPanelContent = isOverflowPanelContent();
- mContentContainer.removeAllViews(); // required to update popup size.
- updatePopupSize();
- // Reset the appropriate content.
- if (mainPanelContent) {
- setMainPanelAsContent();
+ private void updateOverflowHeight(int suggestedHeight) {
+ if (hasOverflow()) {
+ final int maxItemSize = (suggestedHeight - mOverflowButtonSize.getHeight()) /
+ getLineHeight(mContext);
+ final int newHeight = calculateOverflowHeight(maxItemSize);
+ if (mOverflowPanelSize.getHeight() != newHeight) {
+ mOverflowPanelSize = new Size(mOverflowPanelSize.getWidth(), newHeight);
}
- if (overflowPanelContent) {
- setOverflowPanelAsContent();
+ setSize(mOverflowPanel, mOverflowPanelSize);
+ if (mIsOverflowOpen) {
+ setSize(mContentContainer, mOverflowPanelSize);
+ if (mOpenOverflowUpwards) {
+ final int deltaHeight = mOverflowPanelSize.getHeight() - newHeight;
+ mContentContainer.setY(mContentContainer.getY() + deltaHeight);
+ mOverflowButton.setY(mOverflowButton.getY() - deltaHeight);
+ }
+ } else {
+ setSize(mContentContainer, mMainPanelSize);
}
+ updatePopupSize();
}
}
private void updatePopupSize() {
int width = 0;
int height = 0;
- if (mMainPanel != null) {
- Size mainPanelSize = mMainPanel.measure();
- width = mainPanelSize.getWidth();
- height = mainPanelSize.getHeight();
+ if (mMainPanelSize != null) {
+ width = Math.max(width, mMainPanelSize.getWidth());
+ height = Math.max(height, mMainPanelSize.getHeight());
}
- if (mOverflowPanel != null) {
- Size overflowPanelSize = mOverflowPanel.measure();
- width = Math.max(width, overflowPanelSize.getWidth());
- height = Math.max(height, overflowPanelSize.getHeight());
+ if (mOverflowPanelSize != null) {
+ width = Math.max(width, mOverflowPanelSize.getWidth());
+ height = Math.max(height, mOverflowPanelSize.getHeight());
}
mPopupWindow.setWidth(width + mMarginHorizontal * 2);
mPopupWindow.setHeight(height + mMarginVertical * 2);
+ maybeComputeTransitionDurationScale();
}
-
private void refreshViewPort() {
mParent.getWindowVisibleDisplayFrame(mViewPortOnScreen);
}
@@ -947,7 +1014,7 @@ public final class FloatingToolbar {
return !mTmpRect.equals(mViewPortOnScreen);
}
- private int getToolbarWidth(int suggestedWidth) {
+ private int getAdjustedToolbarWidth(int suggestedWidth) {
int width = suggestedWidth;
refreshViewPort();
int maximumWidth = mViewPortOnScreen.width() - 2 * mParent.getResources()
@@ -971,11 +1038,17 @@ public final class FloatingToolbar {
* Sets the touchable region of this popup to be the area occupied by its content.
*/
private void setContentAreaAsTouchableSurface() {
- if (!mPopupWindow.isShowing()) {
- mContentContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ Preconditions.checkNotNull(mMainPanelSize);
+ final int width;
+ final int height;
+ if (mIsOverflowOpen) {
+ Preconditions.checkNotNull(mOverflowPanelSize);
+ width = mOverflowPanelSize.getWidth();
+ height = mOverflowPanelSize.getHeight();
+ } else {
+ width = mMainPanelSize.getWidth();
+ height = mMainPanelSize.getHeight();
}
- int width = mContentContainer.getMeasuredWidth();
- int height = mContentContainer.getMeasuredHeight();
mTouchableRegion.set(
(int) mContentContainer.getX(),
(int) mContentContainer.getY(),
@@ -997,45 +1070,12 @@ public final class FloatingToolbar {
}
private boolean isRTL() {
- return mContentContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ return mContext.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_RTL;
}
- }
-
- /**
- * A widget that holds the primary menu items in the floating toolbar.
- */
- private static final class FloatingToolbarMainPanel {
-
- private final Context mContext;
- private final ViewGroup mContentView;
- private final View.OnClickListener mMenuItemButtonOnClickListener =
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (v.getTag() instanceof MenuItem) {
- if (mOnMenuItemClickListener != null) {
- mOnMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag());
- }
- }
- }
- };
- private final ViewFader viewFader;
- private final Runnable mOpenOverflow;
-
- private View mOpenOverflowButton;
- private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
- /**
- * Initializes a floating toolbar popup main view panel.
- *
- * @param context
- * @param openOverflow The code that opens the toolbar popup overflow.
- */
- public FloatingToolbarMainPanel(Context context, Runnable openOverflow) {
- mContext = Preconditions.checkNotNull(context);
- mContentView = new LinearLayout(context);
- viewFader = new ViewFader(mContentView);
- mOpenOverflow = Preconditions.checkNotNull(openOverflow);
+ private boolean hasOverflow() {
+ return mOverflowPanelSize != null;
}
/**
@@ -1044,16 +1084,14 @@ public final class FloatingToolbar {
*
* @return The menu items that are not included in this main panel.
*/
- public List<MenuItem> layoutMenuItems(List<MenuItem> menuItems, int width) {
+ public List<MenuItem> layoutMainPanelItems(
+ List<MenuItem> menuItems, final int toolbarWidth) {
Preconditions.checkNotNull(menuItems);
- // Reserve space for the "open overflow" button.
- final int toolbarWidth = width - getEstimatedOpenOverflowButtonWidth(mContext);
-
int availableWidth = toolbarWidth;
final LinkedList<MenuItem> remainingMenuItems = new LinkedList<MenuItem>(menuItems);
- mContentView.removeAllViews();
+ mMainPanel.removeAllViews();
boolean isFirstItem = true;
while (!remainingMenuItems.isEmpty()) {
@@ -1081,59 +1119,119 @@ public final class FloatingToolbar {
menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
- if (menuItemButtonWidth <= availableWidth) {
+ // Check if we can fit an item while reserving space for the overflowButton.
+ boolean canFitWithOverflow =
+ menuItemButtonWidth <= availableWidth - mOverflowButtonSize.getWidth();
+ boolean canFitNoOverflow =
+ remainingMenuItems.size() == 1 && menuItemButtonWidth <= availableWidth;
+ if (canFitWithOverflow || canFitNoOverflow) {
setButtonTagAndClickListener(menuItemButton, menuItem);
- mContentView.addView(menuItemButton);
+ mMainPanel.addView(menuItemButton);
ViewGroup.LayoutParams params = menuItemButton.getLayoutParams();
params.width = menuItemButtonWidth;
menuItemButton.setLayoutParams(params);
availableWidth -= menuItemButtonWidth;
remainingMenuItems.pop();
} else {
- if (mOpenOverflowButton == null) {
- mOpenOverflowButton = LayoutInflater.from(mContext)
- .inflate(R.layout.floating_popup_open_overflow_button, null);
- mOpenOverflowButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mOpenOverflowButton != null) {
- mOpenOverflow.run();
- }
- }
- });
- }
- mContentView.addView(mOpenOverflowButton);
+ // Reserve space for overflowButton.
+ mMainPanel.setPaddingRelative(0, 0, mOverflowButtonSize.getWidth(), 0);
break;
}
}
+ mMainPanelSize = measure(mMainPanel);
return remainingMenuItems;
}
- public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
- mOnMenuItemClickListener = listener;
- }
+ private void layoutOverflowPanelItems(List<MenuItem> menuItems) {
+ ArrayAdapter<MenuItem> overflowPanelAdapter =
+ (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter();
+ overflowPanelAdapter.clear();
+ final int size = menuItems.size();
+ for (int i = 0; i < size; i++) {
+ overflowPanelAdapter.add(menuItems.get(i));
+ }
+ mOverflowPanel.setAdapter(overflowPanelAdapter);
+ if (mOpenOverflowUpwards) {
+ mOverflowPanel.setY(0);
+ } else {
+ mOverflowPanel.setY(mOverflowButtonSize.getHeight());
+ }
- public View getView() {
- return mContentView;
+ int width = Math.max(getOverflowWidth(), mOverflowButtonSize.getWidth());
+ int height = calculateOverflowHeight(MAX_OVERFLOW_SIZE);
+ mOverflowPanelSize = new Size(width, height);
+ setSize(mOverflowPanel, mOverflowPanelSize);
}
- public void fadeIn(boolean animate) {
- viewFader.fadeIn(animate);
- }
+ /**
+ * Resets the content container and appropriately position it's panels.
+ */
+ private void preparePopupContent() {
+ mContentContainer.removeAllViews();
+
+ // Add views in the specified order so they stack up as expected.
+ // Order: overflowPanel, mainPanel, overflowButton.
+ if (hasOverflow()) {
+ mContentContainer.addView(mOverflowPanel);
+ }
+ mContentContainer.addView(mMainPanel);
+ if (hasOverflow()) {
+ mContentContainer.addView(mOverflowButton);
+ }
+ setPanelsStatesAtRestingPosition();
+ setContentAreaAsTouchableSurface();
- public void fadeOut(boolean animate) {
- viewFader.fadeOut(animate);
+ // The positioning of contents in RTL is wrong when the view is first rendered.
+ // Hide the view and post a runnable to recalculate positions and render the view.
+ // TODO: Investigate why this happens and fix.
+ if (isRTL()) {
+ mContentContainer.setAlpha(0);
+ mContentContainer.post(mPreparePopupContentRTLHelper);
+ }
}
/**
- * Returns how big this panel's view should be.
- * This method should only be called when the view has not been attached to a parent
- * otherwise it will throw an illegal state.
+ * Clears out the panels and their container. Resets their calculated sizes.
*/
- public Size measure() throws IllegalStateException {
- Preconditions.checkState(mContentView.getParent() == null);
- mContentView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- return new Size(mContentView.getMeasuredWidth(), mContentView.getMeasuredHeight());
+ private void clearPanels() {
+ mOverflowPanelSize = null;
+ mMainPanelSize = null;
+ mIsOverflowOpen = false;
+ mMainPanel.removeAllViews();
+ ArrayAdapter<MenuItem> overflowPanelAdapter =
+ (ArrayAdapter<MenuItem>) mOverflowPanel.getAdapter();
+ overflowPanelAdapter.clear();
+ mOverflowPanel.setAdapter(overflowPanelAdapter);
+ mContentContainer.removeAllViews();
+ }
+
+ private void positionContentYCoordinatesIfOpeningOverflowUpwards() {
+ if (mOpenOverflowUpwards) {
+ mMainPanel.setY(mContentContainer.getHeight() - mMainPanelSize.getHeight());
+ mOverflowButton.setY(mContentContainer.getHeight() - mOverflowButton.getHeight());
+ mOverflowPanel.setY(mContentContainer.getHeight() - mOverflowPanelSize.getHeight());
+ }
+ }
+
+ private int getOverflowWidth() {
+ int overflowWidth = 0;
+ final int count = mOverflowPanel.getAdapter().getCount();
+ for (int i = 0; i < count; i++) {
+ MenuItem menuItem = (MenuItem) mOverflowPanel.getAdapter().getItem(i);
+ overflowWidth =
+ Math.max(mOverflowPanelViewHelper.calculateWidth(menuItem), overflowWidth);
+ }
+ return overflowWidth;
+ }
+
+ private int calculateOverflowHeight(int maxItemSize) {
+ // Maximum of 4 items, minimum of 2 if the overflow has to scroll.
+ int actualSize = Math.min(
+ MAX_OVERFLOW_SIZE,
+ Math.min(
+ Math.max(MIN_OVERFLOW_SIZE, maxItemSize),
+ mOverflowPanel.getCount()));
+ return actualSize * getLineHeight(mContext) + mOverflowButtonSize.getHeight();
}
private void setButtonTagAndClickListener(View menuItemButton, MenuItem menuItem) {
@@ -1144,281 +1242,326 @@ public final class FloatingToolbar {
button.setTag(menuItem);
button.setOnClickListener(mMenuItemButtonOnClickListener);
}
- }
+ /**
+ * NOTE: Use only in android.view.animation.* animations. Do not use in android.animation.*
+ * animations. See comment about this in the code.
+ */
+ private int getAdjustedDuration(int originalDuration) {
+ if (mTransitionDurationScale < 150) {
+ // For smaller transition, decrease the time.
+ return Math.max(originalDuration - 50, 0);
+ } else if (mTransitionDurationScale > 300) {
+ // For bigger transition, increase the time.
+ return originalDuration + 50;
+ }
- /**
- * A widget that holds the overflow items in the floating toolbar.
- */
- private static final class FloatingToolbarOverflowPanel {
-
- private final LinearLayout mContentView;
- private final ViewGroup mBackButtonContainer;
- private final View mBackButton;
- private final ListView mListView;
- private final TextView mListViewItemWidthCalculator;
- private final ViewFader mViewFader;
- private final Runnable mCloseOverflow;
+ // Scale the animation duration with getDurationScale(). This allows
+ // android.view.animation.* animations to scale just like android.animation.* animations
+ // when animator duration scale is adjusted in "Developer Options".
+ // For this reason, do not use this method for android.animation.* animations.
+ return (int) (originalDuration * ValueAnimator.getDurationScale());
+ }
- private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
- private int mOverflowWidth;
- private int mSuggestedHeight;
+ private void maybeComputeTransitionDurationScale() {
+ if (mMainPanelSize == null || mOverflowPanel == null) {
+ int w = mMainPanelSize.getWidth() - mOverflowPanelSize.getWidth();
+ int h = mOverflowPanelSize.getHeight() - mMainPanelSize.getHeight();
+ mTransitionDurationScale = (int) (Math.sqrt(w * w + h * h) /
+ mContentContainer.getContext().getResources().getDisplayMetrics().density);
+ }
+ }
- /**
- * Initializes a floating toolbar popup overflow view panel.
- *
- * @param context
- * @param closeOverflow The code that closes the toolbar popup's overflow.
- */
- public FloatingToolbarOverflowPanel(Context context, Runnable closeOverflow) {
- mCloseOverflow = Preconditions.checkNotNull(closeOverflow);
+ private ViewGroup createMainPanel() {
+ ViewGroup mainPanel = new LinearLayout(mContext) {
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (isOverflowAnimating()) {
+ // Update widthMeasureSpec to make sure that this view is not clipped
+ // as we offset it's coordinates with respect to it's parent.
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ mMainPanelSize.getWidth(),
+ MeasureSpec.EXACTLY);
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
- mContentView = new LinearLayout(context);
- mContentView.setOrientation(LinearLayout.VERTICAL);
- mViewFader = new ViewFader(mContentView);
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // Intercept the touch event while the overflow is animating.
+ return isOverflowAnimating();
+ }
+ };
+ return mainPanel;
+ }
- mBackButton = LayoutInflater.from(context)
- .inflate(R.layout.floating_popup_close_overflow_button, null);
- mBackButton.setOnClickListener(new View.OnClickListener() {
+ private ImageButton createOverflowButton() {
+ final ImageButton overflowButton = (ImageButton) LayoutInflater.from(mContext)
+ .inflate(R.layout.floating_popup_overflow_button, null);
+ overflowButton.setImageDrawable(mOverflow);
+ overflowButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- mCloseOverflow.run();
+ final Drawable drawable = overflowButton.getDrawable();
+ if (mIsOverflowOpen) {
+ overflowButton.setImageDrawable(mToOverflow);
+ mToOverflow.start();
+ closeOverflow();
+ } else {
+ overflowButton.setImageDrawable(mToArrow);
+ mToArrow.start();
+ openOverflow();
+ }
+ overflowButton.postDelayed(
+ mResetOverflowButtonDrawable, OVERFLOW_BUTTON_ANIMATION_DELAY);
}
});
- mBackButtonContainer = new LinearLayout(context);
- mBackButtonContainer.addView(mBackButton);
+ return overflowButton;
+ }
+
+ private ListView createOverflowPanel() {
+ final ListView overflowPanel = new ListView(FloatingToolbarPopup.this.mContext) {
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Update heightMeasureSpec to make sure that this view is not clipped
+ // as we offset it's coordinates with respect to it's parent.
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ mOverflowPanelSize.getHeight() - mOverflowButtonSize.getHeight(),
+ MeasureSpec.EXACTLY);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (isOverflowAnimating()) {
+ // Eat the touch event.
+ return true;
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+ };
+ overflowPanel.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ overflowPanel.setDivider(null);
+ overflowPanel.setDividerHeight(0);
+
+ final ArrayAdapter adapter =
+ new ArrayAdapter<MenuItem>(mContext, 0) {
+ @Override
+ public int getViewTypeCount() {
+ return mOverflowPanelViewHelper.getViewTypeCount();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mOverflowPanelViewHelper.getItemViewType(getItem(position));
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return mOverflowPanelViewHelper.getView(
+ getItem(position), mOverflowPanelSize.getWidth(), convertView);
+ }
+ };
+ overflowPanel.setAdapter(adapter);
- mListView = createOverflowListView();
- mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ overflowPanel.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- MenuItem menuItem = (MenuItem) mListView.getAdapter().getItem(position);
+ MenuItem menuItem = (MenuItem) overflowPanel.getAdapter().getItem(position);
if (mOnMenuItemClickListener != null) {
mOnMenuItemClickListener.onMenuItemClick(menuItem);
}
}
});
- mContentView.addView(mListView);
- mContentView.addView(mBackButtonContainer);
-
- mListViewItemWidthCalculator = createOverflowMenuItemButton(context);
- mListViewItemWidthCalculator.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ return overflowPanel;
}
- /**
- * Sets the menu items to be displayed in the overflow.
- */
- public void setMenuItems(List<MenuItem> menuItems) {
- ArrayAdapter overflowListViewAdapter = (ArrayAdapter) mListView.getAdapter();
- overflowListViewAdapter.clear();
- overflowListViewAdapter.addAll(menuItems);
- setListViewHeight();
- setOverflowWidth();
+ private boolean isOverflowAnimating() {
+ final boolean overflowOpening = mOpenOverflowAnimation.hasStarted()
+ && !mOpenOverflowAnimation.hasEnded();
+ final boolean overflowClosing = mCloseOverflowAnimation.hasStarted()
+ && !mCloseOverflowAnimation.hasEnded();
+ return overflowOpening || overflowClosing;
}
- public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
- mOnMenuItemClickListener = listener;
+ private Animation.AnimationListener createOverflowAnimationListener() {
+ Animation.AnimationListener listener = new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ // Disable the overflow button while it's animating.
+ // It will be re-enabled when the animation stops.
+ mOverflowButton.setEnabled(false);
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // Posting this because it seems like this is called before the animation
+ // actually ends.
+ mContentContainer.post(new Runnable() {
+ @Override
+ public void run() {
+ setPanelsStatesAtRestingPosition();
+ setContentAreaAsTouchableSurface();
+ }
+ });
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ };
+ return listener;
}
- /**
- * Notifies the overflow of the current direction in which the overflow will be opened.
- *
- * @param overflowDirection {@link FloatingToolbarPopup#OVERFLOW_DIRECTION_UP}
- * or {@link FloatingToolbarPopup#OVERFLOW_DIRECTION_DOWN}.
- */
- public void setOverflowDirection(int overflowDirection) {
- mContentView.removeView(mBackButtonContainer);
- int index = (overflowDirection == FloatingToolbarPopup.OVERFLOW_DIRECTION_UP)? 1 : 0;
- mContentView.addView(mBackButtonContainer, index);
+ private static Size measure(View view) {
+ Preconditions.checkState(view.getParent() == null);
+ view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ return new Size(view.getMeasuredWidth(), view.getMeasuredHeight());
}
- public void setSuggestedHeight(int height) {
- mSuggestedHeight = height;
- setListViewHeight();
+ private static void setSize(View view, int width, int height) {
+ view.setMinimumWidth(width);
+ view.setMinimumHeight(height);
+ ViewGroup.LayoutParams params = view.getLayoutParams();
+ params = (params == null) ? new ViewGroup.LayoutParams(0, 0) : params;
+ params.width = width;
+ params.height = height;
+ view.setLayoutParams(params);
}
- public int getMinimumHeight() {
- return mContentView.getContext().getResources().
- getDimensionPixelSize(R.dimen.floating_toolbar_minimum_overflow_height)
- + getEstimatedToolbarHeight(mContentView.getContext());
+ private static void setSize(View view, Size size) {
+ setSize(view, size.getWidth(), size.getHeight());
}
- /**
- * Returns the content view of the overflow.
- */
- public View getView() {
- return mContentView;
+ private static void setWidth(View view, int width) {
+ ViewGroup.LayoutParams params = view.getLayoutParams();
+ setSize(view, width, params.height);
}
- public void fadeIn(boolean animate) {
- mViewFader.fadeIn(animate);
+ private static void setHeight(View view, int height) {
+ ViewGroup.LayoutParams params = view.getLayoutParams();
+ setSize(view, params.width, height);
}
- public void fadeOut(boolean animate) {
- mViewFader.fadeOut(animate);
+ private static int getLineHeight(Context context) {
+ return context.getResources().getDimensionPixelSize(R.dimen.floating_toolbar_height);
}
/**
- * Returns how big this panel's view should be.
- * This method should only be called when the view has not been attached to a parent.
- *
- * @throws IllegalStateException
+ * A custom interpolator used for various floating toolbar animations.
*/
- public Size measure() {
- Preconditions.checkState(mContentView.getParent() == null);
- mContentView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- return new Size(mContentView.getMeasuredWidth(), mContentView.getMeasuredHeight());
- }
+ private static final class LogAccelerateInterpolator implements Interpolator {
- private void setListViewHeight() {
- int itemHeight = getEstimatedToolbarHeight(mContentView.getContext());
- int height = mListView.getAdapter().getCount() * itemHeight;
- int maxHeight = mContentView.getContext().getResources().
- getDimensionPixelSize(R.dimen.floating_toolbar_maximum_overflow_height);
- int minHeight = mContentView.getContext().getResources().
- getDimensionPixelSize(R.dimen.floating_toolbar_minimum_overflow_height);
- int suggestedListViewHeight = mSuggestedHeight - (mSuggestedHeight % itemHeight)
- - itemHeight; // reserve space for the back button.
- ViewGroup.LayoutParams params = mListView.getLayoutParams();
- if (suggestedListViewHeight <= 0) {
- // Invalid height. Use the maximum height available.
- params.height = Math.min(maxHeight, height);
- } else if (suggestedListViewHeight < minHeight) {
- // Height is smaller than minimum allowed. Use minimum height.
- params.height = minHeight;
- } else {
- // Use the suggested height. Cap it at the maximum available height.
- params.height = Math.min(Math.min(suggestedListViewHeight, maxHeight), height);
+ private static final int BASE = 100;
+ private static final float LOGS_SCALE = 1f / computeLog(1, BASE);
+
+ private static float computeLog(float t, int base) {
+ return (float) (1 - Math.pow(base, -t));
}
- mListView.setLayoutParams(params);
- }
- private void setOverflowWidth() {
- mOverflowWidth = 0;
- for (int i = 0; i < mListView.getAdapter().getCount(); i++) {
- MenuItem menuItem = (MenuItem) mListView.getAdapter().getItem(i);
- Preconditions.checkNotNull(menuItem);
- mListViewItemWidthCalculator.setText(menuItem.getTitle());
- mListViewItemWidthCalculator.measure(
- MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- mOverflowWidth = Math.max(
- mListViewItemWidthCalculator.getMeasuredWidth(), mOverflowWidth);
+ @Override
+ public float getInterpolation(float t) {
+ return 1 - computeLog(1 - t, BASE) * LOGS_SCALE;
}
}
- private ListView createOverflowListView() {
- final Context context = mContentView.getContext();
- final ListView overflowListView = new ListView(context);
- overflowListView.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
- overflowListView.setDivider(null);
- overflowListView.setDividerHeight(0);
-
- final int viewTypeCount = 2;
- final int stringLabelViewType = 0;
- final int iconOnlyViewType = 1;
- final ArrayAdapter overflowListViewAdapter =
- new ArrayAdapter<MenuItem>(context, 0) {
- @Override
- public int getViewTypeCount() {
- return viewTypeCount;
- }
-
- @Override
- public int getItemViewType(int position) {
- if (isIconOnlyMenuItem(getItem(position))) {
- return iconOnlyViewType;
- }
- return stringLabelViewType;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (getItemViewType(position) == iconOnlyViewType) {
- return getIconOnlyView(position, convertView);
- }
- return getStringTitleView(position, convertView);
- }
+ /**
+ * A helper for generating views for the overflow panel.
+ */
+ private static final class OverflowPanelViewHelper {
- private View getStringTitleView(int position, View convertView) {
- TextView menuButton;
- if (convertView != null) {
- menuButton = (TextView) convertView;
- } else {
- menuButton = createOverflowMenuItemButton(context);
- }
- MenuItem menuItem = getItem(position);
- menuButton.setText(menuItem.getTitle());
- menuButton.setContentDescription(menuItem.getTitle());
- menuButton.setMinimumWidth(mOverflowWidth);
- return menuButton;
- }
+ private static final int NUM_OF_VIEW_TYPES = 2;
+ private static final int VIEW_TYPE_STRING_TITLE = 0;
+ private static final int VIEW_TYPE_ICON_ONLY = 1;
- private View getIconOnlyView(int position, View convertView) {
- View menuButton;
- if (convertView != null) {
- menuButton = convertView;
- } else {
- menuButton = LayoutInflater.from(context).inflate(
- R.layout.floating_popup_overflow_image_list_item, null);
- }
- MenuItem menuItem = getItem(position);
- ((ImageView) menuButton
- .findViewById(R.id.floating_toolbar_menu_item_image_button))
- .setImageDrawable(menuItem.getIcon());
- menuButton.setMinimumWidth(mOverflowWidth);
- return menuButton;
- }
- };
- overflowListView.setAdapter(overflowListViewAdapter);
- return overflowListView;
- }
- }
+ private final TextView mStringTitleViewCalculator;
+ private final View mIconOnlyViewCalculator;
+ private final Context mContext;
- /**
- * A helper for fading in or out a view.
- */
- private static final class ViewFader {
+ public OverflowPanelViewHelper(Context context) {
+ mContext = Preconditions.checkNotNull(context);
+ mStringTitleViewCalculator = getStringTitleView(null, 0, null);
+ mIconOnlyViewCalculator = getIconOnlyView(null, 0, null);
+ }
- private static final int FADE_OUT_DURATION = 250;
- private static final int FADE_IN_DURATION = 150;
+ public int getViewTypeCount() {
+ return NUM_OF_VIEW_TYPES;
+ }
- private final View mView;
- private final ObjectAnimator mFadeOutAnimation;
- private final ObjectAnimator mFadeInAnimation;
+ public View getView(MenuItem menuItem, int minimumWidth, View convertView) {
+ Preconditions.checkNotNull(menuItem);
+ if (getItemViewType(menuItem) == VIEW_TYPE_ICON_ONLY) {
+ return getIconOnlyView(menuItem, minimumWidth, convertView);
+ }
+ return getStringTitleView(menuItem, minimumWidth, convertView);
+ }
- private ViewFader(View view) {
- mView = Preconditions.checkNotNull(view);
- mFadeOutAnimation = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0)
- .setDuration(FADE_OUT_DURATION);
- mFadeInAnimation = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1)
- .setDuration(FADE_IN_DURATION);
- }
+ public int getItemViewType(MenuItem menuItem) {
+ Preconditions.checkNotNull(menuItem);
+ if (isIconOnlyMenuItem(menuItem)) {
+ return VIEW_TYPE_ICON_ONLY;
+ }
+ return VIEW_TYPE_STRING_TITLE;
+ }
- public void fadeIn(boolean animate) {
- cancelFadeAnimations();
- if (animate) {
- mFadeInAnimation.start();
- } else {
- mView.setAlpha(1);
+ public int calculateWidth(MenuItem menuItem) {
+ final View calculator;
+ if (isIconOnlyMenuItem(menuItem)) {
+ ((ImageView) mIconOnlyViewCalculator
+ .findViewById(R.id.floating_toolbar_menu_item_image_button))
+ .setImageDrawable(menuItem.getIcon());
+ calculator = mIconOnlyViewCalculator;
+ } else {
+ mStringTitleViewCalculator.setText(menuItem.getTitle());
+ calculator = mStringTitleViewCalculator;
+ }
+ calculator.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ return calculator.getMeasuredWidth();
}
- }
- public void fadeOut(boolean animate) {
- cancelFadeAnimations();
- if (animate) {
- mFadeOutAnimation.start();
- } else {
- mView.setAlpha(0);
+ private TextView getStringTitleView(
+ MenuItem menuItem, int minimumWidth, View convertView) {
+ TextView menuButton;
+ if (convertView != null) {
+ menuButton = (TextView) convertView;
+ } else {
+ menuButton = (TextView) LayoutInflater.from(mContext)
+ .inflate(R.layout.floating_popup_overflow_list_item, null);
+ menuButton.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ }
+ if (menuItem != null) {
+ menuButton.setText(menuItem.getTitle());
+ menuButton.setContentDescription(menuItem.getTitle());
+ menuButton.setMinimumWidth(minimumWidth);
+ }
+ return menuButton;
}
- }
- private void cancelFadeAnimations() {
- mFadeInAnimation.cancel();
- mFadeOutAnimation.cancel();
+ private View getIconOnlyView(
+ MenuItem menuItem, int minimumWidth, View convertView) {
+ View menuButton;
+ if (convertView != null) {
+ menuButton = convertView;
+ } else {
+ menuButton = LayoutInflater.from(mContext).inflate(
+ R.layout.floating_popup_overflow_image_list_item, null);
+ menuButton.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ }
+ if (menuItem != null) {
+ ((ImageView) menuButton
+ .findViewById(R.id.floating_toolbar_menu_item_image_button))
+ .setImageDrawable(menuItem.getIcon());
+ menuButton.setMinimumWidth(minimumWidth);
+ }
+ return menuButton;
+ }
}
}
@@ -1453,14 +1596,6 @@ public final class FloatingToolbar {
return menuItemButton;
}
- /**
- * Creates and returns a styled floating toolbar overflow list view item.
- */
- private static TextView createOverflowMenuItemButton(Context context) {
- return (TextView) LayoutInflater.from(context)
- .inflate(R.layout.floating_popup_overflow_list_item, null);
- }
-
private static ViewGroup createContentContainer(Context context) {
ViewGroup contentContainer = (ViewGroup) LayoutInflater.from(context)
.inflate(R.layout.floating_popup_container, null);
@@ -1468,7 +1603,7 @@ public final class FloatingToolbar {
return contentContainer;
}
- private static PopupWindow createPopupWindow(View content) {
+ private static PopupWindow createPopupWindow(ViewGroup content) {
ViewGroup popupContentHolder = new LinearLayout(content.getContext());
PopupWindow popupWindow = new PopupWindow(popupContentHolder);
// TODO: Use .setLayoutInScreenEnabled(true) instead of .setClippingEnabled(false)
@@ -1490,11 +1625,9 @@ public final class FloatingToolbar {
* @param view The view to animate
*/
private static AnimatorSet createEnterAnimation(View view) {
- AnimatorSet animation = new AnimatorSet();
+ AnimatorSet animation = new AnimatorSet();
animation.playTogether(
- ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150),
- // Make sure that view.x is always fixed throughout the duration of this animation.
- ObjectAnimator.ofFloat(view, View.X, view.getX(), view.getX()));
+ ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150));
return animation;
}
@@ -1525,13 +1658,4 @@ public final class FloatingToolbar {
a.recycle();
return new ContextThemeWrapper(originalContext, themeId);
}
-
- private static int getEstimatedToolbarHeight(Context context) {
- return context.getResources().getDimensionPixelSize(R.dimen.floating_toolbar_height);
- }
-
- private static int getEstimatedOpenOverflowButtonWidth(Context context) {
- return context.getResources()
- .getDimensionPixelSize(R.dimen.floating_toolbar_menu_button_minimum_width);
- }
-}
+} \ No newline at end of file
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/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index e339c44db16e..592576bb41d0 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -137,7 +137,7 @@ public class PointerLocationView extends View implements InputDeviceListener,
super(c);
setFocusableInTouchMode(true);
- mIm = (InputManager)c.getSystemService(Context.INPUT_SERVICE);
+ mIm = c.getSystemService(InputManager.class);
mVC = ViewConfiguration.get(c);
mTextPaint = new Paint();
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..2940f6c7fc10 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1460,6 +1460,12 @@
android:description="@string/permdesc_killBackgroundProcesses"
android:protectionLevel="normal" />
+ <!-- @SystemApi @hide Allows an application to query process states and current
+ OOM adjustment scores.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE"
+ android:protectionLevel="signature|privileged|development" />
+
<!-- @SystemApi @hide Allows an application to retrieve a package's importance.
This permission is not available to third party applications. -->
<permission android:name="android.permission.GET_PACKAGE_IMPORTANCE"
@@ -1877,6 +1883,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/anim/ft_avd_toarrow_rectangle_1_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_1_animation.xml
new file mode 100644
index 000000000000..e6a28bb4a2b2
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_1_animation.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="600"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M -6.5,0.0 c 1.08333,-1.0 5.41667,-5.0 6.5,-6.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_4" />
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="rotation"
+ android:valueFrom="-45.0"
+ android:valueTo="-45.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="150"
+ android:propertyName="rotation"
+ android:valueFrom="-45.0"
+ android:valueTo="0.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_1_pivot_0_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_1_pivot_0_animation.xml
new file mode 100644
index 000000000000..c2414de11917
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_1_pivot_0_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="200"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 0.0,0.0 c 0.75,0.0 3.75,0.0 4.5,0.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_5" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_1_pivot_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_1_pivot_animation.xml
new file mode 100644
index 000000000000..593a0eaf0c9b
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_1_pivot_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="450"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 4.5,0.0 c -0.75,0.0 -3.75,0.0 -4.5,0.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_0" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_2_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_2_animation.xml
new file mode 100644
index 000000000000..5b09a41780af
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_2_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="600"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M -8.0,0.0 c 1.33333,0.0 6.66667,0.0 8.0,0.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_3" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_2_pivot_0_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_2_pivot_0_animation.xml
new file mode 100644
index 000000000000..dccbc67de2c5
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_2_pivot_0_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="250"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 0.0,0.0 c 1.66667,0.0 8.5,0.0 10.0,0.0 c 0.13052,0.0 -0.83333,0.0 -1.0,0.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_2" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_2_pivot_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_2_pivot_animation.xml
new file mode 100644
index 000000000000..c1c0c1c3aa51
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_2_pivot_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="516"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 9.0,0.0 c -1.5,0.0 -7.5,0.0 -9.0,0.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_6" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_3_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_3_animation.xml
new file mode 100644
index 000000000000..df69bf75a279
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_3_animation.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="600"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M -6.5,0.0 c 1.08333,1.0 5.41667,5.0 6.5,6.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_4" />
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="rotation"
+ android:valueFrom="45.0"
+ android:valueTo="45.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="150"
+ android:propertyName="rotation"
+ android:valueFrom="45.0"
+ android:valueTo="0.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_3_pivot_0_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_3_pivot_0_animation.xml
new file mode 100644
index 000000000000..c2414de11917
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_3_pivot_0_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="200"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 0.0,0.0 c 0.75,0.0 3.75,0.0 4.5,0.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_5" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_3_pivot_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_3_pivot_animation.xml
new file mode 100644
index 000000000000..593a0eaf0c9b
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_3_pivot_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="450"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 4.5,0.0 c -0.75,0.0 -3.75,0.0 -4.5,0.0"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_0" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_4_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_4_animation.xml
new file mode 100644
index 000000000000..a3753302e588
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_4_animation.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 0.0,6.0 c -1.08333,-1.0 -5.41667,-5.0 -6.5,-6.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="rotation"
+ android:valueFrom="0.0"
+ android:valueTo="45.0"
+ android:valueType="floatType"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_1" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_5_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_5_animation.xml
new file mode 100644
index 000000000000..ae7ee47f9656
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_5_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="250"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 0.0,0.0 c -1.33333,0.0 -6.66667,0.0 -8.0,0.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_6_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_6_animation.xml
new file mode 100644
index 000000000000..3b872af636f8
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_6_animation.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 0.0,-6.0 c -1.08333,1.0 -5.41667,5.0 -6.5,6.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="rotation"
+ android:valueFrom="0.0"
+ android:valueTo="-45.0"
+ android:valueType="floatType"
+ android:interpolator="@interpolator/ft_avd_toarrow_animation_interpolator_1" />
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_path_1_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_path_1_animation.xml
new file mode 100644
index 000000000000..94e04b4ebf33
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_path_1_animation.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -4.54266574364,-1.0 l 9.08533148727,0.0 c 0.528721110251,0.0 0.957334256364,0.428613146113 0.957334256364,0.957334256364 l 0.0,0.0853314872718 c 0.0,0.528721110251 -0.428613146113,0.957334256364 -0.957334256364,0.957334256364 l -9.08533148727,0.0 c -0.528721110251,0.0 -0.957334256364,-0.428613146113 -0.957334256364,-0.957334256364 l 0.0,-0.0853314872718 c 0.0,-0.528721110251 0.428613146113,-0.957334256364 0.957334256364,-0.957334256364 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -4.54266574364,-1.0 l 9.08533148727,0.0 c 0.528721110251,0.0 0.957334256364,0.428613146113 0.957334256364,0.957334256364 l 0.0,0.0853314872718 c 0.0,0.528721110251 -0.428613146113,0.957334256364 -0.957334256364,0.957334256364 l -9.08533148727,0.0 c -0.528721110251,0.0 -0.957334256364,-0.428613146113 -0.957334256364,-0.957334256364 l 0.0,-0.0853314872718 c 0.0,-0.528721110251 0.428613146113,-0.957334256364 0.957334256364,-0.957334256364 Z"
+ android:valueTo="M -3.54375000003,-1.21249999999 l 7.08750000005,0.0 c 0.669645259129,0.0 1.21249999999,0.542854740865 1.21249999999,1.21249999999 l 0.0,0.0 c 0.0,0.669645259129 -0.542854740865,1.21249999999 -1.21249999999,1.21249999999 l -7.08750000005,0.0 c -0.669645259129,0.0 -1.21249999999,-0.542854740865 -1.21249999999,-1.21249999999 l 0.0,0.0 c 0.0,-0.669645259129 0.542854740865,-1.21249999999 1.21249999999,-1.21249999999 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -3.54375000003,-1.21249999999 l 7.08750000005,0.0 c 0.669645259129,0.0 1.21249999999,0.542854740865 1.21249999999,1.21249999999 l 0.0,0.0 c 0.0,0.669645259129 -0.542854740865,1.21249999999 -1.21249999999,1.21249999999 l -7.08750000005,0.0 c -0.669645259129,0.0 -1.21249999999,-0.542854740865 -1.21249999999,-1.21249999999 l 0.0,0.0 c 0.0,-0.669645259129 0.542854740865,-1.21249999999 1.21249999999,-1.21249999999 Z"
+ android:valueTo="M -1.68627963028,-1.62527119327 l 3.37255926056,0.0 c 0.897612494333,0.0 1.62527119327,0.727658698938 1.62527119327,1.62527119327 l 0.0,0.0 c 0.0,0.897612494333 -0.727658698938,1.62527119327 -1.62527119327,1.62527119327 l -3.37255926056,0.0 c -0.897612494333,0.0 -1.62527119327,-0.727658698938 -1.62527119327,-1.62527119327 l 0.0,0.0 c 0.0,-0.897612494333 0.727658698938,-1.62527119327 1.62527119327,-1.62527119327 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -1.68627963028,-1.62527119327 l 3.37255926056,0.0 c 0.897612494333,0.0 1.62527119327,0.727658698938 1.62527119327,1.62527119327 l 0.0,0.0 c 0.0,0.897612494333 -0.727658698938,1.62527119327 -1.62527119327,1.62527119327 l -3.37255926056,0.0 c -0.897612494333,0.0 -1.62527119327,-0.727658698938 -1.62527119327,-1.62527119327 l 0.0,0.0 c 0.0,-0.897612494333 0.727658698938,-1.62527119327 1.62527119327,-1.62527119327 Z"
+ android:valueTo="M -0.866611597071,-1.8074196451 l 1.73322319414,0.0 c 0.998210306475,0.0 1.8074196451,0.80920933862 1.8074196451,1.8074196451 l 0.0,0.0 c 0.0,0.998210306475 -0.80920933862,1.8074196451 -1.8074196451,1.8074196451 l -1.73322319414,0.0 c -0.998210306475,0.0 -1.8074196451,-0.80920933862 -1.8074196451,-1.8074196451 l 0.0,0.0 c 0.0,-0.998210306475 0.80920933862,-1.8074196451 1.8074196451,-1.8074196451 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.866611597071,-1.8074196451 l 1.73322319414,0.0 c 0.998210306475,0.0 1.8074196451,0.80920933862 1.8074196451,1.8074196451 l 0.0,0.0 c 0.0,0.998210306475 -0.80920933862,1.8074196451 -1.8074196451,1.8074196451 l -1.73322319414,0.0 c -0.998210306475,0.0 -1.8074196451,-0.80920933862 -1.8074196451,-1.8074196451 l 0.0,0.0 c 0.0,-0.998210306475 0.80920933862,-1.8074196451 1.8074196451,-1.8074196451 Z"
+ android:valueTo="M -0.416762258226,-1.90738616484 l 0.833524516453,0.0 c 1.05342029082,0.0 1.90738616484,0.853965874019 1.90738616484,1.90738616484 l 0.0,0.0 c 0.0,1.05342029082 -0.853965874019,1.90738616484 -1.90738616484,1.90738616484 l -0.833524516453,0.0 c -1.05342029082,0.0 -1.90738616484,-0.853965874019 -1.90738616484,-1.90738616484 l 0.0,0.0 c 0.0,-1.05342029082 0.853965874019,-1.90738616484 1.90738616484,-1.90738616484 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.416762258226,-1.90738616484 l 0.833524516453,0.0 c 1.05342029082,0.0 1.90738616484,0.853965874019 1.90738616484,1.90738616484 l 0.0,0.0 c 0.0,1.05342029082 -0.853965874019,1.90738616484 -1.90738616484,1.90738616484 l -0.833524516453,0.0 c -1.05342029082,0.0 -1.90738616484,-0.853965874019 -1.90738616484,-1.90738616484 l 0.0,0.0 c 0.0,-1.05342029082 0.853965874019,-1.90738616484 1.90738616484,-1.90738616484 Z"
+ android:valueTo="M -0.163643891635,-1.96363469075 l 0.32728778327,0.0 c 1.08448549388,0.0 1.96363469075,0.87914919687 1.96363469075,1.96363469075 l 0.0,0.0 c 0.0,1.08448549388 -0.87914919687,1.96363469075 -1.96363469075,1.96363469075 l -0.32728778327,0.0 c -1.08448549388,0.0 -1.96363469075,-0.87914919687 -1.96363469075,-1.96363469075 l 0.0,0.0 c 0.0,-1.08448549388 0.87914919687,-1.96363469075 1.96363469075,-1.96363469075 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.163643891635,-1.96363469075 l 0.32728778327,0.0 c 1.08448549388,0.0 1.96363469075,0.87914919687 1.96363469075,1.96363469075 l 0.0,0.0 c 0.0,1.08448549388 -0.87914919687,1.96363469075 -1.96363469075,1.96363469075 l -0.32728778327,0.0 c -1.08448549388,0.0 -1.96363469075,-0.87914919687 -1.96363469075,-1.96363469075 l 0.0,0.0 c 0.0,-1.08448549388 0.87914919687,-1.96363469075 1.96363469075,-1.96363469075 Z"
+ android:valueTo="M -0.0368976091105,-1.99180053131 l 0.073795218221,0.0 c 1.10004105809,0.0 1.99180053131,0.891759473223 1.99180053131,1.99180053131 l 0.0,0.0 c 0.0,1.10004105809 -0.891759473223,1.99180053131 -1.99180053131,1.99180053131 l -0.073795218221,0.0 c -1.10004105809,0.0 -1.99180053131,-0.891759473223 -1.99180053131,-1.99180053131 l 0.0,0.0 c 0.0,-1.10004105809 0.891759473223,-1.99180053131 1.99180053131,-1.99180053131 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.0368976091105,-1.99180053131 l 0.073795218221,0.0 c 1.10004105809,0.0 1.99180053131,0.891759473223 1.99180053131,1.99180053131 l 0.0,0.0 c 0.0,1.10004105809 -0.891759473223,1.99180053131 -1.99180053131,1.99180053131 l -0.073795218221,0.0 c -1.10004105809,0.0 -1.99180053131,-0.891759473223 -1.99180053131,-1.99180053131 l 0.0,0.0 c 0.0,-1.10004105809 0.891759473223,-1.99180053131 1.99180053131,-1.99180053131 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_path_2_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_path_2_animation.xml
new file mode 100644
index 000000000000..423f8d73f900
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_path_2_animation.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -4.54266574364,-1.0 l 9.08533148727,0.0 c 0.528721110251,0.0 0.957334256364,0.428613146113 0.957334256364,0.957334256364 l 0.0,0.0853314872718 c 0.0,0.528721110251 -0.428613146113,0.957334256364 -0.957334256364,0.957334256364 l -9.08533148727,0.0 c -0.528721110251,0.0 -0.957334256364,-0.428613146113 -0.957334256364,-0.957334256364 l 0.0,-0.0853314872718 c 0.0,-0.528721110251 0.428613146113,-0.957334256364 0.957334256364,-0.957334256364 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -4.54266574364,-1.0 l 9.08533148727,0.0 c 0.528721110251,0.0 0.957334256364,0.428613146113 0.957334256364,0.957334256364 l 0.0,0.0853314872718 c 0.0,0.528721110251 -0.428613146113,0.957334256364 -0.957334256364,0.957334256364 l -9.08533148727,0.0 c -0.528721110251,0.0 -0.957334256364,-0.428613146113 -0.957334256364,-0.957334256364 l 0.0,-0.0853314872718 c 0.0,-0.528721110251 0.428613146113,-0.957334256364 0.957334256364,-0.957334256364 Z"
+ android:valueTo="M -2.49262086719,-1.44608425174 l 4.98524173437,0.0 c 0.79865027916,0.0 1.44608425174,0.647433972576 1.44608425174,1.44608425174 l 0.0,0.0 c 0.0,0.79865027916 -0.647433972576,1.44608425174 -1.44608425174,1.44608425174 l -4.98524173437,0.0 c -0.79865027916,0.0 -1.44608425174,-0.647433972576 -1.44608425174,-1.44608425174 l 0.0,0.0 c 0.0,-0.79865027916 0.647433972576,-1.44608425174 1.44608425174,-1.44608425174 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -2.49262086719,-1.44608425174 l 4.98524173437,0.0 c 0.79865027916,0.0 1.44608425174,0.647433972576 1.44608425174,1.44608425174 l 0.0,0.0 c 0.0,0.79865027916 -0.647433972576,1.44608425174 -1.44608425174,1.44608425174 l -4.98524173437,0.0 c -0.79865027916,0.0 -1.44608425174,-0.647433972576 -1.44608425174,-1.44608425174 l 0.0,0.0 c 0.0,-0.79865027916 0.647433972576,-1.44608425174 1.44608425174,-1.44608425174 Z"
+ android:valueTo="M -1.39285008918,-1.69047775796 l 2.78570017836,0.0 c 0.933625085597,0.0 1.69047775796,0.756852672362 1.69047775796,1.69047775796 l 0.0,0.0 c 0.0,0.933625085597 -0.756852672362,1.69047775796 -1.69047775796,1.69047775796 l -2.78570017836,0.0 c -0.933625085597,0.0 -1.69047775796,-0.756852672362 -1.69047775796,-1.69047775796 l 0.0,0.0 c 0.0,-0.933625085597 0.756852672362,-1.69047775796 1.69047775796,-1.69047775796 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -1.39285008918,-1.69047775796 l 2.78570017836,0.0 c 0.933625085597,0.0 1.69047775796,0.756852672362 1.69047775796,1.69047775796 l 0.0,0.0 c 0.0,0.933625085597 -0.756852672362,1.69047775796 -1.69047775796,1.69047775796 l -2.78570017836,0.0 c -0.933625085597,0.0 -1.69047775796,-0.756852672362 -1.69047775796,-1.69047775796 l 0.0,0.0 c 0.0,-0.933625085597 0.756852672362,-1.69047775796 1.69047775796,-1.69047775796 Z"
+ android:valueTo="M -0.77117021921,-1.82862884018 l 1.54234043842,0.0 c 1.00992382147,0.0 1.82862884018,0.818705018702 1.82862884018,1.82862884018 l 0.0,0.0 c 0.0,1.00992382147 -0.818705018702,1.82862884018 -1.82862884018,1.82862884018 l -1.54234043842,0.0 c -1.00992382147,0.0 -1.82862884018,-0.818705018702 -1.82862884018,-1.82862884018 l 0.0,0.0 c 0.0,-1.00992382147 0.818705018702,-1.82862884018 1.82862884018,-1.82862884018 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.77117021921,-1.82862884018 l 1.54234043842,0.0 c 1.00992382147,0.0 1.82862884018,0.818705018702 1.82862884018,1.82862884018 l 0.0,0.0 c 0.0,1.00992382147 -0.818705018702,1.82862884018 -1.82862884018,1.82862884018 l -1.54234043842,0.0 c -1.00992382147,0.0 -1.82862884018,-0.818705018702 -1.82862884018,-1.82862884018 l 0.0,0.0 c 0.0,-1.00992382147 0.818705018702,-1.82862884018 1.82862884018,-1.82862884018 Z"
+ android:valueTo="M -0.387649645988,-1.91385563422 l 0.775299291975,0.0 c 1.0569932801,0.0 1.91385563422,0.856862354124 1.91385563422,1.91385563422 l 0.0,0.0 c 0.0,1.0569932801 -0.856862354124,1.91385563422 -1.91385563422,1.91385563422 l -0.775299291975,0.0 c -1.0569932801,0.0 -1.91385563422,-0.856862354124 -1.91385563422,-1.91385563422 l 0.0,0.0 c 0.0,-1.0569932801 0.856862354124,-1.91385563422 1.91385563422,-1.91385563422 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.387649645988,-1.91385563422 l 0.775299291975,0.0 c 1.0569932801,0.0 1.91385563422,0.856862354124 1.91385563422,1.91385563422 l 0.0,0.0 c 0.0,1.0569932801 -0.856862354124,1.91385563422 -1.91385563422,1.91385563422 l -0.775299291975,0.0 c -1.0569932801,0.0 -1.91385563422,-0.856862354124 -1.91385563422,-1.91385563422 l 0.0,0.0 c 0.0,-1.0569932801 0.856862354124,-1.91385563422 1.91385563422,-1.91385563422 Z"
+ android:valueTo="M -0.156963485208,-1.96511922551 l 0.313926970417,0.0 c 1.08530537979,0.0 1.96511922551,0.879813845722 1.96511922551,1.96511922551 l 0.0,0.0 c 0.0,1.08530537979 -0.879813845722,1.96511922551 -1.96511922551,1.96511922551 l -0.313926970417,0.0 c -1.08530537979,0.0 -1.96511922551,-0.879813845722 -1.96511922551,-1.96511922551 l 0.0,0.0 c 0.0,-1.08530537979 0.879813845722,-1.96511922551 1.96511922551,-1.96511922551 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.156963485208,-1.96511922551 l 0.313926970417,0.0 c 1.08530537979,0.0 1.96511922551,0.879813845722 1.96511922551,1.96511922551 l 0.0,0.0 c 0.0,1.08530537979 -0.879813845722,1.96511922551 -1.96511922551,1.96511922551 l -0.313926970417,0.0 c -1.08530537979,0.0 -1.96511922551,-0.879813845722 -1.96511922551,-1.96511922551 l 0.0,0.0 c 0.0,-1.08530537979 0.879813845722,-1.96511922551 1.96511922551,-1.96511922551 Z"
+ android:valueTo="M -0.0362224951386,-1.99195055664 l 0.0724449902773,0.0 c 1.10012391479,0.0 1.99195055664,0.89182664185 1.99195055664,1.99195055664 l 0.0,0.0 c 0.0,1.10012391479 -0.89182664185,1.99195055664 -1.99195055664,1.99195055664 l -0.0724449902773,0.0 c -1.10012391479,0.0 -1.99195055664,-0.89182664185 -1.99195055664,-1.99195055664 l 0.0,0.0 c 0.0,-1.10012391479 0.89182664185,-1.99195055664 1.99195055664,-1.99195055664 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.0362224951386,-1.99195055664 l 0.0724449902773,0.0 c 1.10012391479,0.0 1.99195055664,0.89182664185 1.99195055664,1.99195055664 l 0.0,0.0 c 0.0,1.10012391479 -0.89182664185,1.99195055664 -1.99195055664,1.99195055664 l -0.0724449902773,0.0 c -1.10012391479,0.0 -1.99195055664,-0.89182664185 -1.99195055664,-1.99195055664 l 0.0,0.0 c 0.0,-1.10012391479 0.89182664185,-1.99195055664 1.99195055664,-1.99195055664 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_path_3_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_path_3_animation.xml
new file mode 100644
index 000000000000..444f6b65c724
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_path_3_animation.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyName="pathData"
+ android:valueFrom="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -6.04266574364,-1.0 l 12.0853314873,0.0 c 0.528721110251,0.0 0.957334256364,0.428613146113 0.957334256364,0.957334256364 l 0.0,0.0853314872718 c 0.0,0.528721110251 -0.428613146113,0.957334256364 -0.957334256364,0.957334256364 l -12.0853314873,0.0 c -0.528721110251,0.0 -0.957334256364,-0.428613146113 -0.957334256364,-0.957334256364 l 0.0,-0.0853314872718 c 0.0,-0.528721110251 0.428613146113,-0.957334256364 0.957334256364,-0.957334256364 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -6.04266574364,-1.0 l 12.0853314873,0.0 c 0.528721110251,0.0 0.957334256364,0.428613146113 0.957334256364,0.957334256364 l 0.0,0.0853314872718 c 0.0,0.528721110251 -0.428613146113,0.957334256364 -0.957334256364,0.957334256364 l -12.0853314873,0.0 c -0.528721110251,0.0 -0.957334256364,-0.428613146113 -0.957334256364,-0.957334256364 l 0.0,-0.0853314872718 c 0.0,-0.528721110251 0.428613146113,-0.957334256364 0.957334256364,-0.957334256364 Z"
+ android:valueTo="M -3.32349448958,-1.44608425174 l 6.64698897916,0.0 c 0.79865027916,0.0 1.44608425174,0.647433972576 1.44608425174,1.44608425174 l 0.0,0.0 c 0.0,0.79865027916 -0.647433972576,1.44608425174 -1.44608425174,1.44608425174 l -6.64698897916,0.0 c -0.79865027916,0.0 -1.44608425174,-0.647433972576 -1.44608425174,-1.44608425174 l 0.0,0.0 c 0.0,-0.79865027916 0.647433972576,-1.44608425174 1.44608425174,-1.44608425174 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -3.32349448958,-1.44608425174 l 6.64698897916,0.0 c 0.79865027916,0.0 1.44608425174,0.647433972576 1.44608425174,1.44608425174 l 0.0,0.0 c 0.0,0.79865027916 -0.647433972576,1.44608425174 -1.44608425174,1.44608425174 l -6.64698897916,0.0 c -0.79865027916,0.0 -1.44608425174,-0.647433972576 -1.44608425174,-1.44608425174 l 0.0,0.0 c 0.0,-0.79865027916 0.647433972576,-1.44608425174 1.44608425174,-1.44608425174 Z"
+ android:valueTo="M -1.85713345224,-1.69047775796 l 3.71426690449,0.0 c 0.933625085597,0.0 1.69047775796,0.756852672362 1.69047775796,1.69047775796 l 0.0,0.0 c 0.0,0.933625085597 -0.756852672362,1.69047775796 -1.69047775796,1.69047775796 l -3.71426690449,0.0 c -0.933625085597,0.0 -1.69047775796,-0.756852672362 -1.69047775796,-1.69047775796 l 0.0,0.0 c 0.0,-0.933625085597 0.756852672362,-1.69047775796 1.69047775796,-1.69047775796 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -1.85713345224,-1.69047775796 l 3.71426690449,0.0 c 0.933625085597,0.0 1.69047775796,0.756852672362 1.69047775796,1.69047775796 l 0.0,0.0 c 0.0,0.933625085597 -0.756852672362,1.69047775796 -1.69047775796,1.69047775796 l -3.71426690449,0.0 c -0.933625085597,0.0 -1.69047775796,-0.756852672362 -1.69047775796,-1.69047775796 l 0.0,0.0 c 0.0,-0.933625085597 0.756852672362,-1.69047775796 1.69047775796,-1.69047775796 Z"
+ android:valueTo="M -1.02822695895,-1.82862884018 l 2.05645391789,0.0 c 1.00992382147,0.0 1.82862884018,0.818705018702 1.82862884018,1.82862884018 l 0.0,0.0 c 0.0,1.00992382147 -0.818705018702,1.82862884018 -1.82862884018,1.82862884018 l -2.05645391789,0.0 c -1.00992382147,0.0 -1.82862884018,-0.818705018702 -1.82862884018,-1.82862884018 l 0.0,0.0 c 0.0,-1.00992382147 0.818705018702,-1.82862884018 1.82862884018,-1.82862884018 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -1.02822695895,-1.82862884018 l 2.05645391789,0.0 c 1.00992382147,0.0 1.82862884018,0.818705018702 1.82862884018,1.82862884018 l 0.0,0.0 c 0.0,1.00992382147 -0.818705018702,1.82862884018 -1.82862884018,1.82862884018 l -2.05645391789,0.0 c -1.00992382147,0.0 -1.82862884018,-0.818705018702 -1.82862884018,-1.82862884018 l 0.0,0.0 c 0.0,-1.00992382147 0.818705018702,-1.82862884018 1.82862884018,-1.82862884018 Z"
+ android:valueTo="M -0.51686619465,-1.91385563422 l 1.0337323893,0.0 c 1.0569932801,0.0 1.91385563422,0.856862354124 1.91385563422,1.91385563422 l 0.0,0.0 c 0.0,1.0569932801 -0.856862354124,1.91385563422 -1.91385563422,1.91385563422 l -1.0337323893,0.0 c -1.0569932801,0.0 -1.91385563422,-0.856862354124 -1.91385563422,-1.91385563422 l 0.0,0.0 c 0.0,-1.0569932801 0.856862354124,-1.91385563422 1.91385563422,-1.91385563422 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.51686619465,-1.91385563422 l 1.0337323893,0.0 c 1.0569932801,0.0 1.91385563422,0.856862354124 1.91385563422,1.91385563422 l 0.0,0.0 c 0.0,1.0569932801 -0.856862354124,1.91385563422 -1.91385563422,1.91385563422 l -1.0337323893,0.0 c -1.0569932801,0.0 -1.91385563422,-0.856862354124 -1.91385563422,-1.91385563422 l 0.0,0.0 c 0.0,-1.0569932801 0.856862354124,-1.91385563422 1.91385563422,-1.91385563422 Z"
+ android:valueTo="M -0.209284646944,-1.96511922551 l 0.418569293889,0.0 c 1.08530537979,0.0 1.96511922551,0.879813845722 1.96511922551,1.96511922551 l 0.0,0.0 c 0.0,1.08530537979 -0.879813845722,1.96511922551 -1.96511922551,1.96511922551 l -0.418569293889,0.0 c -1.08530537979,0.0 -1.96511922551,-0.879813845722 -1.96511922551,-1.96511922551 l 0.0,0.0 c 0.0,-1.08530537979 0.879813845722,-1.96511922551 1.96511922551,-1.96511922551 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.209284646944,-1.96511922551 l 0.418569293889,0.0 c 1.08530537979,0.0 1.96511922551,0.879813845722 1.96511922551,1.96511922551 l 0.0,0.0 c 0.0,1.08530537979 -0.879813845722,1.96511922551 -1.96511922551,1.96511922551 l -0.418569293889,0.0 c -1.08530537979,0.0 -1.96511922551,-0.879813845722 -1.96511922551,-1.96511922551 l 0.0,0.0 c 0.0,-1.08530537979 0.879813845722,-1.96511922551 1.96511922551,-1.96511922551 Z"
+ android:valueTo="M -0.0482966601849,-1.99195055664 l 0.0965933203697,0.0 c 1.10012391479,0.0 1.99195055664,0.89182664185 1.99195055664,1.99195055664 l 0.0,0.0 c 0.0,1.10012391479 -0.89182664185,1.99195055664 -1.99195055664,1.99195055664 l -0.0965933203697,0.0 c -1.10012391479,0.0 -1.99195055664,-0.89182664185 -1.99195055664,-1.99195055664 l 0.0,0.0 c 0.0,-1.10012391479 0.89182664185,-1.99195055664 1.99195055664,-1.99195055664 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.0482966601849,-1.99195055664 l 0.0965933203697,0.0 c 1.10012391479,0.0 1.99195055664,0.89182664185 1.99195055664,1.99195055664 l 0.0,0.0 c 0.0,1.10012391479 -0.89182664185,1.99195055664 -1.99195055664,1.99195055664 l -0.0965933203697,0.0 c -1.10012391479,0.0 -1.99195055664,-0.89182664185 -1.99195055664,-1.99195055664 l 0.0,0.0 c 0.0,-1.10012391479 0.89182664185,-1.99195055664 1.99195055664,-1.99195055664 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_path_4_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_path_4_animation.xml
new file mode 100644
index 000000000000..db294c7c1f6b
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_path_4_animation.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="100"
+ android:propertyName="pathData"
+ android:valueFrom="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="49"
+ android:propertyName="pathData"
+ android:valueFrom="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueTo="M -4.0950630677,-1.30952224204 l 8.1901261354,0.0 c 0.177619793131,0.0 0.32160908516,0.143989292029 0.32160908516,0.32160908516 l 0.0,1.97582631376 c 0.0,0.177619793131 -0.143989292029,0.32160908516 -0.32160908516,0.32160908516 l -8.1901261354,0.0 c -0.177619793131,0.0 -0.32160908516,-0.143989292029 -0.32160908516,-0.32160908516 l 0.0,-1.97582631376 c 0.0,-0.177619793131 0.143989292029,-0.32160908516 0.32160908516,-0.32160908516 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="50"
+ android:propertyName="pathData"
+ android:valueFrom="M -4.0950630677,-1.30952224204 l 8.1901261354,0.0 c 0.177619793131,0.0 0.32160908516,0.143989292029 0.32160908516,0.32160908516 l 0.0,1.97582631376 c 0.0,0.177619793131 -0.143989292029,0.32160908516 -0.32160908516,0.32160908516 l -8.1901261354,0.0 c -0.177619793131,0.0 -0.32160908516,-0.143989292029 -0.32160908516,-0.32160908516 l 0.0,-1.97582631376 c 0.0,-0.177619793131 0.143989292029,-0.32160908516 0.32160908516,-0.32160908516 Z"
+ android:valueTo="M -5.11454284324,-1.11013061622 l 10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="49"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.11454284324,-1.11013061622 l 10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.41755510258,-1.02355568498 l 10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="49"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.41755510258,-1.02355568498 l 10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_path_5_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_path_5_animation.xml
new file mode 100644
index 000000000000..86e4dd638a88
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_path_5_animation.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="50"
+ android:propertyName="pathData"
+ android:valueFrom="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:valueFrom="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueTo="M -0.510644950991,-1.91489250817 l 1.02128990198,0.0 c 1.05756592977,0.0 1.91489250817,0.857326578401 1.91489250817,1.91489250817 l 0.0,0.0 c 0.0,1.05756592977 -0.857326578401,1.91489250817 -1.91489250817,1.91489250817 l -1.02128990198,0.0 c -1.05756592977,0.0 -1.91489250817,-0.857326578401 -1.91489250817,-1.91489250817 l 0.0,0.0 c 0.0,-1.05756592977 0.857326578401,-1.91489250817 1.91489250817,-1.91489250817 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.510644950991,-1.91489250817 l 1.02128990198,0.0 c 1.05756592977,0.0 1.91489250817,0.857326578401 1.91489250817,1.91489250817 l 0.0,0.0 c 0.0,1.05756592977 -0.857326578401,1.91489250817 -1.91489250817,1.91489250817 l -1.02128990198,0.0 c -1.05756592977,0.0 -1.91489250817,-0.857326578401 -1.91489250817,-1.91489250817 l 0.0,0.0 c 0.0,-1.05756592977 0.857326578401,-1.91489250817 1.91489250817,-1.91489250817 Z"
+ android:valueTo="M -3.66172292328,-1.54166666667 l 7.32344584656,0.0 c 0.347908322704,0.0 0.629943743386,0.282035420682 0.629943743386,0.629943743386 l 0.0,1.82344584656 c 0.0,0.347908322704 -0.282035420682,0.629943743386 -0.629943743386,0.629943743386 l -7.32344584656,0.0 c -0.347908322704,0.0 -0.629943743386,-0.282035420682 -0.629943743386,-0.629943743386 l 0.0,-1.82344584656 c 0.0,-0.347908322704 0.282035420682,-0.629943743386 0.629943743386,-0.629943743386 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:valueFrom="M -3.66172292328,-1.54166666667 l 7.32344584656,0.0 c 0.347908322704,0.0 0.629943743386,0.282035420682 0.629943743386,0.629943743386 l 0.0,1.82344584656 c 0.0,0.347908322704 -0.282035420682,0.629943743386 -0.629943743386,0.629943743386 l -7.32344584656,0.0 c -0.347908322704,0.0 -0.629943743386,-0.282035420682 -0.629943743386,-0.629943743386 l 0.0,-1.82344584656 c 0.0,-0.347908322704 0.282035420682,-0.629943743386 0.629943743386,-0.629943743386 Z"
+ android:valueTo="M -5.80605656225,-1.22447422869 l 11.6121131245,0.0 c 0.0395282866537,0.0 0.0715722943065,0.0320440076528 0.0715722943065,0.0715722943065 l 0.0,2.30580386877 c 0.0,0.0395282866537 -0.0320440076528,0.0715722943065 -0.0715722943065,0.0715722943065 l -11.6121131245,0.0 c -0.0395282866537,0.0 -0.0715722943065,-0.0320440076528 -0.0715722943065,-0.0715722943065 l 0.0,-2.30580386877 c 0.0,-0.0395282866537 0.0320440076528,-0.0715722943065 0.0715722943065,-0.0715722943065 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.80605656225,-1.22447422869 l 11.6121131245,0.0 c 0.0395282866537,0.0 0.0715722943065,0.0320440076528 0.0715722943065,0.0715722943065 l 0.0,2.30580386877 c 0.0,0.0395282866537 -0.0320440076528,0.0715722943065 -0.0715722943065,0.0715722943065 l -11.6121131245,0.0 c -0.0395282866537,0.0 -0.0715722943065,-0.0320440076528 -0.0715722943065,-0.0715722943065 l 0.0,-2.30580386877 c 0.0,-0.0395282866537 0.0320440076528,-0.0715722943065 0.0715722943065,-0.0715722943065 Z"
+ android:valueTo="M -6.60386380145,-1.07922723971 l 13.2077276029,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.15845447942 c 0.0,0.0 0.0,0.0 0.0,0.0 l -13.2077276029,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.15845447942 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:valueFrom="M -6.60386380145,-1.07922723971 l 13.2077276029,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.15845447942 c 0.0,0.0 0.0,0.0 0.0,0.0 l -13.2077276029,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.15845447942 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -6.91679014084,-1.01664197183 l 13.8335802817,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.03328394367 c 0.0,0.0 0.0,0.0 0.0,0.0 l -13.8335802817,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.03328394367 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:valueFrom="M -6.91679014084,-1.01664197183 l 13.8335802817,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.03328394367 c 0.0,0.0 0.0,0.0 0.0,0.0 l -13.8335802817,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.03328394367 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_toarrow_rectangle_path_6_animation.xml b/core/res/res/anim/ft_avd_toarrow_rectangle_path_6_animation.xml
new file mode 100644
index 000000000000..db294c7c1f6b
--- /dev/null
+++ b/core/res/res/anim/ft_avd_toarrow_rectangle_path_6_animation.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="100"
+ android:propertyName="pathData"
+ android:valueFrom="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="49"
+ android:propertyName="pathData"
+ android:valueFrom="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueTo="M -4.0950630677,-1.30952224204 l 8.1901261354,0.0 c 0.177619793131,0.0 0.32160908516,0.143989292029 0.32160908516,0.32160908516 l 0.0,1.97582631376 c 0.0,0.177619793131 -0.143989292029,0.32160908516 -0.32160908516,0.32160908516 l -8.1901261354,0.0 c -0.177619793131,0.0 -0.32160908516,-0.143989292029 -0.32160908516,-0.32160908516 l 0.0,-1.97582631376 c 0.0,-0.177619793131 0.143989292029,-0.32160908516 0.32160908516,-0.32160908516 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="50"
+ android:propertyName="pathData"
+ android:valueFrom="M -4.0950630677,-1.30952224204 l 8.1901261354,0.0 c 0.177619793131,0.0 0.32160908516,0.143989292029 0.32160908516,0.32160908516 l 0.0,1.97582631376 c 0.0,0.177619793131 -0.143989292029,0.32160908516 -0.32160908516,0.32160908516 l -8.1901261354,0.0 c -0.177619793131,0.0 -0.32160908516,-0.143989292029 -0.32160908516,-0.32160908516 l 0.0,-1.97582631376 c 0.0,-0.177619793131 0.143989292029,-0.32160908516 0.32160908516,-0.32160908516 Z"
+ android:valueTo="M -5.11454284324,-1.11013061622 l 10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="49"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.11454284324,-1.11013061622 l 10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.2290856865,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.22026123243 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.41755510258,-1.02355568498 l 10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="49"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.41755510258,-1.02355568498 l 10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.8351102052,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.04711136995 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_1_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_1_animation.xml
new file mode 100644
index 000000000000..3869ced8afe0
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_1_animation.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="400"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M -6.5,0.0 c 1.08333,-1.0 5.41667,-5.0 6.5,-6.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="rotation"
+ android:valueFrom="-45.0"
+ android:valueTo="0.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_1_pivot_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_1_pivot_animation.xml
new file mode 100644
index 000000000000..90010a75dcfe
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_1_pivot_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 4.5,0.0 c -0.75,0.0 -3.75,0.0 -4.5,0.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_2_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_2_animation.xml
new file mode 100644
index 000000000000..b5e031e4e8b6
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_2_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M -8.0,0.0 c 1.33333,0.0 6.66667,0.0 8.0,0.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_2_pivot_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_2_pivot_animation.xml
new file mode 100644
index 000000000000..55c72c6e852c
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_2_pivot_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="216"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 9.0,0.0 c -1.5,0.0 -7.5,0.0 -9.0,0.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_3_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_3_animation.xml
new file mode 100644
index 000000000000..0524f2a21ed0
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_3_animation.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="400"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M -6.5,0.0 c 1.08333,1.0 5.41667,5.0 6.5,6.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="rotation"
+ android:valueFrom="45.0"
+ android:valueTo="0.0"
+ android:valueType="floatType"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_3_pivot_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_3_pivot_animation.xml
new file mode 100644
index 000000000000..90010a75dcfe
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_3_pivot_animation.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="300"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:pathData="M 4.5,0.0 c -0.75,0.0 -3.75,0.0 -4.5,0.0"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_path_1_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_path_1_animation.xml
new file mode 100644
index 000000000000..ced8cf50d33b
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_path_1_animation.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.349609375,-1.04296875 l 10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.349609375,-1.04296875 l 10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -4.57327108594,-1.25 l 9.14654217189,0.0 c 0.0285690903566,0.0 0.0517289140556,0.023159823699 0.0517289140556,0.0517289140556 l 0.0,2.39654217189 c 0.0,0.0285690903566 -0.023159823699,0.0517289140556 -0.0517289140556,0.0517289140556 l -9.14654217189,0.0 c -0.0285690903566,0.0 -0.0517289140556,-0.023159823699 -0.0517289140556,-0.0517289140556 l 0.0,-2.39654217189 c 0.0,-0.0285690903566 0.023159823699,-0.0517289140556 0.0517289140556,-0.0517289140556 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -4.57327108594,-1.25 l 9.14654217189,0.0 c 0.0285690903566,0.0 0.0517289140556,0.023159823699 0.0517289140556,0.0517289140556 l 0.0,2.39654217189 c 0.0,0.0285690903566 -0.023159823699,0.0517289140556 -0.0517289140556,0.0517289140556 l -9.14654217189,0.0 c -0.0285690903566,0.0 -0.0517289140556,-0.023159823699 -0.0517289140556,-0.0517289140556 l 0.0,-2.39654217189 c 0.0,-0.0285690903566 0.023159823699,-0.0517289140556 0.0517289140556,-0.0517289140556 Z"
+ android:valueTo="M -3.04137381938,-1.55960748018 l 6.08274763876,0.0 c 0.2761423749,0.0 0.5,0.2238576251 0.5,0.5 l 0.0,2.11921496035 c 0.0,0.2761423749 -0.2238576251,0.5 -0.5,0.5 l -6.08274763876,0.0 c -0.2761423749,0.0 -0.5,-0.2238576251 -0.5,-0.5 l 0.0,-2.11921496035 c 0.0,-0.2761423749 0.2238576251,-0.5 0.5,-0.5 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -3.04137381938,-1.55960748018 l 6.08274763876,0.0 c 0.2761423749,0.0 0.5,0.2238576251 0.5,0.5 l 0.0,2.11921496035 c 0.0,0.2761423749 -0.2238576251,0.5 -0.5,0.5 l -6.08274763876,0.0 c -0.2761423749,0.0 -0.5,-0.2238576251 -0.5,-0.5 l 0.0,-2.11921496035 c 0.0,-0.2761423749 0.2238576251,-0.5 0.5,-0.5 Z"
+ android:valueTo="M -1.55858989728,-1.77552577131 l 3.11717979456,0.0 c 0.677691994437,0.0 1.22706990313,0.549377908693 1.22706990313,1.22706990313 l 0.0,1.09691173636 c 0.0,0.677691994437 -0.549377908693,1.22706990313 -1.22706990313,1.22706990313 l -3.11717979456,0.0 c -0.677691994437,0.0 -1.22706990313,-0.549377908693 -1.22706990313,-1.22706990313 l 0.0,-1.09691173636 c 0.0,-0.677691994437 0.549377908693,-1.22706990313 1.22706990313,-1.22706990313 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -1.55858989728,-1.77552577131 l 3.11717979456,0.0 c 0.677691994437,0.0 1.22706990313,0.549377908693 1.22706990313,1.22706990313 l 0.0,1.09691173636 c 0.0,0.677691994437 -0.549377908693,1.22706990313 -1.22706990313,1.22706990313 l -3.11717979456,0.0 c -0.677691994437,0.0 -1.22706990313,-0.549377908693 -1.22706990313,-1.22706990313 l 0.0,-1.09691173636 c 0.0,-0.677691994437 0.549377908693,-1.22706990313 1.22706990313,-1.22706990313 Z"
+ android:valueTo="M -0.706008791281,-1.89447268498 l 1.41201758256,0.0 c 0.918635554655,0.0 1.66333681129,0.744701256633 1.66333681129,1.66333681129 l 0.0,0.462271747384 c 0.0,0.918635554655 -0.744701256633,1.66333681129 -1.66333681129,1.66333681129 l -1.41201758256,0.0 c -0.918635554655,0.0 -1.66333681129,-0.744701256633 -1.66333681129,-1.66333681129 l 0.0,-0.462271747384 c 0.0,-0.918635554655 0.744701256633,-1.66333681129 1.66333681129,-1.66333681129 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.706008791281,-1.89447268498 l 1.41201758256,0.0 c 0.918635554655,0.0 1.66333681129,0.744701256633 1.66333681129,1.66333681129 l 0.0,0.462271747384 c 0.0,0.918635554655 -0.744701256633,1.66333681129 -1.66333681129,1.66333681129 l -1.41201758256,0.0 c -0.918635554655,0.0 -1.66333681129,-0.744701256633 -1.66333681129,-1.66333681129 l 0.0,-0.462271747384 c 0.0,-0.918635554655 0.744701256633,-1.66333681129 1.66333681129,-1.66333681129 Z"
+ android:valueTo="M -0.265730251554,-1.95936709392 l 0.531460503108,0.0 c 1.03635400439,0.0 1.87648491973,0.840130915331 1.87648491973,1.87648491973 l 0.0,0.165764348389 c 0.0,1.03635400439 -0.840130915331,1.87648491973 -1.87648491973,1.87648491973 l -0.531460503108,0.0 c -1.03635400439,0.0 -1.87648491973,-0.840130915331 -1.87648491973,-1.87648491973 l 0.0,-0.165764348389 c 0.0,-1.03635400439 0.840130915331,-1.87648491973 1.87648491973,-1.87648491973 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.265730251554,-1.95936709392 l 0.531460503108,0.0 c 1.03635400439,0.0 1.87648491973,0.840130915331 1.87648491973,1.87648491973 l 0.0,0.165764348389 c 0.0,1.03635400439 -0.840130915331,1.87648491973 -1.87648491973,1.87648491973 l -0.531460503108,0.0 c -1.03635400439,0.0 -1.87648491973,-0.840130915331 -1.87648491973,-1.87648491973 l 0.0,-0.165764348389 c 0.0,-1.03635400439 0.840130915331,-1.87648491973 1.87648491973,-1.87648491973 Z"
+ android:valueTo="M -0.0581061000545,-1.99098433926 l 0.116212200109,0.0 c 1.08990562844,0.0 1.97344871252,0.883543084083 1.97344871252,1.97344871252 l 0.0,0.0350712534878 c 0.0,1.08990562844 -0.883543084083,1.97344871252 -1.97344871252,1.97344871252 l -0.116212200109,0.0 c -1.08990562844,0.0 -1.97344871252,-0.883543084083 -1.97344871252,-1.97344871252 l 0.0,-0.0350712534878 c 0.0,-1.08990562844 0.883543084083,-1.97344871252 1.97344871252,-1.97344871252 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.0581061000545,-1.99098433926 l 0.116212200109,0.0 c 1.08990562844,0.0 1.97344871252,0.883543084083 1.97344871252,1.97344871252 l 0.0,0.0350712534878 c 0.0,1.08990562844 -0.883543084083,1.97344871252 -1.97344871252,1.97344871252 l -0.116212200109,0.0 c -1.08990562844,0.0 -1.97344871252,-0.883543084083 -1.97344871252,-1.97344871252 l 0.0,-0.0350712534878 c 0.0,-1.08990562844 0.883543084083,-1.97344871252 1.97344871252,-1.97344871252 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_path_2_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_path_2_animation.xml
new file mode 100644
index 000000000000..ced8cf50d33b
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_path_2_animation.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -5.349609375,-1.04296875 l 10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -5.349609375,-1.04296875 l 10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 l -10.69921875,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0859375 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -4.57327108594,-1.25 l 9.14654217189,0.0 c 0.0285690903566,0.0 0.0517289140556,0.023159823699 0.0517289140556,0.0517289140556 l 0.0,2.39654217189 c 0.0,0.0285690903566 -0.023159823699,0.0517289140556 -0.0517289140556,0.0517289140556 l -9.14654217189,0.0 c -0.0285690903566,0.0 -0.0517289140556,-0.023159823699 -0.0517289140556,-0.0517289140556 l 0.0,-2.39654217189 c 0.0,-0.0285690903566 0.023159823699,-0.0517289140556 0.0517289140556,-0.0517289140556 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -4.57327108594,-1.25 l 9.14654217189,0.0 c 0.0285690903566,0.0 0.0517289140556,0.023159823699 0.0517289140556,0.0517289140556 l 0.0,2.39654217189 c 0.0,0.0285690903566 -0.023159823699,0.0517289140556 -0.0517289140556,0.0517289140556 l -9.14654217189,0.0 c -0.0285690903566,0.0 -0.0517289140556,-0.023159823699 -0.0517289140556,-0.0517289140556 l 0.0,-2.39654217189 c 0.0,-0.0285690903566 0.023159823699,-0.0517289140556 0.0517289140556,-0.0517289140556 Z"
+ android:valueTo="M -3.04137381938,-1.55960748018 l 6.08274763876,0.0 c 0.2761423749,0.0 0.5,0.2238576251 0.5,0.5 l 0.0,2.11921496035 c 0.0,0.2761423749 -0.2238576251,0.5 -0.5,0.5 l -6.08274763876,0.0 c -0.2761423749,0.0 -0.5,-0.2238576251 -0.5,-0.5 l 0.0,-2.11921496035 c 0.0,-0.2761423749 0.2238576251,-0.5 0.5,-0.5 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -3.04137381938,-1.55960748018 l 6.08274763876,0.0 c 0.2761423749,0.0 0.5,0.2238576251 0.5,0.5 l 0.0,2.11921496035 c 0.0,0.2761423749 -0.2238576251,0.5 -0.5,0.5 l -6.08274763876,0.0 c -0.2761423749,0.0 -0.5,-0.2238576251 -0.5,-0.5 l 0.0,-2.11921496035 c 0.0,-0.2761423749 0.2238576251,-0.5 0.5,-0.5 Z"
+ android:valueTo="M -1.55858989728,-1.77552577131 l 3.11717979456,0.0 c 0.677691994437,0.0 1.22706990313,0.549377908693 1.22706990313,1.22706990313 l 0.0,1.09691173636 c 0.0,0.677691994437 -0.549377908693,1.22706990313 -1.22706990313,1.22706990313 l -3.11717979456,0.0 c -0.677691994437,0.0 -1.22706990313,-0.549377908693 -1.22706990313,-1.22706990313 l 0.0,-1.09691173636 c 0.0,-0.677691994437 0.549377908693,-1.22706990313 1.22706990313,-1.22706990313 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -1.55858989728,-1.77552577131 l 3.11717979456,0.0 c 0.677691994437,0.0 1.22706990313,0.549377908693 1.22706990313,1.22706990313 l 0.0,1.09691173636 c 0.0,0.677691994437 -0.549377908693,1.22706990313 -1.22706990313,1.22706990313 l -3.11717979456,0.0 c -0.677691994437,0.0 -1.22706990313,-0.549377908693 -1.22706990313,-1.22706990313 l 0.0,-1.09691173636 c 0.0,-0.677691994437 0.549377908693,-1.22706990313 1.22706990313,-1.22706990313 Z"
+ android:valueTo="M -0.706008791281,-1.89447268498 l 1.41201758256,0.0 c 0.918635554655,0.0 1.66333681129,0.744701256633 1.66333681129,1.66333681129 l 0.0,0.462271747384 c 0.0,0.918635554655 -0.744701256633,1.66333681129 -1.66333681129,1.66333681129 l -1.41201758256,0.0 c -0.918635554655,0.0 -1.66333681129,-0.744701256633 -1.66333681129,-1.66333681129 l 0.0,-0.462271747384 c 0.0,-0.918635554655 0.744701256633,-1.66333681129 1.66333681129,-1.66333681129 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.706008791281,-1.89447268498 l 1.41201758256,0.0 c 0.918635554655,0.0 1.66333681129,0.744701256633 1.66333681129,1.66333681129 l 0.0,0.462271747384 c 0.0,0.918635554655 -0.744701256633,1.66333681129 -1.66333681129,1.66333681129 l -1.41201758256,0.0 c -0.918635554655,0.0 -1.66333681129,-0.744701256633 -1.66333681129,-1.66333681129 l 0.0,-0.462271747384 c 0.0,-0.918635554655 0.744701256633,-1.66333681129 1.66333681129,-1.66333681129 Z"
+ android:valueTo="M -0.265730251554,-1.95936709392 l 0.531460503108,0.0 c 1.03635400439,0.0 1.87648491973,0.840130915331 1.87648491973,1.87648491973 l 0.0,0.165764348389 c 0.0,1.03635400439 -0.840130915331,1.87648491973 -1.87648491973,1.87648491973 l -0.531460503108,0.0 c -1.03635400439,0.0 -1.87648491973,-0.840130915331 -1.87648491973,-1.87648491973 l 0.0,-0.165764348389 c 0.0,-1.03635400439 0.840130915331,-1.87648491973 1.87648491973,-1.87648491973 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.265730251554,-1.95936709392 l 0.531460503108,0.0 c 1.03635400439,0.0 1.87648491973,0.840130915331 1.87648491973,1.87648491973 l 0.0,0.165764348389 c 0.0,1.03635400439 -0.840130915331,1.87648491973 -1.87648491973,1.87648491973 l -0.531460503108,0.0 c -1.03635400439,0.0 -1.87648491973,-0.840130915331 -1.87648491973,-1.87648491973 l 0.0,-0.165764348389 c 0.0,-1.03635400439 0.840130915331,-1.87648491973 1.87648491973,-1.87648491973 Z"
+ android:valueTo="M -0.0581061000545,-1.99098433926 l 0.116212200109,0.0 c 1.08990562844,0.0 1.97344871252,0.883543084083 1.97344871252,1.97344871252 l 0.0,0.0350712534878 c 0.0,1.08990562844 -0.883543084083,1.97344871252 -1.97344871252,1.97344871252 l -0.116212200109,0.0 c -1.08990562844,0.0 -1.97344871252,-0.883543084083 -1.97344871252,-1.97344871252 l 0.0,-0.0350712534878 c 0.0,-1.08990562844 0.883543084083,-1.97344871252 1.97344871252,-1.97344871252 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.0581061000545,-1.99098433926 l 0.116212200109,0.0 c 1.08990562844,0.0 1.97344871252,0.883543084083 1.97344871252,1.97344871252 l 0.0,0.0350712534878 c 0.0,1.08990562844 -0.883543084083,1.97344871252 -1.97344871252,1.97344871252 l -0.116212200109,0.0 c -1.08990562844,0.0 -1.97344871252,-0.883543084083 -1.97344871252,-1.97344871252 l 0.0,-0.0350712534878 c 0.0,-1.08990562844 0.883543084083,-1.97344871252 1.97344871252,-1.97344871252 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/anim/ft_avd_tooverflow_rectangle_path_3_animation.xml b/core/res/res/anim/ft_avd_tooverflow_rectangle_path_3_animation.xml
new file mode 100644
index 000000000000..cb294100b48d
--- /dev/null
+++ b/core/res/res/anim/ft_avd_tooverflow_rectangle_path_3_animation.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android" >
+ <set
+ android:ordering="sequentially" >
+ <objectAnimator
+ android:duration="50"
+ android:propertyName="pathData"
+ android:valueFrom="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:valueTo="M -4.36843359242,-1.49992262412 l 8.73686718484,0.0 c 0.0728757880921,0.0 0.131953286993,0.0590774989007 0.131953286993,0.131953286993 l 0.0,2.73593867425 c 0.0,0.0728757880921 -0.0590774989007,0.131953286993 -0.131953286993,0.131953286993 l -8.73686718484,0.0 c -0.0728757880921,0.0 -0.131953286993,-0.0590774989007 -0.131953286993,-0.131953286993 l 0.0,-2.73593867425 c 0.0,-0.0728757880921 0.0590774989007,-0.131953286993 0.131953286993,-0.131953286993 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -4.36843359242,-1.49992262412 l 8.73686718484,0.0 c 0.0728757880921,0.0 0.131953286993,0.0590774989007 0.131953286993,0.131953286993 l 0.0,2.73593867425 c 0.0,0.0728757880921 -0.0590774989007,0.131953286993 -0.131953286993,0.131953286993 l -8.73686718484,0.0 c -0.0728757880921,0.0 -0.131953286993,-0.0590774989007 -0.131953286993,-0.131953286993 l 0.0,-2.73593867425 c 0.0,-0.0728757880921 0.0590774989007,-0.131953286993 0.131953286993,-0.131953286993 Z"
+ android:valueTo="M -2.7976112102,-1.69047775796 l 5.59522242041,0.0 c 0.41421356235,0.0 0.75,0.33578643765 0.75,0.75 l 0.0,1.88095551592 c 0.0,0.41421356235 -0.33578643765,0.75 -0.75,0.75 l -5.59522242041,0.0 c -0.41421356235,0.0 -0.75,-0.33578643765 -0.75,-0.75 l 0.0,-1.88095551592 c 0.0,-0.41421356235 0.33578643765,-0.75 0.75,-0.75 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -2.7976112102,-1.69047775796 l 5.59522242041,0.0 c 0.41421356235,0.0 0.75,0.33578643765 0.75,0.75 l 0.0,1.88095551592 c 0.0,0.41421356235 -0.33578643765,0.75 -0.75,0.75 l -5.59522242041,0.0 c -0.41421356235,0.0 -0.75,-0.33578643765 -0.75,-0.75 l 0.0,-1.88095551592 c 0.0,-0.41421356235 0.33578643765,-0.75 0.75,-0.75 Z"
+ android:valueTo="M -1.5412962309,-1.81003891076 l 3.08259246181,0.0 c 0.777898159561,0.0 1.4085092153,0.630611055735 1.4085092153,1.4085092153 l 0.0,0.803059390927 c 0.0,0.777898159561 -0.630611055735,1.4085092153 -1.4085092153,1.4085092153 l -3.08259246181,0.0 c -0.777898159561,0.0 -1.4085092153,-0.630611055735 -1.4085092153,-1.4085092153 l 0.0,-0.803059390927 c 0.0,-0.777898159561 0.630611055735,-1.4085092153 1.4085092153,-1.4085092153 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -1.5412962309,-1.81003891076 l 3.08259246181,0.0 c 0.777898159561,0.0 1.4085092153,0.630611055735 1.4085092153,1.4085092153 l 0.0,0.803059390927 c 0.0,0.777898159561 -0.630611055735,1.4085092153 -1.4085092153,1.4085092153 l -3.08259246181,0.0 c -0.777898159561,0.0 -1.4085092153,-0.630611055735 -1.4085092153,-1.4085092153 l 0.0,-0.803059390927 c 0.0,-0.777898159561 0.630611055735,-1.4085092153 1.4085092153,-1.4085092153 Z"
+ android:valueTo="M -0.798718330914,-1.88987363368 l 1.59743666183,0.0 c 0.967555109393,0.0 1.75191350068,0.784358391285 1.75191350068,1.75191350068 l 0.0,0.275920266008 c 0.0,0.967555109393 -0.784358391285,1.75191350068 -1.75191350068,1.75191350068 l -1.59743666183,0.0 c -0.967555109393,0.0 -1.75191350068,-0.784358391285 -1.75191350068,-1.75191350068 l 0.0,-0.275920266008 c 0.0,-0.967555109393 0.784358391285,-1.75191350068 1.75191350068,-1.75191350068 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.798718330914,-1.88987363368 l 1.59743666183,0.0 c 0.967555109393,0.0 1.75191350068,0.784358391285 1.75191350068,1.75191350068 l 0.0,0.275920266008 c 0.0,0.967555109393 -0.784358391285,1.75191350068 -1.75191350068,1.75191350068 l -1.59743666183,0.0 c -0.967555109393,0.0 -1.75191350068,-0.784358391285 -1.75191350068,-1.75191350068 l 0.0,-0.275920266008 c 0.0,-0.967555109393 0.784358391285,-1.75191350068 1.75191350068,-1.75191350068 Z"
+ android:valueTo="M -0.366220962052,-1.94300934217 l 0.732441924103,0.0 c 1.05968660322,0.0 1.91873232712,0.859045723904 1.91873232712,1.91873232712 l 0.0,0.0485540300878 c 0.0,1.05968660322 -0.859045723904,1.91873232712 -1.91873232712,1.91873232712 l -0.732441924103,0.0 c -1.05968660322,0.0 -1.91873232712,-0.859045723904 -1.91873232712,-1.91873232712 l 0.0,-0.0485540300878 c 0.0,-1.05968660322 0.859045723904,-1.91873232712 1.91873232712,-1.91873232712 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.366220962052,-1.94300934217 l 0.732441924103,0.0 c 1.05968660322,0.0 1.91873232712,0.859045723904 1.91873232712,1.91873232712 l 0.0,0.0485540300878 c 0.0,1.05968660322 -0.859045723904,1.91873232712 -1.91873232712,1.91873232712 l -0.732441924103,0.0 c -1.05968660322,0.0 -1.91873232712,-0.859045723904 -1.91873232712,-1.91873232712 l 0.0,-0.0485540300878 c 0.0,-1.05968660322 0.859045723904,-1.91873232712 1.91873232712,-1.91873232712 Z"
+ android:valueTo="M -0.141334109858,-1.97644431502 l 0.282668219716,0.0 c 1.09156005402,0.0 1.97644431502,0.884884261007 1.97644431502,1.97644431502 l 0.0,0.0 c 0.0,1.09156005402 -0.884884261007,1.97644431502 -1.97644431502,1.97644431502 l -0.282668219716,0.0 c -1.09156005402,0.0 -1.97644431502,-0.884884261007 -1.97644431502,-1.97644431502 l 0.0,0.0 c 0.0,-1.09156005402 0.884884261007,-1.97644431502 1.97644431502,-1.97644431502 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.141334109858,-1.97644431502 l 0.282668219716,0.0 c 1.09156005402,0.0 1.97644431502,0.884884261007 1.97644431502,1.97644431502 l 0.0,0.0 c 0.0,1.09156005402 -0.884884261007,1.97644431502 -1.97644431502,1.97644431502 l -0.282668219716,0.0 c -1.09156005402,0.0 -1.97644431502,-0.884884261007 -1.97644431502,-1.97644431502 l 0.0,0.0 c 0.0,-1.09156005402 0.884884261007,-1.97644431502 1.97644431502,-1.97644431502 Z"
+ android:valueTo="M -0.0331287849506,-1.99447853584 l 0.0662575699012,0.0 c 1.10152007915,0.0 1.99447853584,0.892958456693 1.99447853584,1.99447853584 l 0.0,0.0 c 0.0,1.10152007915 -0.892958456693,1.99447853584 -1.99447853584,1.99447853584 l -0.0662575699012,0.0 c -1.10152007915,0.0 -1.99447853584,-0.892958456693 -1.99447853584,-1.99447853584 l 0.0,0.0 c 0.0,-1.10152007915 0.892958456693,-1.99447853584 1.99447853584,-1.99447853584 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ <objectAnimator
+ android:duration="37"
+ android:propertyName="pathData"
+ android:valueFrom="M -0.0331287849506,-1.99447853584 l 0.0662575699012,0.0 c 1.10152007915,0.0 1.99447853584,0.892958456693 1.99447853584,1.99447853584 l 0.0,0.0 c 0.0,1.10152007915 -0.892958456693,1.99447853584 -1.99447853584,1.99447853584 l -0.0662575699012,0.0 c -1.10152007915,0.0 -1.99447853584,-0.892958456693 -1.99447853584,-1.99447853584 l 0.0,0.0 c 0.0,-1.10152007915 0.892958456693,-1.99447853584 1.99447853584,-1.99447853584 Z"
+ android:valueTo="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z"
+ android:valueType="pathType"
+ android:interpolator="@android:interpolator/linear" />
+ </set>
+</set>
diff --git a/core/res/res/drawable/ft_avd_toarrow.xml b/core/res/res/drawable/ft_avd_toarrow.xml
new file mode 100644
index 000000000000..087ea74fc60f
--- /dev/null
+++ b/core/res/res/drawable/ft_avd_toarrow.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="ft_avd_toarrow"
+ android:width="24dp"
+ android:viewportWidth="24"
+ android:height="24dp"
+ android:viewportHeight="24" >
+ <group
+ android:name="carrot_3"
+ android:translateX="12"
+ android:translateY="12" >
+ <group
+ android:name="rectangle_4"
+ android:translateY="6" >
+ <group
+ android:name="rectangle_3_pivot_0" >
+ <path
+ android:name="rectangle_path_4"
+ android:fillColor="#FF000000"
+ android:pathData="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z" />
+ </group>
+ </group>
+ <group
+ android:name="rectangle_5" >
+ <group
+ android:name="rectangle_2_pivot_0" >
+ <path
+ android:name="rectangle_path_5"
+ android:fillColor="#FF000000"
+ android:pathData="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z" />
+ </group>
+ </group>
+ <group
+ android:name="rectangle_6"
+ android:translateY="-6" >
+ <group
+ android:name="rectangle_1_pivot_0" >
+ <path
+ android:name="rectangle_path_6"
+ android:fillColor="#FF000000"
+ android:pathData="M 0.0,-2.0 l 0.0,0.0 c 1.1045694996,0.0 2.0,0.8954305004 2.0,2.0 l 0.0,0.0 c 0.0,1.1045694996 -0.8954305004,2.0 -2.0,2.0 l 0.0,0.0 c -1.1045694996,0.0 -2.0,-0.8954305004 -2.0,-2.0 l 0.0,0.0 c 0.0,-1.1045694996 0.8954305004,-2.0 2.0,-2.0 Z" />
+ </group>
+ </group>
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ft_avd_toarrow_animation.xml b/core/res/res/drawable/ft_avd_toarrow_animation.xml
new file mode 100644
index 000000000000..e31067d56893
--- /dev/null
+++ b/core/res/res/drawable/ft_avd_toarrow_animation.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<animated-vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/ft_avd_toarrow" >
+ <target
+ android:name="rectangle_4"
+ android:animation="@anim/ft_avd_toarrow_rectangle_4_animation" />
+ <target
+ android:name="rectangle_3_pivot_0"
+ android:animation="@anim/ft_avd_toarrow_rectangle_3_pivot_0_animation" />
+ <target
+ android:name="rectangle_path_4"
+ android:animation="@anim/ft_avd_toarrow_rectangle_path_4_animation" />
+ <target
+ android:name="rectangle_5"
+ android:animation="@anim/ft_avd_toarrow_rectangle_5_animation" />
+ <target
+ android:name="rectangle_2_pivot_0"
+ android:animation="@anim/ft_avd_toarrow_rectangle_2_pivot_0_animation" />
+ <target
+ android:name="rectangle_path_5"
+ android:animation="@anim/ft_avd_toarrow_rectangle_path_5_animation" />
+ <target
+ android:name="rectangle_6"
+ android:animation="@anim/ft_avd_toarrow_rectangle_6_animation" />
+ <target
+ android:name="rectangle_1_pivot_0"
+ android:animation="@anim/ft_avd_toarrow_rectangle_1_pivot_0_animation" />
+ <target
+ android:name="rectangle_path_6"
+ android:animation="@anim/ft_avd_toarrow_rectangle_path_6_animation" />
+</animated-vector>
diff --git a/core/res/res/drawable/ft_avd_tooverflow.xml b/core/res/res/drawable/ft_avd_tooverflow.xml
new file mode 100644
index 000000000000..a2b9cec56fa7
--- /dev/null
+++ b/core/res/res/drawable/ft_avd_tooverflow.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="ft_avd_tooverflow"
+ android:width="24dp"
+ android:viewportWidth="24"
+ android:height="24dp"
+ android:viewportHeight="24" >
+ <group
+ android:name="carrot_5"
+ android:translateX="12"
+ android:translateY="12" >
+ <group
+ android:name="rectangle_3"
+ android:translateX="-6.5"
+ android:rotation="45" >
+ <group
+ android:name="rectangle_3_pivot"
+ android:translateX="4.5" >
+ <path
+ android:name="rectangle_path_2"
+ android:fillColor="#FF000000"
+ android:pathData="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
+ </group>
+ </group>
+ <group
+ android:name="rectangle_2"
+ android:translateX="-8" >
+ <group
+ android:name="rectangle_2_pivot"
+ android:translateX="9" >
+ <path
+ android:name="rectangle_path_3"
+ android:fillColor="#FF000000"
+ android:pathData="M -7.0,-1.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
+ </group>
+ </group>
+ <group
+ android:name="rectangle_1"
+ android:translateX="-6.5"
+ android:rotation="-45" >
+ <group
+ android:name="rectangle_1_pivot"
+ android:translateX="4.5" >
+ <path
+ android:name="rectangle_path_1"
+ android:fillColor="#FF000000"
+ android:pathData="M -5.5,-1.0 l 11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -11.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" />
+ </group>
+ </group>
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ft_avd_tooverflow_animation.xml b/core/res/res/drawable/ft_avd_tooverflow_animation.xml
new file mode 100644
index 000000000000..ed9975bceb0d
--- /dev/null
+++ b/core/res/res/drawable/ft_avd_tooverflow_animation.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<animated-vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/ft_avd_tooverflow" >
+ <target
+ android:name="rectangle_3"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_3_animation" />
+ <target
+ android:name="rectangle_3_pivot"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_3_pivot_animation" />
+ <target
+ android:name="rectangle_path_2"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_path_2_animation" />
+ <target
+ android:name="rectangle_2"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_2_animation" />
+ <target
+ android:name="rectangle_2_pivot"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_2_pivot_animation" />
+ <target
+ android:name="rectangle_path_3"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_path_3_animation" />
+ <target
+ android:name="rectangle_1"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_1_animation" />
+ <target
+ android:name="rectangle_1_pivot"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_1_pivot_animation" />
+ <target
+ android:name="rectangle_path_1"
+ android:animation="@anim/ft_avd_tooverflow_rectangle_path_1_animation" />
+</animated-vector>
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/interpolator/ft_avd_toarrow_animation_interpolator_0.xml b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_0.xml
new file mode 100644
index 000000000000..c6db901fdd22
--- /dev/null
+++ b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_0.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 l 0.668316831683,0.0 c 0.00003,0.0 0.0663366336634,1.0 0.331683168317,1.0 L 1.0,1.0" />
diff --git a/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_1.xml b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_1.xml
new file mode 100644
index 000000000000..584385da8c20
--- /dev/null
+++ b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_1.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 c 0.0001,0.0 0.0,1.0 1.0,1.0" />
diff --git a/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_2.xml b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_2.xml
new file mode 100644
index 000000000000..334dee7a2b6c
--- /dev/null
+++ b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_2.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 c 0.161290322581,0.0 0.0806451612903,0.909090909091 0.403225806452,0.909090909091 c 0.238709677419,0.0 0.11935483871,0.0909090909091 0.596774193548,0.0909090909091 L 1.0,1.0" />
diff --git a/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_3.xml b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_3.xml
new file mode 100644
index 000000000000..67891fc15281
--- /dev/null
+++ b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_3.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 l 0.5,0.0 c 0.2,0.0 0.1,1.0 0.5,1.0 L 1.0,1.0" />
diff --git a/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_4.xml b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_4.xml
new file mode 100644
index 000000000000..756a9e1f1c64
--- /dev/null
+++ b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_4.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 l 0.5,0.0 c 0.00005,0.0 0.1,1.0 0.5,1.0 L 1.0,1.0" />
diff --git a/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_5.xml b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_5.xml
new file mode 100644
index 000000000000..584385da8c20
--- /dev/null
+++ b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_5.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 c 0.0001,0.0 0.0,1.0 1.0,1.0" />
diff --git a/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_6.xml b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_6.xml
new file mode 100644
index 000000000000..bed54d419dbb
--- /dev/null
+++ b/core/res/res/interpolator/ft_avd_toarrow_animation_interpolator_6.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="M 0.0,0.0 l 0.582706766917,0.0 c 0.166917293233,0.0 0.0834586466165,1.0 0.417293233083,1.0 L 1.0,1.0" />
diff --git a/core/res/res/layout/floating_popup_container.xml b/core/res/res/layout/floating_popup_container.xml
index 63dae4470cbe..dd161e38486e 100644
--- a/core/res/res/layout/floating_popup_container.xml
+++ b/core/res/res/layout/floating_popup_container.xml
@@ -15,12 +15,11 @@
** limitations under the License.
*/
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
- android:layout_height="@dimen/floating_toolbar_height"
+ android:layout_height="wrap_content"
android:padding="0dp"
- android:layout_margin="0dp"
+ android:layout_margin="20dp"
android:elevation="2dp"
android:focusable="true"
android:focusableInTouchMode="true"
diff --git a/core/res/res/layout/floating_popup_overflow_button.xml b/core/res/res/layout/floating_popup_overflow_button.xml
new file mode 100644
index 000000000000..7053f3ec07fe
--- /dev/null
+++ b/core/res/res/layout/floating_popup_overflow_button.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/overflow"
+ android:layout_width="@dimen/floating_toolbar_menu_image_button_width"
+ android:layout_height="@dimen/floating_toolbar_height"
+ android:paddingStart="@dimen/floating_toolbar_menu_button_side_padding"
+ android:paddingTop="@dimen/floating_toolbar_menu_image_button_vertical_padding"
+ android:paddingEnd="@dimen/floating_toolbar_menu_button_side_padding"
+ android:paddingBottom="@dimen/floating_toolbar_menu_image_button_vertical_padding"
+ android:scaleType="centerInside"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:tint="?attr/floatingToolbarForegroundColor" />
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-af/strings.xml b/core/res/res/values-af/strings.xml
index 669eba14abb5..c3f7afc85f74 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Stembystand"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Sluit nou"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Veiligmodus"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-stelsel"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Persoonlik"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index a7af0f1ea2fa..4de0097c0a02 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"የድምጽ እርዳታ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"አሁን ቆልፍ"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"የሚያስተማምን ሁነታ"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android ስርዓት"</string>
<string name="user_owner_label" msgid="2804351898001038951">"የግል"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 4a9b16354609..0c05bc41e97d 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -226,6 +226,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"المساعد الصوتي"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"قفل الآن"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"الوضع الآمن"</string>
<string name="android_system_label" msgid="6577375335728551336">"‏نظام Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"شخصي"</string>
@@ -1470,8 +1472,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"المطالبة برقم التعريف الشخصي قبل إزالة التثبيت"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"المطالبة بنقش إلغاء القفل قبل إزالة التثبيت"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"المطالبة بكلمة المرور قبل إزالة التثبيت"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"التطبيق غير قابل لتغيير الحجم، يمكنك تمريره بإصبعين."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"تم تثبيت الحزمة عن طريق المشرف"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"تم التحديث بواسطة المشرف"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"تم حذف الحزمة عن طريق المشرف"</string>
diff --git a/core/res/res/values-az-rAZ/strings.xml b/core/res/res/values-az-rAZ/strings.xml
index 54dbc39e4607..593400e4b9b7 100644
--- a/core/res/res/values-az-rAZ/strings.xml
+++ b/core/res/res/values-az-rAZ/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Səs Yardımçısı"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"İndi kilidləyin"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Təhlükəsiz rejim"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android sistemi"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Şəxsi"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 233d7095802e..50f293841d68 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Гласова помощ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Заключване сега"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Безопасен режим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Системно от Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Личен"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Запитване за ПИН код преди освобождаване"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Запитване за фигура за отключване преди освобождаване"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Запитване за парола преди освобождаване"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Приложението не може да се преоразмерява. Превъртете го с два пръста."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Инсталирано от администратора ви"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Актуализирано от администратора ви"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Изтрито от администратора ви"</string>
diff --git a/core/res/res/values-bn-rBD/strings.xml b/core/res/res/values-bn-rBD/strings.xml
index f5fb017de824..fdd46172ee78 100644
--- a/core/res/res/values-bn-rBD/strings.xml
+++ b/core/res/res/values-bn-rBD/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"ভয়েস সহায়তা"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"এখনই লক করুন"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"৯৯৯+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"নিরাপদ মোড"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android সিস্টেম"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ব্যক্তিগত"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 0e731f647666..cb24d265ae9f 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Assist. per veu"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloqueja ara"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"+999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Mode segur"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index fd3ed4b4317b..c2f1364fbb03 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Hlas. asistence"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Zamknout"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Nouzový režim"</string>
<string name="android_system_label" msgid="6577375335728551336">"Systém Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Osobní"</string>
@@ -355,7 +357,7 @@
<string name="permlab_vibrate" msgid="7696427026057705834">"ovládání vibrací"</string>
<string name="permdesc_vibrate" msgid="6284989245902300945">"Umožňuje aplikaci ovládat vibrace."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"ovládání kontrolky"</string>
- <string name="permdesc_flashlight" msgid="6522284794568368310">"Umožňuje aplikaci ovládat baterku."</string>
+ <string name="permdesc_flashlight" msgid="6522284794568368310">"Umožňuje aplikaci ovládat svítilnu."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"přímé volání na telefonní čísla"</string>
<string name="permdesc_callPhone" msgid="3740797576113760827">"Umožňuje aplikaci volat na telefonní čísla bez vašeho přičinění. Může mít za následek neočekávané poplatky nebo hovory. Toto oprávnění neumožňuje aplikaci volat na tísňová čísla. Škodlivé aplikace vás mohou připravit o peníze uskutečňováním hovorů bez vašeho svolení."</string>
<string name="permlab_accessImsCallService" msgid="3574943847181793918">"přístup ke službě zasílání rychlých zpráv pro účely hovorů"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 646f7341ccb9..42cb42b57caa 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Taleassistent"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lås nu"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Sikker tilstand"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-system"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personlig"</string>
@@ -1434,8 +1435,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Bed om pinkode inden frigørelse"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Bed om oplåsningsmønster ved deaktivering"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Bed om adgangskode inden frigørelse"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Appens størrelse kan ikke ændres. Gennemgå den ved at rulle med to fingre."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Installeret af din administrator"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Opdateret af administrator"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Slettet af din administrator"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index f8b78c00ee1e..d52c728806d9 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Sprachassistent"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Jetzt sperren"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Abgesicherter Modus"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-System"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Privat"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 7a7cce184120..f86c2fd46853 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Φων.υποβοηθ."</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Κλείδωμα τώρα"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Ασφαλής λειτουργία"</string>
<string name="android_system_label" msgid="6577375335728551336">"Σύστημα Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Προσωπικό"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Να γίνεται ερώτηση για το PIN, πριν από το ξεκαρφίτσωμα"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Να γίνεται ερώτηση για το μοτίβο ξεκλειδώματος, πριν από το ξεκαρφίτσωμα"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Να γίνεται ερώτηση για τον κωδικό πρόσβασης, πριν από το ξεκαρφίτσωμα"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Το μέγεθος της εφαρμογής δεν είναι προσαρμόσιμο. Σύρετε προς τα κάτω με δύο δάχτυλα."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Εγκαταστάθηκε από το διαχειριστή σας"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Ενημερώθηκε από το διαχειριστή σας"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Διαγράφηκε από το διαχειριστή σας"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index c66c55a10dad..e23f8d5178a4 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lock now"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Safe mode"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android system"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index c66c55a10dad..e23f8d5178a4 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lock now"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Safe mode"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android system"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index c66c55a10dad..e23f8d5178a4 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lock now"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Safe mode"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android system"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 0168d4f1ad2a..b442bc1ff743 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Asistente voz"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloquear ahora"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Modo seguro"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Solicitar PIN para quitar fijación"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Solicitar patrón de desbloqueo para quitar fijación"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Solicitar contraseña para quitar fijación"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"No se puede modificar el tamaño de la app, desplázala con dos dedos."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Lo instaló el administrador."</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Actualizado por el administrador"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Lo eliminó el administrador."</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 04cdfe0406fa..acbef23b9df9 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Asistente voz"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloquear ahora"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"&gt; 999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Modo seguro"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
@@ -243,7 +245,7 @@
<string name="permgrouplab_phone" msgid="5229115638567440675">"Teléfono"</string>
<string name="permgroupdesc_phone" msgid="6234224354060641055">"hacer y administrar llamadas de teléfono"</string>
<string name="permgrouplab_sensors" msgid="416037179223226722">"Sensores corporales"</string>
- <string name="permgroupdesc_sensors" msgid="7147968539346634043">"acceder a datos del sensor sobre tus constantes vitales"</string>
+ <string name="permgroupdesc_sensors" msgid="7147968539346634043">"acceder a datos de sensores de tus constantes vitales"</string>
<string name="capability_title_canRetrieveWindowContent" msgid="3901717936930170320">"Recuperar el contenido de la ventana"</string>
<string name="capability_desc_canRetrieveWindowContent" msgid="3772225008605310672">"Inspecciona el contenido de una ventana con la que estés interactuando."</string>
<string name="capability_title_canRequestTouchExploration" msgid="3108723364676667320">"Activar la exploración táctil"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Solicitar PIN para desactivar"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Solicitar patrón de desbloqueo para desactivar"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Solicitar contraseña para desactivar"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"No se puede cambiar el tamaño de la aplicación: desplázala con dos dedos."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Instalado por tu administrador"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Actualizado por tu administrador"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Eliminado por tu administrador"</string>
diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml
index 5898ca048824..9a966838084c 100644
--- a/core/res/res/values-et-rEE/strings.xml
+++ b/core/res/res/values-et-rEE/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Häälabi"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lukusta kohe"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Turvarežiim"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-süsteem"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Isiklik"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Enne vabastamist küsi PIN-koodi"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Enne vabastamist küsi avamismustrit"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Enne vabastamist küsi parooli"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Rakenduse suurust ei saa muuta. Kerige kahe sõrmega."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Installis teie administraator"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Värskendas administraator"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Kustutas teie administraator"</string>
diff --git a/core/res/res/values-eu-rES/strings.xml b/core/res/res/values-eu-rES/strings.xml
index 0986b70b324a..a30c43717b8d 100644
--- a/core/res/res/values-eu-rES/strings.xml
+++ b/core/res/res/values-eu-rES/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Ahots-laguntza"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Blokeatu"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Modu segurua"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android sistema"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Pertsonalak"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 689e9e0bf8f3..86721710cb20 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -191,7 +191,7 @@
<string name="reboot_to_update_prepare" msgid="6305853831955310890">"آماده‌سازی برای به‌روزرسانی…"</string>
<string name="reboot_to_update_package" msgid="3871302324500927291">"در حال پردازش بسته‌بندی به‌روز…"</string>
<string name="reboot_to_update_reboot" msgid="6428441000951565185">"در حال راه‌اندازی مجدد…"</string>
- <string name="reboot_to_reset_title" msgid="4142355915340627490">"بازنشانی به داده‌های کارخانه"</string>
+ <string name="reboot_to_reset_title" msgid="4142355915340627490">"بازنشانی داده‌های کارخانه"</string>
<string name="reboot_to_reset_message" msgid="2432077491101416345">"در حال راه‌اندازی مجدد…"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"در حال خاموش شدن…"</string>
<string name="shutdown_confirm" product="tablet" msgid="3385745179555731470">"رایانهٔ لوحی شما خاموش می‌شود."</string>
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"دستیار صوتی"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"اکنون قفل شود"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"بیشتر از 999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"حالت ایمن"</string>
<string name="android_system_label" msgid="6577375335728551336">"‏سیستم Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"شخصی"</string>
@@ -511,9 +513,9 @@
<string name="policylab_forceLock" msgid="2274085384704248431">"قفل کردن صفحه"</string>
<string name="policydesc_forceLock" msgid="1141797588403827138">"نحوه و زمان قفل شدن صفحه را کنترل می‌کند."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"پاک کردن تمام داده‌ها"</string>
- <string name="policydesc_wipeData" product="tablet" msgid="4306184096067756876">"با انجام بازنشانی به داده‌های کارخانه، داده‌های رایانهٔ لوحی بدون هشدار پاک می‌شود."</string>
- <string name="policydesc_wipeData" product="tv" msgid="5816221315214527028">"داده‌های تلویزیون را بدون هشدار با انجام بازنشانی به داده کارخانه پاک کنید."</string>
- <string name="policydesc_wipeData" product="default" msgid="5096895604574188391">"با انجام بازنشانی به داده‌های کارخانه، داده‌های تلفن بدون هشدار پاک می‌شود."</string>
+ <string name="policydesc_wipeData" product="tablet" msgid="4306184096067756876">"با انجام بازنشانی داده‌های کارخانه، داده‌های رایانهٔ لوحی بدون هشدار پاک می‌شود."</string>
+ <string name="policydesc_wipeData" product="tv" msgid="5816221315214527028">"داده‌های تلویزیون را بدون هشدار با انجام بازنشانی داده‌های کارخانه پاک کنید."</string>
+ <string name="policydesc_wipeData" product="default" msgid="5096895604574188391">"با انجام بازنشانی داده‌های کارخانه، داده‌های تلفن بدون هشدار پاک می‌شود."</string>
<string name="policylab_wipeData_secondaryUser" msgid="8362863289455531813">"پاک کردن داده‌های کاربر"</string>
<string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="6336255514635308054">"داده‌های این کاربر را در این رایانه لوحی بدون هشدار پاک می‌کند."</string>
<string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2086473496848351810">"داده‌های این کاربر را در این تلویزیون بدون هشدار پاک می‌کند."</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"درخواست کد پین قبل از برداشتن پین"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"درخواست الگوی باز کردن قفل قبل از برداشتن پین"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"درخواست گذرواژه قبل از برداشتن پین"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"اندازه برنامه قابل تغییر نیست، با دو انگشت آن را پیمایش کنید."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"توسط سرپرستتان نصب شد"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"توسط سرپرست شما به‌روزرسانی شد"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"توسط سرپرستتان حذف شد"</string>
@@ -1484,7 +1485,7 @@
<string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"آخر هفته"</string>
<string name="zen_mode_default_events_name" msgid="8158334939013085363">"رویداد"</string>
<string name="muted_by" msgid="6147073845094180001">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> آن را بی‌صدا کرد"</string>
- <string name="system_error_wipe_data" msgid="6608165524785354962">"دستگاهتان یک مشکل داخلی دارد، و ممکن است تا زمانی که بازنشانی به داده کارخانه انجام نگیرد، بی‌ثبات بماند."</string>
+ <string name="system_error_wipe_data" msgid="6608165524785354962">"دستگاهتان یک مشکل داخلی دارد، و ممکن است تا زمانی که بازنشانی داده‌های کارخانه انجام نگیرد، بی‌ثبات بماند."</string>
<string name="system_error_manufacturer" msgid="8086872414744210668">"دستگاهتان یک مشکل داخلی دارد. برای جزئیات آن با سازنده‌تان تماس بگیرید."</string>
<string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"‏درخواست USSD به درخواست DIAL اصلاح می‌شود."</string>
<string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"‏درخواست USSD به درخواست SS اصلاح می‌شود."</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 8af6e567a6c8..8100305ae3a5 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Ääniapuri"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lukitse nyt"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Suojattu tila"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-järjestelmä"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Henkilökoht."</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Pyydä PIN ennen irrotusta"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Pyydä lukituksenpoistokuvio ennen irrotusta"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Pyydä salasana ennen irrotusta"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Sovelluksen koko ei muutu. Vieritä näkymää kahdella sormella."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Järjestelmänvalvoja on asentanut paketin."</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Järjestelmänvalvojasi on päivittänyt paketin."</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Järjestelmänvalvoja on poistanut paketin."</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index e2daae6c230c..b27398002226 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Assist. vocale"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Verrouiller"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"&gt;999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Mode sécurisé"</string>
<string name="android_system_label" msgid="6577375335728551336">"Système Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personnel"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Demander le NIP avant d\'annuler l\'épinglage"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Demander le schéma de déverrouillage avant d\'annuler l\'épinglage"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Demander le mot de passe avant d\'annuler l\'épinglage"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Impossible de redimensionner l\'application. Faites-la défiler avec deux doigts."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Installé par votre administrateur"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Mis à jour par votre administrateur"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Supprimé par votre administrateur"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 5de09716f216..850c18a32621 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Assistance vocale"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Verrouiller"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"&gt;999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Mode sécurisé"</string>
<string name="android_system_label" msgid="6577375335728551336">"Système Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personnel"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Demander le code PIN avant d\'annuler l\'épinglage"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Demander le schéma de déverrouillage avant d\'annuler l\'épinglage"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Demander le mot de passe avant d\'annuler l\'épinglage"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Il est impossible de redimensionner l\'application. Faites-la défiler avec deux doigts."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Installé par votre administrateur"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Mis à jour par votre administrateur"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Supprimé par votre administrateur"</string>
diff --git a/core/res/res/values-gl-rES/strings.xml b/core/res/res/values-gl-rES/strings.xml
index ba25f896a822..998f49417445 100644
--- a/core/res/res/values-gl-rES/strings.xml
+++ b/core/res/res/values-gl-rES/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Asistente voz"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloquear agora"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"&gt;999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Modo seguro"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Persoal"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Solicitar un PIN antes de soltar a pantalla"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Solicitar un padrón de desbloqueo antes de soltar a pantalla"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Solicitar un contrasinal antes de soltar a pantalla"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Non se pode cambiar o tamaño da aplicación. Desprázate por ela con dous dedos."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Instalado polo administrador"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Actualizado polo administrador"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Eliminado polo administrador"</string>
diff --git a/core/res/res/values-gu-rIN/strings.xml b/core/res/res/values-gu-rIN/strings.xml
index ce05ef530fdc..7cfd200aa034 100644
--- a/core/res/res/values-gu-rIN/strings.xml
+++ b/core/res/res/values-gu-rIN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"વૉઇસ સહાય"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"હવે લૉક કરો"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"સુરક્ષિત મોડ"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android સિસ્ટમ"</string>
<string name="user_owner_label" msgid="2804351898001038951">"વ્યક્તિગત"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"અનપિન કરતાં પહેલાં PIN માટે પૂછો"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"અનપિન કરતા પહેલાં અનલૉક પેટર્ન માટે પૂછો"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"અનપિન કરતાં પહેલાં પાસવર્ડ માટે પૂછો"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"ઍપ્લિકેશનનું કદ બદલવા યોગ્ય નથી, બે આંગળીઓ વડે તેને સ્ક્રોલ કરો."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"તમારા વ્યવસ્થાપક દ્વારા ઇન્સ્ટોલ કરેલ"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"તમારા વ્યવસ્થાપક દ્વારા અપડેટ થયેલ"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"તમારા વ્યવસ્થાપક દ્વારા કાઢી નાખેલ"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 47fdd286afef..a001211cf5df 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"वॉइस सहायक"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"अभी लॉक करें"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"सुरक्षित मोड"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android सिस्‍टम"</string>
<string name="user_owner_label" msgid="2804351898001038951">"व्यक्तिगत"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 9bc026790847..55399afe26f2 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -223,6 +223,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Glasovna pomoć"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Zaključaj sada"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Siguran način rada"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sustav Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Osobno"</string>
@@ -1443,8 +1445,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Traži PIN radi otkvačivanja"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Traži uzorak za otključavanje radi otkvačivanja"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Traži zaporku radi otkvačivanja"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Veličina aplikacije ne može se mijenjati, pomičite je s dva prsta."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Instalirao administrator"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Ažurira vaš administrator"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Izbrisao administrator"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 1c0257f2f82c..dc879434034e 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Hangsegéd"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Zárolás most"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Biztonsági üzemmód"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android rendszer"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Személyes"</string>
diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml
index 02c6bedad31c..f1d32d80f1e4 100644
--- a/core/res/res/values-hy-rAM/strings.xml
+++ b/core/res/res/values-hy-rAM/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Ձայնային օգնութ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Կողպել հիմա"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Անվտանգ ռեժիմ"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android համակարգ"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Անձնական"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Ապաամրացնելուց առաջ հարցնել PIN-կոդը"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Ապաամրացնելուց առաջ հարցնել ապակողպող նախշը"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Ապաամրացնելուց առաջ հարցնել գաղտնաբառը"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Հավելվածի չափը հնարավոր չէ փոխել, ոլորեք այն երկու մատի օգնությամբ:"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Ադմինիստրատորը տեղադրել է այն"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Ադմինիստրատորը թարմացրել է այն"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Ադմինիստրատորը ջնջել է այն"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 7346d31a818c..8e18daee3c4f 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Bantuan Suara"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Kunci sekarang"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Mode aman"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistem Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Pribadi"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Meminta PIN sebelum melepas sematan"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Meminta pola pembukaan kunci sebelum melepas sematan"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Meminta sandi sebelum melepas sematan"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Aplikasi tidak dapat diubah ukurannya, gulir dengan dua jari."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Dipasang oleh administrator"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Diperbarui oleh administrator"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Dihapus oleh administrator"</string>
diff --git a/core/res/res/values-is-rIS/strings.xml b/core/res/res/values-is-rIS/strings.xml
index c6ec5b14eca3..b2c98f09744e 100644
--- a/core/res/res/values-is-rIS/strings.xml
+++ b/core/res/res/values-is-rIS/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Raddaðstoð"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Læsa núna"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Örugg stilling"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android kerfið"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Persónulegt"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Biðja um PIN-númer til að losa"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Biðja um opnunarmynstur til að losa"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Biðja um aðgangsorð til að losa"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Ekki er hægt að breyta stærð forritsins, flettu upp og niður með tveimur fingrum."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Uppsett af kerfisstjóra"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Uppfært af kerfisstjóranum"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Eytt af kerfisstjóra"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index c4ecbad844b9..b36e2e5a3e3e 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Blocca ora"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Modalità provvisoria"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personale"</string>
@@ -1434,8 +1435,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Richiedi il PIN per lo sblocco"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Richiedi sequenza di sblocco prima di sbloccare"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Richiedi password prima di sbloccare"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Non è posssibile ridimensionare l\'app: scorri con due dita."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Installato dall\'amministratore"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Aggiornato dall\'amministratore"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Eliminato dall\'amministratore"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 55fa332e694e..cf4ca527b4af 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"נעל עכשיו"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"מצב בטוח"</string>
<string name="android_system_label" msgid="6577375335728551336">"‏מערכת Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"אישי"</string>
@@ -1452,8 +1454,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"‏בקש PIN לפני ביטול הצמדה"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"בקש קו ביטול נעילה לפני ביטול הצמדה"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"בקש סיסמה לפני ביטול הצמדה"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"אין אפשרות לשנות את גודל האפליקציה, גלול אותה בשתי אצבעות."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"הותקנה על ידי מנהל המערכת שלך"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"עודכן על ידי מנהל המערכת שלך"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"נמחקה על ידי מנהל המערכת שלך"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index a74d5b7c0bc7..1ebf6995280f 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"音声アシスト"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"今すぐロック"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g> 件)"</string>
<string name="safeMode" msgid="2788228061547930246">"セーフモード"</string>
<string name="android_system_label" msgid="6577375335728551336">"Androidシステム"</string>
<string name="user_owner_label" msgid="2804351898001038951">"個人用"</string>
@@ -1434,8 +1435,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"オフライン再生を解除する前にPINの入力を求める"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"画面固定を解除する前にロック解除パターンの入力を求める"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"オフライン再生を解除する前にパスワードの入力を求める"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"アプリのサイズは変更できません。2 本の指でスクロールしてください。"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"管理者によってインストールされました"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"管理者によって更新されています"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"管理者によって削除されました"</string>
diff --git a/core/res/res/values-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml
index d37f6099660c..604323ad4ef7 100644
--- a/core/res/res/values-ka-rGE/strings.xml
+++ b/core/res/res/values-ka-rGE/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"ხმოვანი ასისტ."</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ახლა ჩაკეტვა"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"უსაფრთხო რეჟიმი"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-ის სისტემა"</string>
<string name="user_owner_label" msgid="2804351898001038951">"პირადი"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"ფიქსაციის მოხსნამდე PIN-ის მოთხოვნა"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"ფიქსაციის მოხსნამდე განბლოკვის ნიმუშის მოთხოვნა"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"ფიქსაციის მოხსნამდე პაროლის მოთხოვნა"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"აპის ზომა ვერ შეიცვლება. გადაადგილდით მასში ორი თითის მეშვეობით."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"თქვენი ადმინისტრატორის მიერ დაყენებული"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"განახლებულია თქვენი ადმინისტრატორის მიერ"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"თქვენი ადმინისტრატორის მიერ წაშლილი"</string>
diff --git a/core/res/res/values-kk-rKZ/strings.xml b/core/res/res/values-kk-rKZ/strings.xml
index 0a79c3bd0ec8..323be5dbb8f1 100644
--- a/core/res/res/values-kk-rKZ/strings.xml
+++ b/core/res/res/values-kk-rKZ/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Дауыс көмекшісі"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Қазір бекіту"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Қауіпсіз режим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android жүйесі"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Жеке"</string>
@@ -1305,7 +1307,7 @@
<string name="enable_accessibility_canceled" msgid="3833923257966635673">"Қол жетімділік өшірілді."</string>
<string name="user_switched" msgid="3768006783166984410">"Ағымдағы пайдаланушы <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="2871009331809089783">"<xliff:g id="NAME">%1$s</xliff:g> ауысу орындалуда…"</string>
- <string name="user_logging_out_message" msgid="8939524935808875155">"<xliff:g id="NAME">%1$s</xliff:g> ішінен шығу орындалуда…"</string>
+ <string name="user_logging_out_message" msgid="8939524935808875155">"<xliff:g id="NAME">%1$s</xliff:g> ішінен шығу…"</string>
<string name="owner_name" msgid="2716755460376028154">"Пайдаланушы"</string>
<string name="error_message_title" msgid="4510373083082500195">"Қателік"</string>
<string name="error_message_change_not_allowed" msgid="1347282344200417578">"Бұл өзгертуге әкімші рұқсат етпеген"</string>
diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml
index 21b1dc68ecb3..c00bc14f5361 100644
--- a/core/res/res/values-km-rKH/strings.xml
+++ b/core/res/res/values-km-rKH/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"ជំនួយសម្លេង"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ចាក់សោ​ឥឡូវនេះ"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"របៀប​​​សុវត្ថិភាព"</string>
<string name="android_system_label" msgid="6577375335728551336">"ប្រព័ន្ធ​​ Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ផ្ទាល់ខ្លួន"</string>
@@ -1436,8 +1438,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"សួរ​រក​កូដ PIN មុន​ពេល​ផ្ដាច់"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"សួរ​រក​លំនាំ​ដោះ​សោ​មុន​ពេល​ផ្ដាច់"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"សួរ​រក​ពាក្យ​សម្ងាត់​មុន​ពេល​ផ្ដាច់"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"កម្មវិធីមិនអាចផ្លាស់ប្តូរទំហំបានទេ សូមរមូរវាដោយប្រើម្រាមដៃពីរ។"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"បានដំឡើងដោយអ្នកគ្រប់គ្រងរបស់អ្នក"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"បានធ្វើបច្ចុប្បន្នភាពដោយអ្នកគ្រប់គ្រងរបស់អ្នក"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"បានលុបដោយអ្នកគ្រប់គ្រងរបស់អ្នក"</string>
diff --git a/core/res/res/values-kn-rIN/strings.xml b/core/res/res/values-kn-rIN/strings.xml
index e828ce0a5efa..1aba0bb28ad4 100644
--- a/core/res/res/values-kn-rIN/strings.xml
+++ b/core/res/res/values-kn-rIN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"ಧ್ವನಿ ಸಹಾಯಕ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ಈಗ ಲಾಕ್ ಮಾಡಿ"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"ಸುರಕ್ಷಿತ ಮೋಡ್"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android ಸಿಸ್ಟಂ"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ವೈಯಕ್ತಿಕ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 8ed4a6f9630f..3384f0923bea 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"음성 지원"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"지금 잠그기"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"안전 모드"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android 시스템"</string>
<string name="user_owner_label" msgid="2804351898001038951">"개인"</string>
@@ -229,7 +231,7 @@
<string name="permgrouplab_contacts" msgid="3657758145679177612">"주소록"</string>
<string name="permgroupdesc_contacts" msgid="6951499528303668046">"주소록 액세스"</string>
<string name="permgrouplab_location" msgid="7275582855722310164">"위치"</string>
- <string name="permgroupdesc_location" msgid="1346617465127855033">"이 기기의 위치에 액세스하기"</string>
+ <string name="permgroupdesc_location" msgid="1346617465127855033">"이 기기의 위치에 액세스"</string>
<string name="permgrouplab_calendar" msgid="5863508437783683902">"캘린더"</string>
<string name="permgroupdesc_calendar" msgid="3889615280211184106">"캘린더 액세스"</string>
<string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
@@ -239,7 +241,7 @@
<string name="permgrouplab_microphone" msgid="171539900250043464">"마이크"</string>
<string name="permgroupdesc_microphone" msgid="4988812113943554584">"오디오 녹음"</string>
<string name="permgrouplab_camera" msgid="4820372495894586615">"카메라"</string>
- <string name="permgroupdesc_camera" msgid="3250611594678347720">"사진 찍기 및 동영상 녹화"</string>
+ <string name="permgroupdesc_camera" msgid="3250611594678347720">"사진 및 동영상 촬영"</string>
<string name="permgrouplab_phone" msgid="5229115638567440675">"전화"</string>
<string name="permgroupdesc_phone" msgid="6234224354060641055">"전화 걸기 및 관리"</string>
<string name="permgrouplab_sensors" msgid="416037179223226722">"신체 센서"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"고정 해제 이전에 PIN 요청"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"고정 해제 이전에 잠금해제 패턴 요청"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"고정 해제 이전에 비밀번호 요청"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"앱에서 크기 조절이 불가능합니다. 두 손가락을 사용해 스크롤하세요."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"관리자가 설치함"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"관리자에 의해 업데이트됨"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"관리자가 삭제함"</string>
diff --git a/core/res/res/values-ky-rKG/strings.xml b/core/res/res/values-ky-rKG/strings.xml
index 8381c32ce22f..870c858c0ba4 100644
--- a/core/res/res/values-ky-rKG/strings.xml
+++ b/core/res/res/values-ky-rKG/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Үн жардамчысы"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Азыр кулпулоо"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Коопсуз режим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android Тутуму"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Жеке"</string>
@@ -1435,8 +1437,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Бошотуудан мурун PIN суралсын"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Бошотуудан мурун кулпуну ачкан үлгү суралсын"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Бошотуудан мурун сырсөз суралсын"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Колдонмонун көлөмүн өзгөртүүгө болбойт, андыктан эки манжаңыз менен сыдырып караңыз."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Администраторуңуз тарабынан орнотулган"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Администраторуңуз жаңырткан"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Администраторуңуз тарабынан жок кылынган"</string>
diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml
index 9884bf18f26a..d5484d3a2396 100644
--- a/core/res/res/values-lo-rLA/strings.xml
+++ b/core/res/res/values-lo-rLA/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"ຊ່ວຍ​ເຫຼືອ​ທາງ​ສຽງ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ລັອກ​ດຽວ​ນີ້"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Safe mode"</string>
<string name="android_system_label" msgid="6577375335728551336">"ລະບົບ Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"​ສ່ວນ​ໂຕ"</string>
@@ -1305,7 +1307,7 @@
<string name="enable_accessibility_canceled" msgid="3833923257966635673">"ຍົກເລີກໂຕຊ່ວຍການເຂົ້າເຖິງແລ້ວ."</string>
<string name="user_switched" msgid="3768006783166984410">"ຜູ່ໃຊ້ປັດຈຸບັນ <xliff:g id="NAME">%1$s</xliff:g> ."</string>
<string name="user_switching_message" msgid="2871009331809089783">"ກຳ​ລັງ​ສະ​ລັບ​​ໄປ​ຫາ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
- <string name="user_logging_out_message" msgid="8939524935808875155">"ກຳລັງ​ອອກ​ຈາກ​ລະບົບ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
+ <string name="user_logging_out_message" msgid="8939524935808875155">"ກຳລັງອອກຈາກລະບົບ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="2716755460376028154">"ເຈົ້າຂອງ"</string>
<string name="error_message_title" msgid="4510373083082500195">"ຜິດພາດ"</string>
<string name="error_message_change_not_allowed" msgid="1347282344200417578">"​ຜູ່​ເບິ່ງ​ແຍງ​ລະ​ບົບ​ຂອງ​ທ່ານບໍ່​ອະ​ນຸ​ຍາດ​ໃຫ້​ປ່ຽນ​ແປງ​ສິ່ງ​ນີ້"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"​ຖາມ​ຫາ PIN ກ່ອນ​ຍົກ​ເລີກ​ການປັກ​ໝຸດ"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"​ຖາມ​ຫາ​ຮູບ​ແບບ​ປົດ​ລັອກ​ກ່ອນ​ຍົກ​ເລີກ​ການ​ປັກ​ໝຸດ"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"​ຖາມ​ຫາ​ລະ​ຫັດ​ຜ່ານ​ກ່ອນ​ຍົກ​ເລີກ​ການ​ປັກ​ໝຸດ"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"ບໍ່ສາມາດປັບຂະໜາດແອັບຯໄດ້, ກະລຸນາເລື່ອນມັນໂດຍໃຊ້ນິ້ວສອງນິ້ວແທນ."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"ຜູ້​ຄວບ​ຄຸມ​ຂອງ​ທ່ານ​ຕິດ​ຕັ້ງ​ໃສ່​ແລ້ວ"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"ອັບ​ເດດ​ໂດຍ​ຜູ້​ຄວບ​ຄຸມ​ຂອງ​ທ່ານ​ແລ້ວ"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"ຖືກ​ຜູ້​ຄວບ​ຄຸມ​ຂອງ​ທ່ານ​ລຶບ​ໄປ​ແລ້ວ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 1c326fe7136a..6ee24cf9b01a 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Užrakinti dabar"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Saugos režimas"</string>
<string name="android_system_label" msgid="6577375335728551336">"„Android“ sistema"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Asmeninė"</string>
@@ -1452,8 +1454,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Prašyti PIN kodo prieš atsegant"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Prašyti atrakinimo piešinio prieš atsegant"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Prašyti slaptažodžio prieš atsegant"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Programos dydis nekeičiamas, slinkite dviem pirštais."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Įdiegė administratorius"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Atnaujino administratorius"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Ištrynė administratorius"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 23be85ac8afb..77859652141b 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -223,6 +223,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Balss palīgs"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloķēt tūlīt"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"Pārsniedz"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Drošais režīms"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android sistēma"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personisks"</string>
diff --git a/core/res/res/values-mk-rMK/strings.xml b/core/res/res/values-mk-rMK/strings.xml
index cd2efc4a0500..eb90369db328 100644
--- a/core/res/res/values-mk-rMK/strings.xml
+++ b/core/res/res/values-mk-rMK/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Гласовна помош"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Заклучи сега"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Безбеден режим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Систем Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Лични"</string>
@@ -1436,8 +1437,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Прашај за ПИН пред откачување"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Прашај за шема за отклучување пред откачување"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Прашај за лозинка пред откачување"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Не може да се промени големината на апликацијата. Движете ја со два прста."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Инсталирано од администраторот"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Ажурирано од администраторот"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Избришано од администраторот"</string>
diff --git a/core/res/res/values-ml-rIN/strings.xml b/core/res/res/values-ml-rIN/strings.xml
index c8eae7e7e832..a83928ec600f 100644
--- a/core/res/res/values-ml-rIN/strings.xml
+++ b/core/res/res/values-ml-rIN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"വോയ്‌സ് സഹായം"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ഇപ്പോൾ ലോക്കുചെയ്യുക"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"സുരക്ഷിത മോഡ്"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android സിസ്റ്റം"</string>
<string name="user_owner_label" msgid="2804351898001038951">"വ്യക്തിഗതം"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"ചെയ്യുംമുമ്പ് പിൻ ചോദിക്കൂ"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"അൺപിൻ ചെയ്യുന്നതിനുമുമ്പ് അൺലോക്ക് പാറ്റേൺ ആവശ്യപ്പെടുക"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"അൺപിൻ ചെയ്യുന്നതിനുമുമ്പ് പാസ്‌വേഡ് ആവശ്യപ്പെടുക"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"ആപ്പിന്റെ വലുപ്പം ക്രമീകരിക്കാൻ കഴിയില്ല, രണ്ട് വിരലുകൾ ഉപയോഗിച്ച് അത് സ്ക്രോൾ ചെയ്യുക."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"നിങ്ങളുടെ അഡ്‌മിനിസ്‌ട്രേറ്റർ ഇൻസ്റ്റാളുചെയ്‌തു"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"നിങ്ങളുടെ അഡ്‌മിനിസ്‌ട്രേറ്റർ അപ്‌ഡേറ്റുചെയ്‌തു"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"നിങ്ങളുടെ അഡ്‌മിനിസ്‌ട്രേറ്റർ ഇല്ലാതാക്കി"</string>
diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml
index 7b7d4b167e85..a9fb85932546 100644
--- a/core/res/res/values-mn-rMN/strings.xml
+++ b/core/res/res/values-mn-rMN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Дуут туслах"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Одоо түгжих"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Аюулгүй горим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Андройд систем"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Хувийн"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Тогтоосныг суллахаас өмнө PIN асуух"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Тогтоосныг суллахаас өмнө түгжээ тайлах хээ асуух"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Тогтоосныг суллахаас өмнө нууц үг асуух"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Апп-н хэмжээ нь өөрчлөгддөггүй. Үүнийг 2 хуруугаараа гүйлгэнэ үү."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Таны админ суулгасан байна"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Танай админ шинэчилсэн"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Таны админ устгасан байна"</string>
diff --git a/core/res/res/values-mr-rIN/strings.xml b/core/res/res/values-mr-rIN/strings.xml
index 6ae6e82be9df..a8896c3bceca 100644
--- a/core/res/res/values-mr-rIN/strings.xml
+++ b/core/res/res/values-mr-rIN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"व्हॉइस सहाय्य"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"आता लॉक करा"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"सुरक्षित मोड"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android सिस्‍टम"</string>
<string name="user_owner_label" msgid="2804351898001038951">"वैयक्तिक"</string>
diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml
index f354ea0aedfa..cababdd915f2 100644
--- a/core/res/res/values-ms-rMY/strings.xml
+++ b/core/res/res/values-ms-rMY/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Bantuan Suara"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Kunci sekarang"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Mod selamat"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistem Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Peribadi"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Minta PIN sebelum menyahsemat"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Minta corak buka kunci sebelum menyahsemat"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Minta kata laluan sebelum menyahsemat"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Apl tidak boleh tukar saiznya, tatal apl itu menggunakan dua jari."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Dipasang oleh pentadbir anda"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Dikemas kini oleh pentadbir anda"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Dipadamkan oleh pentadbir anda"</string>
diff --git a/core/res/res/values-my-rMM/strings.xml b/core/res/res/values-my-rMM/strings.xml
index 4a0a2276f9d8..8e61f41bc560 100644
--- a/core/res/res/values-my-rMM/strings.xml
+++ b/core/res/res/values-my-rMM/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"အသံ အကူအညီ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ယခု သော့ပိတ်ရန်"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"၉၉၉+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"အန္တရာယ်ကင်းမှု စနစ်(Safe mode)"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android စနစ်"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ကိုယ်ရေး"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 4fc707887a48..f29a903b2f0d 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Talehjelp"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lås nå"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Sikkermodus"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-system"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personlig"</string>
@@ -235,7 +237,7 @@
<string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
<string name="permgroupdesc_sms" msgid="4656988620100940350">"sende og lese SMS-meldinger"</string>
<string name="permgrouplab_storage" msgid="1971118770546336966">"Lagring"</string>
- <string name="permgroupdesc_storage" msgid="637758554581589203">"åpne bilder, media og filer på enheten din"</string>
+ <string name="permgroupdesc_storage" msgid="637758554581589203">"åpne bilder, medier og filer på enheten din"</string>
<string name="permgrouplab_microphone" msgid="171539900250043464">"Mikrofon"</string>
<string name="permgroupdesc_microphone" msgid="4988812113943554584">"spill inn lyd"</string>
<string name="permgrouplab_camera" msgid="4820372495894586615">"Kamera"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"PIN-kode for å løsne apper"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Krev bruk av opplåsningsmønster for å løsne apper"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Krev passord for å løsne apper"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Du kan ikke endre størrselse på appen – rull med to fingre."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Installert av administratoren"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Oppdatert av administratoren"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Slettet av administratoren"</string>
diff --git a/core/res/res/values-ne-rNP/strings.xml b/core/res/res/values-ne-rNP/strings.xml
index 17e67f85fc76..a33289f9ed01 100644
--- a/core/res/res/values-ne-rNP/strings.xml
+++ b/core/res/res/values-ne-rNP/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"आवाज सहायता"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"अब बन्द गर्नुहोस्"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"९९९+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"सुरक्षित मोड"</string>
<string name="android_system_label" msgid="6577375335728551336">"एन्ड्रोइड प्रणाली"</string>
<string name="user_owner_label" msgid="2804351898001038951">"व्यक्तिगत"</string>
@@ -1440,12 +1442,11 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"पिन निकाल्नुअघि PIN सोध्नुहोस्"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"पिन निकाल्नुअघि खोल्ने रूपरेखा सोध्नुहोस्"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"पिन निकाल्नुअघि पासवर्ड सोध्नुहोस्"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"अनुप्रयोगको आकार सानो-ठुलो बनाउन मिल्दैन, दुई औँलाले यसलाई स्क्रोल गर्नुहोस्।"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"तपाईँको प्रशासकद्वारा स्थापना गरिएको"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"तपाईँको प्रशासकद्वारा अद्यावधिक गरिएको"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"तपाईँको प्रशासकद्वारा हटाइएको"</string>
- <string name="battery_saver_description" msgid="1960431123816253034">"ब्याट्रीको आयु सुधार्न, ब्याट्री रक्षकले तपाईँको यन्त्रको कार्यसम्पादन घटाउँछ र भाइब्रेसन, स्थान सेवा र बहुसंख्यक पृष्ठभूमि डेटा सीमित गर्दछ। इमेल, सन्देश, र अन्य अनुप्रयोगहरू जुन सिङ्कमा भर पर्छन् अद्यावधिक नहुन सक्छन् जबसम्म तपाईँ तिनीहरूलाई खोल्नुहुन्न\n\n ब्याट्री रक्षक स्वत: निस्कृय हुन्छ जब तपाईँको यन्त्र चार्ज हुँदै हुन्छ।"</string>
+ <string name="battery_saver_description" msgid="1960431123816253034">"ब्याट्रीको आयु सुधार्न, ब्याट्री संरक्षकले तपाईँको यन्त्रको कार्यसम्पादन घटाउँछ र भाइब्रेसन, स्थान सेवा र बहुसंख्यक पृष्ठभूमि डेटा सीमित गर्दछ। इमेल, सन्देश, र अन्य अनुप्रयोगहरू जुन सिङ्कमा भर पर्छन् अद्यावधिक नहुन सक्छन् जबसम्म तपाईँ तिनीहरूलाई खोल्नुहुन्न\n\n ब्याट्री संरक्षक स्वत: निस्कृय हुन्छ जब तपाईँको यन्त्र चार्ज हुँदै हुन्छ।"</string>
<plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="4367877408072000848">
<item quantity="other"> %1$d मिनेटको लागि (<xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g> सम्म)</item>
<item quantity="one">एक मिनेटको लागि (<xliff:g id="FORMATTEDTIME_0">%2$s</xliff:g> सम्म)</item>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index d04e10bded21..53b45b0172fb 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Spraakassistent"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Nu vergrendelen"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999 +"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Veilige modus"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-systeem"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Persoonlijk"</string>
@@ -243,7 +244,7 @@
<string name="permgrouplab_phone" msgid="5229115638567440675">"Telefoon"</string>
<string name="permgroupdesc_phone" msgid="6234224354060641055">"bellen en telefoontjes beheren"</string>
<string name="permgrouplab_sensors" msgid="416037179223226722">"Lichaamssensoren"</string>
- <string name="permgroupdesc_sensors" msgid="7147968539346634043">"toegang tot sensorgegevens over je vitale functies"</string>
+ <string name="permgroupdesc_sensors" msgid="7147968539346634043">"toegang krijgen tot sensorgegevens over je vitale functies"</string>
<string name="capability_title_canRetrieveWindowContent" msgid="3901717936930170320">"Inhoud van vensters ophalen"</string>
<string name="capability_desc_canRetrieveWindowContent" msgid="3772225008605310672">"De inhoud inspecteren van een venster waarmee je interactie hebt."</string>
<string name="capability_title_canRequestTouchExploration" msgid="3108723364676667320">"\'Verkennen via aanraking\' inschakelen"</string>
diff --git a/core/res/res/values-pa-rIN/strings.xml b/core/res/res/values-pa-rIN/strings.xml
index c3df4ac5a971..d450bf929748 100644
--- a/core/res/res/values-pa-rIN/strings.xml
+++ b/core/res/res/values-pa-rIN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"ਵੌਇਸ ਅਸਿਸਟ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ਹੁਣ ਲੌਕ ਕਰੋ"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"ਸੁਰੱਖਿਅਤ ਮੋਡ"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android System"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ਨਿੱਜੀ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 14e56cb067be..b7f984d95598 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Asystent głosowy"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Zablokuj teraz"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"&gt;999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Tryb awaryjny"</string>
<string name="android_system_label" msgid="6577375335728551336">"System Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Osobiste"</string>
@@ -1452,8 +1454,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Podaj PIN, aby odpiąć"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Aby odpiąć, poproś o wzór odblokowania"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Aby odpiąć, poproś o hasło"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Rozmiaru tej aplikacji nie można zmienić. Przewiń ją dwoma palcami."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Zainstalowany przez administratora"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Zaktualizowane przez administratora"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Usunięty przez administratora"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 8212d13cdf39..4464259e99ee 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Ajuda de voz"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloquear agora"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"&gt;999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Modo de segurança"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Pessoal"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Pedir PIN antes de liberar"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Pedir padrão de desbloqueio antes de liberar"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Pedir senha antes de liberar"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"O app não é redimensionável. Desloque-o com dois dedos."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Instalado pelo seu administrador"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Atualizado pelo administrador"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Excluído pelo seu administrador"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 90091d8ef64e..af38435b9485 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Assist. de voz"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloquear agora"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Modo seguro"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Pessoal"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Pedir PIN antes de soltar"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Pedir sequência de desbloqueio antes de soltar"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Pedir palavra-passe antes de soltar"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"A aplicação não é redimensionável. Desloque-a com dois dedos."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Instalado pelo administrador"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Atualizado pelo administrador"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Eliminado pelo administrador"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 8212d13cdf39..4464259e99ee 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Ajuda de voz"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Bloquear agora"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"&gt;999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Modo de segurança"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Pessoal"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Pedir PIN antes de liberar"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Pedir padrão de desbloqueio antes de liberar"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Pedir senha antes de liberar"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"O app não é redimensionável. Desloque-o com dois dedos."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Instalado pelo seu administrador"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Atualizado pelo administrador"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Excluído pelo seu administrador"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 9536c706e66a..7ac92c30983e 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -223,6 +223,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Asistent vocal"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Blocați acum"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"˃999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Mod sigur"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistemul Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
@@ -1443,8 +1445,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Solicită codul PIN înainte de a anula fixarea"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Solicită modelul pentru deblocare înainte de a anula fixarea"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Solicită parola înainte de a anula fixarea"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Aplicația nu poate fi redimensionată. Derulați în ea cu două degete."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Instalat de administrator"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Actualizat de un administrator"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Șters de administrator"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 613c738f69a4..b7ba99f3ba07 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Аудиоподсказки"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Заблокировать"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"&gt;999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Безопасный режим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Система Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Личные данные"</string>
@@ -1452,8 +1454,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"PIN-код для отключения"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Запрашивать графический ключ для отключения блокировки"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Запрашивать пароль для отключения блокировки"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Размер окна нельзя изменить. Прокрутите страницу двумя пальцами."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Установлено администратором"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Обновлено администратором"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Удалено администратором"</string>
diff --git a/core/res/res/values-si-rLK/strings.xml b/core/res/res/values-si-rLK/strings.xml
index b9ae0e91f43a..4e26517759d3 100644
--- a/core/res/res/values-si-rLK/strings.xml
+++ b/core/res/res/values-si-rLK/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"හඬ සහායක"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"දැන් අගුළු දමන්න"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"ආරක්‍ෂිත ආකාරය"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android පද්ධතිය"</string>
<string name="user_owner_label" msgid="2804351898001038951">"පෞද්ගලික"</string>
@@ -1436,8 +1438,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"ගැලවීමට පෙර PIN විමසන්න"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"ගැලවීමට පෙර අගුළු අරින රටාව සඳහා අසන්න"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"ගැලවීමට පෙර මුරපදය විමසන්න"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"යෙදුම ප්‍රතිප්‍රමාණ කළ හැකි නොවේ, එය ඇඟිලි දෙකකින් අනුචලනය කරන්න."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"ඔබගේ පරිපාලක විසින් ස්ථාපනය කරන ලද"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"ඔබගේ පරිපාලක විසින් යාවත්කාලීන කරන ලදී"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"ඔබගේ පරිපාලක විසින් මකන ලද"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 856f8f7b3d5a..ba5465eeeb67 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Hlasový asistent"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Uzamknúť"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Núdzový režim"</string>
<string name="android_system_label" msgid="6577375335728551336">"Systém Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Osobné"</string>
@@ -1452,8 +1454,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Pred uvoľnením požiadať o číslo PIN"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Pred uvoľnením požiadať o bezpečnostný vzor"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Pred uvoľnením požiadať o heslo"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Veľkosť aplikácie nie je možné zmeniť. Zobrazenie môžete posúvať dvoma prstami."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Inštalovaný správcom"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Aktualizované správcom"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Odstránený správcom"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index eff1574c2b89..04b7fc7fa7a3 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Glas. pomočnik"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Zakleni zdaj"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999 +"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Varni način"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistem Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Osebno"</string>
@@ -1452,8 +1454,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Zahtevaj PIN pred odpenjanjem"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Pred odpenjanjem vprašaj za vzorec za odklepanje"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Pred odpenjanjem vprašaj za geslo"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Velikosti aplikacije ni mogoče spremeniti. Po njej se pomikajte z dvema prstoma."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Namestil skrbnik"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Posodobil skrbnik"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Izbrisal skrbnik"</string>
diff --git a/core/res/res/values-sq-rAL/strings.xml b/core/res/res/values-sq-rAL/strings.xml
index a29a9605e5d7..b8ca56561422 100644
--- a/core/res/res/values-sq-rAL/strings.xml
+++ b/core/res/res/values-sq-rAL/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Ndihma zanore"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Kyç tani"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Modaliteti i sigurisë"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistemi \"android\""</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
@@ -1434,8 +1435,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Zhgozhdimi kërkon PIN-in"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Kërko model shkyçjeje para heqjes së gozhdimit"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Kërko fjalëkalim para heqjes nga gozhdimi."</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Përmasa e apl. nuk mund të ndryshohet, lëvize atë me të dy gishtat."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"U instalua nga administratori yt"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Përditësuar nga administratori"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"U fshi nga administratori yt"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 9d122922b34e..4118c63b227d 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -223,6 +223,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Гласовна помоћ"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Закључај одмах"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Безбедни режим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android систем"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Лично"</string>
@@ -1443,8 +1445,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Тражи PIN пре откачињања"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Тражи шаблон за откључавање пре откачињања"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Тражи лозинку пре откачињања"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Величина апликације не може да се мења, померајте је помоћу два прста."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Инсталирао је ваш администратор"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Ажурирао је администратор"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Избрисао је ваш адмиистратор"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 57edbcb0054e..40141de1eb63 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Lås nu"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Säkert läge"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-system"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personligt"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Be om pinkod innan skärmen slutar fästas"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Be om upplåsningsmönster innan skärmen slutar fästas"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Be om lösenord innan skärmen slutar fästas"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Det går inte att ändra appens storlek. Rulla med två fingrar."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Paketet har installerats av administratören"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Uppdaterat av administratören"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Paketet har raderats av administratören"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 3de8eebb286b..196d5dcff0d2 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -224,6 +224,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Usaidizi wa Sauti"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Funga sasa"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Mtindo salama"</string>
<string name="android_system_label" msgid="6577375335728551336">"Mfumo wa Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Binafsi"</string>
diff --git a/core/res/res/values-ta-rIN/strings.xml b/core/res/res/values-ta-rIN/strings.xml
index 30b326aded5a..1113776f6bb8 100644
--- a/core/res/res/values-ta-rIN/strings.xml
+++ b/core/res/res/values-ta-rIN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"குரல் உதவி"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"இப்போது பூட்டு"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"பாதுகாப்பு பயன்முறை"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android அமைப்பு"</string>
<string name="user_owner_label" msgid="2804351898001038951">"தனிப்பட்ட"</string>
diff --git a/core/res/res/values-te-rIN/strings.xml b/core/res/res/values-te-rIN/strings.xml
index 365908003754..c563e7c19342 100644
--- a/core/res/res/values-te-rIN/strings.xml
+++ b/core/res/res/values-te-rIN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"వాయిస్ సహాయకం"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ఇప్పుడు లాక్ చేయండి"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"సురక్షిత మోడ్"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android సిస్టమ్"</string>
<string name="user_owner_label" msgid="2804351898001038951">"వ్యక్తిగతం"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index f168d6c0c718..0080f514a1b6 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"ตัวช่วยเสียง"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ล็อกเลย"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"โหมดปลอดภัย"</string>
<string name="android_system_label" msgid="6577375335728551336">"ระบบ Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ส่วนตัว"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"ขอ PIN ก่อนเลิกตรึง"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"ขอรูปแบบการปลดล็อกก่อนเลิกตรึง"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"ขอรหัสผ่านก่อนเลิกตรึง"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"แอปไม่สามารถปรับขนาดได้ เลื่อนแอปด้วยนิ้ว 2 นิ้ว"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"ติดตั้งโดยผู้ดูแลระบบของคุณ"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"อัปเดตโดยผู้ดูแลระบบ"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"ลบโดยผู้ดูแลระบบของคุณ"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index ab30e3063b61..4f907c11c4e0 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"I-lock ngayon"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Safe mode"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android System"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Personal"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Humingi ng PIN bago mag-unpin"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Humingi ng pattern sa pag-unlock bago mag-unpin"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Humingi ng password bago mag-unpin"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Hindi nare-resize ang app, mag-scroll dito gamit ang dalawang daliri."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Na-install ng iyong administrator"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Na-update ng iyong administrator"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Na-delete ng iyong administrator"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 8cf8935ee6c3..06f8c753f052 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Sesli Yardım"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Şimdi kilitle"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Güvenli mod"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android Sistemi"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Kişisel"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Sabitlemeyi kaldırmadan önce PIN\'i sor"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Sabitlemeyi kaldırmadan önce kilit açma desenini sor"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Sabitlemeyi kaldırmadan önce şifre sor"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Uygulama yeniden boyutlandırılamaz. İki parmağınızla kaydırın."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Yöneticiniz tarafından yüklendi"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Yöneticiniz tarafından güncellendi"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Yöneticiniz tarafından silindi"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 7522e4eef224..721f24de4e90 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -224,6 +224,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Голос. підказки"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Блокувати зараз"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Безп. режим"</string>
<string name="android_system_label" msgid="6577375335728551336">"Система Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Особисті дані"</string>
diff --git a/core/res/res/values-ur-rPK/strings.xml b/core/res/res/values-ur-rPK/strings.xml
index c4d246697626..e31fa5717381 100644
--- a/core/res/res/values-ur-rPK/strings.xml
+++ b/core/res/res/values-ur-rPK/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Voice Assist"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"ابھی مقفل کریں"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"‎999+‎"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"حفاظتی وضع"</string>
<string name="android_system_label" msgid="6577375335728551336">"‏Android سسٹم"</string>
<string name="user_owner_label" msgid="2804351898001038951">"ذاتی"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"‏پن ہٹانے سے پہلے PIN طلب کریں"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"پن ہٹانے سے پہلے غیر مقفل کرنے کا پیٹرن طلب کریں"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"پن ہٹانے سے پہلے پاس ورڈ طلب کریں"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"ایپ ری سائز ایبل نہیں ہے، اسے دو انگلیوں کے ساتھ سکرول کریں۔"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"آپ کے منتظم کی جانب سے انسٹال کر دیا گیا"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"آپ کے منتظم نے اپ ڈيٹ کر دیا"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"آپ کے منتظم کی جانب سے حذف کر دیا گیا"</string>
diff --git a/core/res/res/values-uz-rUZ/strings.xml b/core/res/res/values-uz-rUZ/strings.xml
index 596bbede146c..04896384e7c6 100644
--- a/core/res/res/values-uz-rUZ/strings.xml
+++ b/core/res/res/values-uz-rUZ/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Ovozli yordam"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Qulflash"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Xavfsiz usul"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android tizimi"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Shaxsiy"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Yechishda PIN-kod so‘ralsin"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Bo‘shatishdan oldin chizmali parol so‘ralsin"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Bo‘shatishdan oldin parol so‘ralsin"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Oyna o‘lchamini o‘zgartirib bo‘lmaydi. Sahifani ikkita barmoq bilan aylantiring."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Administratoringiz tomonidan o‘rnatilgan"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Administratoringiz tomonidan yangilandi"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Administratoringiz tomonidan o‘chirilgan"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index c5aa37e75145..ccd13453130b 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Trợ lý thoại"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Khóa ngay"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"Chế độ an toàn"</string>
<string name="android_system_label" msgid="6577375335728551336">"Hệ thống Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Cá nhân"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Hỏi mã PIN trước khi bỏ ghim"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Hỏi hình mở khóa trước khi bỏ ghim"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Hỏi mật khẩu trước khi bỏ ghim"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"Ứng dụng không đổi kích thước được, hãy cuộn ứng dụng bằng hai ngón tay."</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"Được cài đặt bởi quản trị viên của bạn"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"Được cập nhật bởi quản trị viên của bạn"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"Đã bị xóa bởi quản trị viên của bạn"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 311fe55e1986..1afede050354 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"语音助理"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"立即锁定"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"安全模式"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android 系统"</string>
<string name="user_owner_label" msgid="2804351898001038951">"个人"</string>
@@ -1305,8 +1307,7 @@
<string name="enable_accessibility_canceled" msgid="3833923257966635673">"已取消无障碍功能。"</string>
<string name="user_switched" msgid="3768006783166984410">"当前用户是<xliff:g id="NAME">%1$s</xliff:g>。"</string>
<string name="user_switching_message" msgid="2871009331809089783">"正在切换为<xliff:g id="NAME">%1$s</xliff:g>…"</string>
- <!-- no translation found for user_logging_out_message (8939524935808875155) -->
- <skip />
+ <string name="user_logging_out_message" msgid="8939524935808875155">"正在将<xliff:g id="NAME">%1$s</xliff:g>退出帐号…"</string>
<string name="owner_name" msgid="2716755460376028154">"机主"</string>
<string name="error_message_title" msgid="4510373083082500195">"错误"</string>
<string name="error_message_change_not_allowed" msgid="1347282344200417578">"您的管理员不允许进行此更改"</string>
@@ -1435,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"取消时要求输入PIN码"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"取消时要求绘制解锁图案"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"取消时要求输入密码"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"无法调整这个应用的大小,请用双指滚动应用。"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"已由管理员安装"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"由您单位的管理员更新"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"已被管理员删除"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index efab307b6ae8..84c85bfda652 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"語音助手"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"立即鎖定"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"安全模式"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android 系統"</string>
<string name="user_owner_label" msgid="2804351898001038951">"個人"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 4d23d76109c0..b45fe758af3c 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -222,6 +222,8 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"語音小幫手"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"立即鎖定"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"超過 999"</string>
+ <!-- no translation found for notification_children_count_bracketed (1769425473168347839) -->
+ <skip />
<string name="safeMode" msgid="2788228061547930246">"安全模式"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android 系統"</string>
<string name="user_owner_label" msgid="2804351898001038951">"個人"</string>
@@ -1434,8 +1436,7 @@
<string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"取消固定時必須輸入 PIN"</string>
<string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"取消固定時必須畫出解鎖圖形"</string>
<string name="lock_to_app_unlock_password" msgid="6380979775916974414">"取消固定時必須輸入密碼"</string>
- <!-- no translation found for dock_non_resizeble_text (9156251681042762723) -->
- <skip />
+ <string name="dock_non_resizeble_text" msgid="9156251681042762723">"無法調整這個應用程式的大小,請用雙指捲動該應用程式。"</string>
<string name="package_installed_device_owner" msgid="8420696545959087545">"已由管理員安裝"</string>
<string name="package_updated_device_owner" msgid="8856631322440187071">"由您的管理員更新"</string>
<string name="package_deleted_device_owner" msgid="7650577387493101353">"已遭管理員刪除"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index bd566cdd801b..c2159d7a05a6 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -222,6 +222,7 @@
<string name="global_action_voice_assist" msgid="7751191495200504480">"Isisekeli sezwi"</string>
<string name="global_action_lockdown" msgid="8751542514724332873">"Khiya manje"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
+ <string name="notification_children_count_bracketed" msgid="1769425473168347839">"(<xliff:g id="NOTIFICATIONCOUNT">%d</xliff:g>)"</string>
<string name="safeMode" msgid="2788228061547930246">"Imodi ephephile"</string>
<string name="android_system_label" msgid="6577375335728551336">"Uhlelo lwe-Android"</string>
<string name="user_owner_label" msgid="2804351898001038951">"Okomuntu siqu"</string>
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..3860f68f1292 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" />
@@ -2276,6 +2268,7 @@
<java-symbol type="layout" name="floating_popup_menu_image_button" />
<java-symbol type="layout" name="floating_popup_overflow_list_item" />
<java-symbol type="layout" name="floating_popup_overflow_image_list_item" />
+ <java-symbol type="layout" name="floating_popup_overflow_button" />
<java-symbol type="dimen" name="floating_toolbar_height" />
<java-symbol type="dimen" name="floating_toolbar_menu_button_side_padding" />
<java-symbol type="dimen" name="floating_toolbar_overflow_side_padding" />
@@ -2287,6 +2280,10 @@
<java-symbol type="dimen" name="floating_toolbar_horizontal_margin" />
<java-symbol type="dimen" name="floating_toolbar_vertical_margin" />
<java-symbol type="dimen" name="content_rect_bottom_clip_allowance" />
+ <java-symbol type="drawable" name="ft_avd_tooverflow" />
+ <java-symbol type="drawable" name="ft_avd_toarrow" />
+ <java-symbol type="drawable" name="ft_avd_toarrow_animation" />
+ <java-symbol type="drawable" name="ft_avd_tooverflow_animation" />
<java-symbol type="string" name="date_picker_prev_month_button" />
<java-symbol type="string" name="date_picker_next_month_button" />
@@ -2352,4 +2349,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/apks/install_complete_package_info/Android.mk b/core/tests/coretests/apks/install_complete_package_info/Android.mk
index 1edccb4b288b..19bf3561a706 100644
--- a/core/tests/coretests/apks/install_complete_package_info/Android.mk
+++ b/core/tests/coretests/apks/install_complete_package_info/Android.mk
@@ -5,7 +5,9 @@ LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_PACKAGE_NAME := FrameworkCoreTests_install_complete_package_info
+LOCAL_PACKAGE_NAME := install_complete_package_info
+#LOCAL_MANIFEST_FILE := api_test/AndroidManifest.xml
-include $(BUILD_PACKAGE)
+include $(FrameworkCoreTests_BUILD_PACKAGE)
+#include $(BUILD_PACKAGE)
diff --git a/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml b/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml
index 4c7e968c2722..2897bd5d0b55 100644
--- a/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml
+++ b/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml
@@ -17,4 +17,11 @@
package="com.android.frameworks.coretests.keysets_api">
<application android:hasCode="false">
</application>
+ <key-sets>
+ <key-set android:name="A" >
+ <public-key android:name="keyA"
+ android:value="MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJoN1Nsgqf0V4C/bbN8wo8O2X/S5D76+5Mb9mlIsHkUTUTbHCNk+LxHIUYLm89YbP9zImrV0bUHLUAZUyoMUCiMCAwEAAQ=="/>
+ </key-set>
+ </key-sets>
+
</manifest>
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 9498f4c81d12..b5f06178aa3c 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -73,7 +73,6 @@ import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-@Suppress // Failing.
public class PackageManagerTests extends AndroidTestCase {
private static final boolean localLOGV = true;
@@ -434,14 +433,6 @@ public class PackageManagerTests extends AndroidTestCase {
SECURE_CONTAINERS_PREFIX, publicSrcPath);
assertStartsWith("The native library path should point to the ASEC",
SECURE_CONTAINERS_PREFIX, info.nativeLibraryDir);
- try {
- String compatLib = new File(info.dataDir + "/lib").getCanonicalPath();
- assertEquals("The compatibility lib directory should be a symbolic link to "
- + info.nativeLibraryDir,
- info.nativeLibraryDir, compatLib);
- } catch (IOException e) {
- fail("compat check: Can't read " + info.dataDir + "/lib");
- }
} else {
assertFalse(
(info.privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0);
@@ -1014,7 +1005,8 @@ public class PackageManagerTests extends AndroidTestCase {
private static void assertUninstalled(ApplicationInfo info) throws Exception {
File nativeLibraryFile = new File(info.nativeLibraryDir);
- assertFalse("Native library directory should be erased", nativeLibraryFile.exists());
+ assertFalse("Native library directory " + info.nativeLibraryDir
+ + " should be erased", nativeLibraryFile.exists());
}
public void deleteFromRawResource(int iFlags, int dFlags) throws Exception {
@@ -1650,15 +1642,10 @@ public class PackageManagerTests extends AndroidTestCase {
(info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
assertStartsWith("Native library dir should point to ASEC",
SECURE_CONTAINERS_PREFIX, info.nativeLibraryDir);
- final File nativeLibSymLink = new File(info.dataDir, "lib");
- assertStartsWith("The data directory should have a 'lib' symlink that points to the ASEC container",
- SECURE_CONTAINERS_PREFIX, nativeLibSymLink.getCanonicalPath());
}
}
} catch (NameNotFoundException e) {
failStr("Pkg hasnt been installed correctly");
- } catch (Exception e) {
- failStr("Failed with exception : " + e);
} finally {
if (ip != null) {
cleanUpInstall(ip);
@@ -1689,6 +1676,7 @@ public class PackageManagerTests extends AndroidTestCase {
sampleMoveFromRawResource(installFlags, moveFlags, fail, result);
}
+ @Suppress
@LargeTest
public void testMoveAppInternalToInternal() throws Exception {
int installFlags = PackageManager.INSTALL_INTERNAL;
@@ -2157,6 +2145,7 @@ public class PackageManagerTests extends AndroidTestCase {
-1);
}
+ @Suppress
@LargeTest
public void testFlagFExistingI() throws Exception {
int iFlags = PackageManager.INSTALL_INTERNAL;
@@ -3272,14 +3261,15 @@ public class PackageManagerTests extends AndroidTestCase {
assertTrue(false); // should have thrown
} catch (IllegalArgumentException e) {
}
- installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+ final InstallParams ip = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
try {
ks = pm.getSigningKeySet(otherPkgName);
assertTrue(false); // should have thrown
} catch (SecurityException e) {
+ } finally {
+ cleanUpInstall(ip);
}
- cleanUpInstall(otherPkgName);
ks = pm.getSigningKeySet(mContext.getPackageName());
assertNotNull(ks);
}
@@ -3318,16 +3308,20 @@ public class PackageManagerTests extends AndroidTestCase {
assertTrue(false); // should have thrown
} catch(IllegalArgumentException e) {
}
- installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+
+ // make sure we can get a KeySet from our pkg
+ ks = pm.getKeySetByAlias(mPkgName, "A");
+ assertNotNull(ks);
+
+ // and another
+ final InstallParams ip = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
try {
ks = pm.getKeySetByAlias(otherPkgName, "A");
- assertTrue(false); // should have thrown
- } catch (SecurityException e) {
+ assertNotNull(ks);
+ } finally {
+ cleanUpInstall(ip);
}
- cleanUpInstall(otherPkgName);
- ks = pm.getKeySetByAlias(mPkgName, "A");
- assertNotNull(ks);
}
public void testIsSignedBy() throws Exception {
@@ -3360,17 +3354,23 @@ public class PackageManagerTests extends AndroidTestCase {
assertFalse(pm.isSignedBy(mPkgName, new KeySet(new Binder())));
assertTrue(pm.isSignedBy(mPkgName, mSigningKS));
- installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+ final InstallParams ip1 = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
- assertFalse(pm.isSignedBy(otherPkgName, mDefinedKS));
- assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
- cleanUpInstall(otherPkgName);
+ try {
+ assertFalse(pm.isSignedBy(otherPkgName, mDefinedKS));
+ assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
+ } finally {
+ cleanUpInstall(ip1);
+ }
- installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
+ final InstallParams ip2 = installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
- assertTrue(pm.isSignedBy(otherPkgName, mDefinedKS));
- assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
- cleanUpInstall(otherPkgName);
+ try {
+ assertTrue(pm.isSignedBy(otherPkgName, mDefinedKS));
+ assertTrue(pm.isSignedBy(otherPkgName, mSigningKS));
+ } finally {
+ cleanUpInstall(ip2);
+ }
}
public void testIsSignedByExactly() throws Exception {
@@ -3402,17 +3402,23 @@ public class PackageManagerTests extends AndroidTestCase {
assertFalse(pm.isSignedByExactly(mPkgName, new KeySet(new Binder())));
assertTrue(pm.isSignedByExactly(mPkgName, mSigningKS));
- installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
+ final InstallParams ip1 = installFromRawResource("keysetApi.apk", R.raw.keyset_splat_api,
0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
- assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
- assertTrue(pm.isSignedByExactly(otherPkgName, mSigningKS));
- cleanUpInstall(otherPkgName);
+ try {
+ assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
+ assertTrue(pm.isSignedByExactly(otherPkgName, mSigningKS));
+ } finally {
+ cleanUpInstall(ip1);
+ }
- installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
+ final InstallParams ip2 = installFromRawResource("keysetApi.apk", R.raw.keyset_splata_api,
0, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
- assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
- assertFalse(pm.isSignedByExactly(otherPkgName, mSigningKS));
- cleanUpInstall(otherPkgName);
+ try {
+ assertFalse(pm.isSignedByExactly(otherPkgName, mDefinedKS));
+ assertFalse(pm.isSignedByExactly(otherPkgName, mSigningKS));
+ } finally {
+ cleanUpInstall(ip2);
+ }
}
@@ -3465,11 +3471,10 @@ public class PackageManagerTests extends AndroidTestCase {
int apk2 = APP2_CERT1_CERT2;
String apk1Name = "install1.apk";
String apk2Name = "install2.apk";
- InstallParams ip1 = null;
+ final InstallParams ip = installFromRawResource(apk1Name, apk1, 0, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
try {
- ip1 = installFromRawResource(apk1Name, apk1, 0, false,
- false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
PackageManager pm = mContext.getPackageManager();
// Delete app2
File filesDir = mContext.getFilesDir();
@@ -3480,12 +3485,10 @@ public class PackageManagerTests extends AndroidTestCase {
getPm().deletePackage(pkg.packageName, null, PackageManager.DELETE_ALL_USERS);
// Check signatures now
int match = mContext.getPackageManager().checkSignatures(
- ip1.pkg.packageName, pkg.packageName);
+ ip.pkg.packageName, pkg.packageName);
assertEquals(PackageManager.SIGNATURE_UNKNOWN_PACKAGE, match);
} finally {
- if (ip1 != null) {
- cleanUpInstall(ip1);
- }
+ cleanUpInstall(ip);
}
}
@@ -3493,14 +3496,10 @@ public class PackageManagerTests extends AndroidTestCase {
public void testInstallNoCertificates() throws Exception {
int apk1 = APP1_UNSIGNED;
String apk1Name = "install1.apk";
- InstallParams ip1 = null;
- try {
- installFromRawResource(apk1Name, apk1, 0, false,
- true, PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES,
- PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
- } finally {
- }
+ installFromRawResource(apk1Name, apk1, 0, false,
+ true, PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
}
/*
@@ -3766,35 +3765,43 @@ public class PackageManagerTests extends AndroidTestCase {
* Test that getInstalledPackages returns all the data specified in flags.
*/
public void testGetInstalledPackagesAll() throws Exception {
- int flags = PackageManager.GET_ACTIVITIES | PackageManager.GET_GIDS
+ final int flags = PackageManager.GET_ACTIVITIES | PackageManager.GET_GIDS
| PackageManager.GET_CONFIGURATIONS | PackageManager.GET_INSTRUMENTATION
| PackageManager.GET_PERMISSIONS | PackageManager.GET_PROVIDERS
| PackageManager.GET_RECEIVERS | PackageManager.GET_SERVICES
| PackageManager.GET_SIGNATURES | PackageManager.GET_UNINSTALLED_PACKAGES;
- List<PackageInfo> packages = getPm().getInstalledPackages(flags);
- assertNotNull("installed packages cannot be null", packages);
- assertTrue("installed packages cannot be empty", packages.size() > 0);
-
- PackageInfo packageInfo = null;
-
- // Find the package with all components specified in the AndroidManifest
- // to ensure no null values
- for (PackageInfo pi : packages) {
- if ("com.android.frameworks.coretests.install_complete_package_info"
- .equals(pi.packageName)) {
- packageInfo = pi;
- break;
+ final InstallParams ip =
+ installFromRawResource("install.apk", R.raw.install_complete_package_info,
+ 0 /*flags*/, false /*cleanUp*/, false /*fail*/, -1 /*result*/,
+ PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ try {
+ final List<PackageInfo> packages = getPm().getInstalledPackages(flags);
+ assertNotNull("installed packages cannot be null", packages);
+ assertTrue("installed packages cannot be empty", packages.size() > 0);
+
+ PackageInfo packageInfo = null;
+
+ // Find the package with all components specified in the AndroidManifest
+ // to ensure no null values
+ for (PackageInfo pi : packages) {
+ if ("com.android.frameworks.coretests.install_complete_package_info"
+ .equals(pi.packageName)) {
+ packageInfo = pi;
+ break;
+ }
}
+ assertNotNull("activities should not be null", packageInfo.activities);
+ assertNotNull("configPreferences should not be null", packageInfo.configPreferences);
+ assertNotNull("instrumentation should not be null", packageInfo.instrumentation);
+ assertNotNull("permissions should not be null", packageInfo.permissions);
+ assertNotNull("providers should not be null", packageInfo.providers);
+ assertNotNull("receivers should not be null", packageInfo.receivers);
+ assertNotNull("services should not be null", packageInfo.services);
+ assertNotNull("signatures should not be null", packageInfo.signatures);
+ } finally {
+ cleanUpInstall(ip);
}
- assertNotNull("activities should not be null", packageInfo.activities);
- assertNotNull("configPreferences should not be null", packageInfo.configPreferences);
- assertNotNull("instrumentation should not be null", packageInfo.instrumentation);
- assertNotNull("permissions should not be null", packageInfo.permissions);
- assertNotNull("providers should not be null", packageInfo.providers);
- assertNotNull("receivers should not be null", packageInfo.receivers);
- assertNotNull("services should not be null", packageInfo.services);
- assertNotNull("signatures should not be null", packageInfo.signatures);
}
/**
@@ -3802,38 +3809,52 @@ public class PackageManagerTests extends AndroidTestCase {
* flags when the GET_UNINSTALLED_PACKAGES flag is set.
*/
public void testGetUnInstalledPackagesAll() throws Exception {
- int flags = PackageManager.GET_UNINSTALLED_PACKAGES
+ final int flags = PackageManager.GET_UNINSTALLED_PACKAGES
| PackageManager.GET_ACTIVITIES | PackageManager.GET_GIDS
| PackageManager.GET_CONFIGURATIONS | PackageManager.GET_INSTRUMENTATION
| PackageManager.GET_PERMISSIONS | PackageManager.GET_PROVIDERS
| PackageManager.GET_RECEIVERS | PackageManager.GET_SERVICES
| PackageManager.GET_SIGNATURES | PackageManager.GET_UNINSTALLED_PACKAGES;
- List<PackageInfo> packages = getPm().getInstalledPackages(flags);
- assertNotNull("installed packages cannot be null", packages);
- assertTrue("installed packages cannot be empty", packages.size() > 0);
+ // first, install the package
+ final InstallParams ip =
+ installFromRawResource("install.apk", R.raw.install_complete_package_info,
+ 0 /*flags*/, false /*cleanUp*/, false /*fail*/, -1 /*result*/,
+ PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ try {
+ // then, remove it, keeping it's data around
+ final GenericReceiver receiver = new DeleteReceiver(ip.pkg.packageName);
+ invokeDeletePackage(ip.pkg.packageName, PackageManager.DELETE_KEEP_DATA, receiver);
+
+ final List<PackageInfo> packages = getPm().getInstalledPackages(flags);
+ assertNotNull("installed packages cannot be null", packages);
+ assertTrue("installed packages cannot be empty", packages.size() > 0);
- PackageInfo packageInfo = null;
+ PackageInfo packageInfo = null;
- // Find the package with all components specified in the AndroidManifest
- // to ensure no null values
- for (PackageInfo pi : packages) {
- if ("com.android.frameworks.coretests.install_complete_package_info"
- .equals(pi.packageName)) {
- packageInfo = pi;
- break;
+ // Find the package with all components specified in the AndroidManifest
+ // to ensure no null values
+ for (PackageInfo pi : packages) {
+ if ("com.android.frameworks.coretests.install_complete_package_info"
+ .equals(pi.packageName)) {
+ packageInfo = pi;
+ break;
+ }
}
+ assertNotNull("activities should not be null", packageInfo.activities);
+ assertNotNull("configPreferences should not be null", packageInfo.configPreferences);
+ assertNotNull("instrumentation should not be null", packageInfo.instrumentation);
+ assertNotNull("permissions should not be null", packageInfo.permissions);
+ assertNotNull("providers should not be null", packageInfo.providers);
+ assertNotNull("receivers should not be null", packageInfo.receivers);
+ assertNotNull("services should not be null", packageInfo.services);
+ assertNotNull("signatures should not be null", packageInfo.signatures);
+ } finally {
+ cleanUpInstall(ip);
}
- assertNotNull("activities should not be null", packageInfo.activities);
- assertNotNull("configPreferences should not be null", packageInfo.configPreferences);
- assertNotNull("instrumentation should not be null", packageInfo.instrumentation);
- assertNotNull("permissions should not be null", packageInfo.permissions);
- assertNotNull("providers should not be null", packageInfo.providers);
- assertNotNull("receivers should not be null", packageInfo.receivers);
- assertNotNull("services should not be null", packageInfo.services);
- assertNotNull("signatures should not be null", packageInfo.signatures);
}
+ @Suppress
public void testInstall_BadDex_CleanUp() throws Exception {
int retCode = PackageManager.INSTALL_FAILED_DEXOPT;
installFromRawResource("install.apk", R.raw.install_bad_dex, 0, true, true, retCode,
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index c5e2ae67affc..ddbdc87a4972 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -16,7 +16,11 @@
package android.widget;
+import static android.widget.espresso.TextViewActions.mouseDoubleClickOnTextAtIndex;
+import static android.widget.espresso.TextViewActions.mouseLongClickOnTextAtIndex;
+import static android.widget.espresso.TextViewActions.mouseDoubleClickAndDragOnText;
import static android.widget.espresso.TextViewActions.mouseDragOnText;
+import static android.widget.espresso.TextViewActions.mouseLongClickAndDragOnText;
import static android.widget.espresso.TextViewAssertions.hasSelection;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
@@ -62,4 +66,104 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2<
onView(withId(R.id.textview)).check(hasSelection("llo wor"));
}
+
+ @SmallTest
+ public void testSelectTextByLongClick() throws Exception {
+ getActivity();
+
+ final String helloWorld = "Hello world!";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
+
+ onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(0));
+ onView(withId(R.id.textview)).check(hasSelection("Hello"));
+
+ onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(
+ helloWorld.indexOf("world")));
+ onView(withId(R.id.textview)).check(hasSelection("world"));
+
+ onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(
+ helloWorld.indexOf("llo")));
+ onView(withId(R.id.textview)).check(hasSelection("Hello"));
+
+ onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(
+ helloWorld.indexOf("rld")));
+ onView(withId(R.id.textview)).check(hasSelection("world"));
+ }
+
+ @SmallTest
+ public void testSelectTextByDoubleClick() throws Exception {
+ getActivity();
+
+ final String helloWorld = "Hello world!";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
+
+ onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(0));
+ onView(withId(R.id.textview)).check(hasSelection("Hello"));
+
+ onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(
+ helloWorld.indexOf("world")));
+ onView(withId(R.id.textview)).check(hasSelection("world"));
+
+ onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(
+ helloWorld.indexOf("llo")));
+ onView(withId(R.id.textview)).check(hasSelection("Hello"));
+
+ onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(
+ helloWorld.indexOf("rld")));
+ onView(withId(R.id.textview)).check(hasSelection("world"));
+ }
+
+ @SmallTest
+ public void testSelectTextByDoubleClickAndDrag() throws Exception {
+ getActivity();
+
+ final String text = "abcd efg hijk lmn";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+ onView(withId(R.id.textview)).perform(
+ mouseDoubleClickAndDragOnText(text.indexOf("f"), text.indexOf("j")));
+ onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
+ }
+
+ @SmallTest
+ public void testSelectTextByDoubleClickAndDrag_reverse() throws Exception {
+ getActivity();
+
+ final String text = "abcd efg hijk lmn";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+ onView(withId(R.id.textview)).perform(
+ mouseDoubleClickAndDragOnText(text.indexOf("j"), text.indexOf("f")));
+ onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
+ }
+
+ @SmallTest
+ public void testSelectTextByLongPressAndDrag() throws Exception {
+ getActivity();
+
+ final String text = "abcd efg hijk lmn";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+ onView(withId(R.id.textview)).perform(
+ mouseLongClickAndDragOnText(text.indexOf("f"), text.indexOf("j")));
+ onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
+ }
+
+ @SmallTest
+ public void testSelectTextByLongPressAndDrag_reverse() throws Exception {
+ getActivity();
+
+ final String text = "abcd efg hijk lmn";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+ onView(withId(R.id.textview)).perform(
+ mouseLongClickAndDragOnText(text.indexOf("j"), text.indexOf("f")));
+ onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
+ }
}
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/core/tests/coretests/src/android/widget/espresso/DragAction.java b/core/tests/coretests/src/android/widget/espresso/DragAction.java
index 1132ce0c52ac..ce975688a894 100644
--- a/core/tests/coretests/src/android/widget/espresso/DragAction.java
+++ b/core/tests/coretests/src/android/widget/espresso/DragAction.java
@@ -91,6 +91,72 @@ public final class DragAction implements ViewAction {
},
/**
+ * Starts a drag with a mouse double click.
+ */
+ MOUSE_DOUBLE_CLICK {
+ private DownMotionPerformer downMotion = new DownMotionPerformer() {
+ @Override
+ @Nullable
+ public MotionEvent perform(
+ UiController uiController, float[] coordinates, float[] precision) {
+ return performDoubleTap(uiController, coordinates, precision);
+ }
+ };
+
+ @Override
+ public Status sendSwipe(
+ UiController uiController,
+ float[] startCoordinates, float[] endCoordinates, float[] precision) {
+ return sendLinearDrag(
+ uiController, downMotion, startCoordinates, endCoordinates, precision);
+ }
+
+ @Override
+ public String toString() {
+ return "mouse double click and drag to select";
+ }
+
+ @Override
+ public UiController wrapUiController(UiController uiController) {
+ return new MouseUiController(uiController);
+ }
+ },
+
+ /**
+ * Starts a drag with a mouse long click.
+ */
+ MOUSE_LONG_CLICK {
+ private DownMotionPerformer downMotion = new DownMotionPerformer() {
+ @Override
+ public MotionEvent perform(
+ UiController uiController, float[] coordinates, float[] precision) {
+ MotionEvent downEvent = MotionEvents.sendDown(
+ uiController, coordinates, precision)
+ .down;
+ return performLongPress(uiController, coordinates, precision);
+ }
+ };
+
+ @Override
+ public Status sendSwipe(
+ UiController uiController,
+ float[] startCoordinates, float[] endCoordinates, float[] precision) {
+ return sendLinearDrag(
+ uiController, downMotion, startCoordinates, endCoordinates, precision);
+ }
+
+ @Override
+ public String toString() {
+ return "mouse long click and drag to select";
+ }
+
+ @Override
+ public UiController wrapUiController(UiController uiController) {
+ return new MouseUiController(uiController);
+ }
+ },
+
+ /**
* Starts a drag with a tap.
*/
TAP {
@@ -127,15 +193,7 @@ public final class DragAction implements ViewAction {
@Override
public MotionEvent perform(
UiController uiController, float[] coordinates, float[] precision) {
- MotionEvent downEvent = MotionEvents.sendDown(
- uiController, coordinates, precision)
- .down;
- // Duration before a press turns into a long press.
- // Factor 1.5 is needed, otherwise a long press is not safely detected.
- // See android.test.TouchUtils longClickView
- long longPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
- uiController.loopMainThreadForAtLeast(longPressTimeout);
- return downEvent;
+ return performLongPress(uiController, coordinates, precision);
}
};
@@ -162,25 +220,7 @@ public final class DragAction implements ViewAction {
@Nullable
public MotionEvent perform(
UiController uiController, float[] coordinates, float[] precision) {
- MotionEvent downEvent = MotionEvents.sendDown(
- uiController, coordinates, precision)
- .down;
- try {
- if (!MotionEvents.sendUp(uiController, downEvent)) {
- String logMessage = "Injection of up event as part of the double tap " +
- "failed. Sending cancel event.";
- Log.d(TAG, logMessage);
- MotionEvents.sendCancel(uiController, downEvent);
- return null;
- }
-
- long doubleTapMinimumTimeout = ViewConfiguration.getDoubleTapMinTime();
- uiController.loopMainThreadForAtLeast(doubleTapMinimumTimeout);
-
- return MotionEvents.sendDown(uiController, coordinates, precision).down;
- } finally {
- downEvent.recycle();
- }
+ return performDoubleTap(uiController, coordinates, precision);
}
};
@@ -267,6 +307,43 @@ public final class DragAction implements ViewAction {
return res;
}
+ private static MotionEvent performLongPress(
+ UiController uiController, float[] coordinates, float[] precision) {
+ MotionEvent downEvent = MotionEvents.sendDown(
+ uiController, coordinates, precision)
+ .down;
+ // Duration before a press turns into a long press.
+ // Factor 1.5 is needed, otherwise a long press is not safely detected.
+ // See android.test.TouchUtils longClickView
+ long longPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
+ uiController.loopMainThreadForAtLeast(longPressTimeout);
+ return downEvent;
+ }
+
+ @Nullable
+ private static MotionEvent performDoubleTap(
+ UiController uiController, float[] coordinates, float[] precision) {
+ MotionEvent downEvent = MotionEvents.sendDown(
+ uiController, coordinates, precision)
+ .down;
+ try {
+ if (!MotionEvents.sendUp(uiController, downEvent)) {
+ String logMessage = "Injection of up event as part of the double tap " +
+ "failed. Sending cancel event.";
+ Log.d(TAG, logMessage);
+ MotionEvents.sendCancel(uiController, downEvent);
+ return null;
+ }
+
+ long doubleTapMinimumTimeout = ViewConfiguration.getDoubleTapMinTime();
+ uiController.loopMainThreadForAtLeast(doubleTapMinimumTimeout);
+
+ return MotionEvents.sendDown(uiController, coordinates, precision).down;
+ } finally {
+ downEvent.recycle();
+ }
+ }
+
@Override
public UiController wrapUiController(UiController uiController) {
return uiController;
diff --git a/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
new file mode 100644
index 000000000000..de640ca1053d
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.widget.espresso;
+
+import org.hamcrest.Matcher;
+
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.action.CoordinatesProvider;
+import android.support.test.espresso.action.GeneralClickAction;
+import android.support.test.espresso.action.PrecisionDescriber;
+import android.support.test.espresso.action.Tapper;
+import android.view.View;
+
+/**
+ * ViewAction for performing an click on View by a mouse.
+ */
+public final class MouseClickAction implements ViewAction {
+ private final GeneralClickAction mGeneralClickAction;
+
+ public MouseClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider,
+ PrecisionDescriber precisionDescriber) {
+ mGeneralClickAction = new GeneralClickAction(tapper, coordinatesProvider,
+ precisionDescriber);
+ }
+
+ @Override
+ public Matcher<View> getConstraints() {
+ return mGeneralClickAction.getConstraints();
+ }
+
+ @Override
+ public String getDescription() {
+ return mGeneralClickAction.getDescription();
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ mGeneralClickAction.perform(new MouseUiController(uiController), view);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
index 32abc861587d..32cc6d648556 100644
--- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
@@ -53,6 +53,21 @@ public final class TextViewActions {
}
/**
+ * Returns an action that clicks by mouse on text at an index on the TextView.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ *
+ * @param index The index of the TextView's text to click on.
+ */
+ public static ViewAction mouseClickOnTextAtIndex(int index) {
+ return actionWithAssertions(
+ new MouseClickAction(Tap.SINGLE, new TextCoordinates(index), Press.PINPOINT));
+ }
+
+ /**
* Returns an action that double-clicks on text at an index on the TextView.<br>
* <br>
* View constraints:
@@ -68,6 +83,21 @@ public final class TextViewActions {
}
/**
+ * Returns an action that double-clicks by mouse on text at an index on the TextView.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ *
+ * @param index The index of the TextView's text to double-click on.
+ */
+ public static ViewAction mouseDoubleClickOnTextAtIndex(int index) {
+ return actionWithAssertions(
+ new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.PINPOINT));
+ }
+
+ /**
* Returns an action that long presses on text at an index on the TextView.<br>
* <br>
* View constraints:
@@ -83,6 +113,21 @@ public final class TextViewActions {
}
/**
+ * Returns an action that long click by mouse on text at an index on the TextView.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ *
+ * @param index The index of the TextView's text to long click on.
+ */
+ public static ViewAction mouseLongClickOnTextAtIndex(int index) {
+ return actionWithAssertions(
+ new MouseClickAction(Tap.LONG, new TextCoordinates(index), Press.PINPOINT));
+ }
+
+ /**
* Returns an action that long presses then drags on text from startIndex to endIndex on the
* TextView.<br>
* <br>
@@ -148,6 +193,50 @@ public final class TextViewActions {
TextView.class));
}
+ /**
+ * Returns an action that double click then drags by mouse on text from startIndex to endIndex
+ * on the TextView.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ *
+ * @param startIndex The index of the TextView's text to start a drag from
+ * @param endIndex The index of the TextView's text to end the drag at
+ */
+ public static ViewAction mouseDoubleClickAndDragOnText(int startIndex, int endIndex) {
+ return actionWithAssertions(
+ new DragAction(
+ DragAction.Drag.MOUSE_DOUBLE_CLICK,
+ new TextCoordinates(startIndex),
+ new TextCoordinates(endIndex),
+ Press.PINPOINT,
+ TextView.class));
+ }
+
+ /**
+ * Returns an action that long click then drags by mouse on text from startIndex to endIndex
+ * on the TextView.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ *
+ * @param startIndex The index of the TextView's text to start a drag from
+ * @param endIndex The index of the TextView's text to end the drag at
+ */
+ public static ViewAction mouseLongClickAndDragOnText(int startIndex, int endIndex) {
+ return actionWithAssertions(
+ new DragAction(
+ DragAction.Drag.MOUSE_LONG_CLICK,
+ new TextCoordinates(startIndex),
+ new TextCoordinates(endIndex),
+ Press.PINPOINT,
+ TextView.class));
+ }
+
public enum Handle {
SELECTION_START,
SELECTION_END,
@@ -226,9 +315,25 @@ public final class TextViewActions {
(new TextCoordinates(mIndex)).calculateCoordinates(mTextView);
final Rect bounds = new Rect();
view.getBoundsOnScreen(bounds);
- final float diffX = bounds.centerX() - currentCoordinates[0];
+ final Rect visibleDisplayBounds = new Rect();
+ mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds);
+ visibleDisplayBounds.right -= 1;
+ visibleDisplayBounds.bottom -= 1;
+ if (!visibleDisplayBounds.intersect(bounds)) {
+ throw new PerformException.Builder()
+ .withActionDescription(mActionDescription
+ + " The handle is entirely out of the visible display frame of"
+ + "the TextView's window.")
+ .withViewDescription(HumanReadables.describe(view))
+ .build();
+ }
+ final float dragPointX = Math.max(Math.min(bounds.centerX(),
+ visibleDisplayBounds.right), visibleDisplayBounds.left);
+ final float diffX = dragPointX - currentCoordinates[0];
final float verticalOffset = bounds.height() * 0.7f;
- float diffY = bounds.top + verticalOffset - currentCoordinates[1];
+ final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset,
+ visibleDisplayBounds.bottom), visibleDisplayBounds.top);
+ float diffY = dragPointY - currentCoordinates[1];
if (currentLine > targetLine) {
diffY -= mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER;
} else if (currentLine < targetLine) {
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index ab375194b03a..51019cc1d46c 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -126,6 +126,7 @@
<assign-permission name="android.permission.WAKE_LOCK" uid="media" />
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="media" />
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="media" />
+ <assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="media" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" />
diff --git a/data/sounds/AllAudio.mk b/data/sounds/AllAudio.mk
index f6d8ee962fcc..edfd3800600c 100644
--- a/data/sounds/AllAudio.mk
+++ b/data/sounds/AllAudio.mk
@@ -228,6 +228,7 @@ PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
$(LOCAL_PATH)/effects/ogg/Trusted_48k.ogg:system/media/audio/ui/Trusted.ogg \
$(LOCAL_PATH)/effects/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/ogg/WirelessChargingStarted.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \
$(LOCAL_PATH)/effects/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
diff --git a/data/sounds/AudioPackage10.mk b/data/sounds/AudioPackage10.mk
index 5a5eea612c4d..c5222afcc2f7 100644
--- a/data/sounds/AudioPackage10.mk
+++ b/data/sounds/AudioPackage10.mk
@@ -1,9 +1,9 @@
#
# Audio Package 10 - Mako
-#
+#
# Include this file in a product makefile to include these audio files
#
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -23,6 +23,7 @@ PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/material/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/material/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/material/ogg/LowBattery_48k.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage11.mk b/data/sounds/AudioPackage11.mk
index 0f85b33e8940..43c83b99b11f 100644
--- a/data/sounds/AudioPackage11.mk
+++ b/data/sounds/AudioPackage11.mk
@@ -1,9 +1,9 @@
#
# Audio Package 11 - Razor
-#
+#
# Include this file in a product makefile to include these audio files
#
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -23,6 +23,7 @@ PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/material/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/material/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/material/ogg/LowBattery_48k.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage12.mk b/data/sounds/AudioPackage12.mk
index 42513324d3d0..cd4d35b4d3fc 100644
--- a/data/sounds/AudioPackage12.mk
+++ b/data/sounds/AudioPackage12.mk
@@ -13,7 +13,7 @@ NOTIFICATION_FILES := Ariel Ceres Carme Elara Europa Iapetus Io Rhea Salacia Tit
RINGTONE_FILES := Callisto Dione Ganymede Luna Oberon Phobos Sedna Titania Triton Umbriel
EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
camera_focus Dock Undock Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted
+MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted VideoStop
PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
$(LOCAL_PATH)/alarms/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
diff --git a/data/sounds/AudioPackage12_48.mk b/data/sounds/AudioPackage12_48.mk
index 70e68d3d5202..80758f495035 100644
--- a/data/sounds/AudioPackage12_48.mk
+++ b/data/sounds/AudioPackage12_48.mk
@@ -13,7 +13,7 @@ NOTIFICATION_FILES := Ariel Ceres Carme Elara Europa Iapetus Io Rhea Salacia Tit
RINGTONE_FILES := Callisto Dione Ganymede Luna Oberon Phobos Sedna Titania Triton Umbriel
EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted
+MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted VideoStop
# Alarms not yet available in 48 kHz
PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
diff --git a/data/sounds/AudioPackage13.mk b/data/sounds/AudioPackage13.mk
index cec7280aa878..d33a4aff22b8 100644
--- a/data/sounds/AudioPackage13.mk
+++ b/data/sounds/AudioPackage13.mk
@@ -14,7 +14,7 @@ RINGTONE_FILES := Atria Callisto Dione Ganymede Luna Oberon Phobos Pyxis Sedna T
Umbriel
EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
camera_focus Dock Undock Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery
+MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery VideoStop
PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
$(LOCAL_PATH)/alarms/material/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
diff --git a/data/sounds/AudioPackage13_48.mk b/data/sounds/AudioPackage13_48.mk
index d1b17c800ef7..9c320ae8855e 100644
--- a/data/sounds/AudioPackage13_48.mk
+++ b/data/sounds/AudioPackage13_48.mk
@@ -14,7 +14,7 @@ RINGTONE_FILES := Atria Callisto Dione Ganymede Luna Oberon Phobos Pyxis Sedna T
Umbriel
EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery
+MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery VideoStop
PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
$(LOCAL_PATH)/alarms/material/ogg/$(fn)_48k.ogg:system/media/audio/alarms/$(fn).ogg)
diff --git a/data/sounds/AudioPackage2.mk b/data/sounds/AudioPackage2.mk
index ba9d7e22e0d0..40319c4505ec 100644
--- a/data/sounds/AudioPackage2.mk
+++ b/data/sounds/AudioPackage2.mk
@@ -1,11 +1,11 @@
#
# Audio Package 2
-#
+#
# Include this file in a product makefile to include these audio files
#
# This is a larger package of sounds than the 1.0 release for devices
# that have larger internal flash.
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -34,6 +34,7 @@ PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
$(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage3.mk b/data/sounds/AudioPackage3.mk
index 5bfeb4272448..a05de723c8f1 100644
--- a/data/sounds/AudioPackage3.mk
+++ b/data/sounds/AudioPackage3.mk
@@ -1,11 +1,11 @@
#
# Audio Package 3
-#
+#
# Include this file in a product makefile to include these audio files
#
# This is a larger package of sounds than the 1.0 release for devices
# that have larger internal flash.
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -34,6 +34,7 @@ PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
$(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage4.mk b/data/sounds/AudioPackage4.mk
index 43dbe207edd5..d376a2d91f6c 100644
--- a/data/sounds/AudioPackage4.mk
+++ b/data/sounds/AudioPackage4.mk
@@ -1,11 +1,11 @@
#
# Audio Package 4
-#
+#
# Include this file in a product makefile to include these audio files
#
# This is a larger package of sounds than the 1.0 release for devices
# that have larger internal flash.
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -39,6 +39,7 @@ PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
$(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage5.mk b/data/sounds/AudioPackage5.mk
index fbbb16a3572e..72384c8b9146 100644
--- a/data/sounds/AudioPackage5.mk
+++ b/data/sounds/AudioPackage5.mk
@@ -1,9 +1,9 @@
#
# Audio Package 5 - Crespo/Soju
-#
+#
# Include this file in a product makefile to include these audio files
#
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -20,6 +20,7 @@ PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage6.mk b/data/sounds/AudioPackage6.mk
index c843fdc4e976..5413704f0fac 100644
--- a/data/sounds/AudioPackage6.mk
+++ b/data/sounds/AudioPackage6.mk
@@ -1,9 +1,9 @@
#
# Audio Package 6 - Trygon/Stingray
-#
+#
# Include this file in a product makefile to include these audio files
#
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -19,6 +19,7 @@ PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage7.mk b/data/sounds/AudioPackage7.mk
index ce82651bc876..e4763be2d4aa 100644
--- a/data/sounds/AudioPackage7.mk
+++ b/data/sounds/AudioPackage7.mk
@@ -1,9 +1,9 @@
#
# Audio Package 7 - Tuna
-#
+#
# Include this file in a product makefile to include these audio files
#
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -21,6 +21,7 @@ PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage7alt.mk b/data/sounds/AudioPackage7alt.mk
index db468f316538..30e617316ef4 100644
--- a/data/sounds/AudioPackage7alt.mk
+++ b/data/sounds/AudioPackage7alt.mk
@@ -21,6 +21,7 @@ PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
$(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage8.mk b/data/sounds/AudioPackage8.mk
index 4112c18c099e..b38e62dec6ad 100644
--- a/data/sounds/AudioPackage8.mk
+++ b/data/sounds/AudioPackage8.mk
@@ -1,9 +1,9 @@
#
# Audio Package 7 - Tuna
-#
+#
# Include this file in a product makefile to include these audio files
#
-#
+#
LOCAL_PATH:= frameworks/base/data/sounds
@@ -23,6 +23,7 @@ PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage9.mk b/data/sounds/AudioPackage9.mk
index 1b430c01ddcd..dbe1350f304a 100644
--- a/data/sounds/AudioPackage9.mk
+++ b/data/sounds/AudioPackage9.mk
@@ -23,6 +23,7 @@ PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/OriginalAudio.mk b/data/sounds/OriginalAudio.mk
index e1ca24b0681f..f68375205d89 100644
--- a/data/sounds/OriginalAudio.mk
+++ b/data/sounds/OriginalAudio.mk
@@ -1,8 +1,8 @@
#
# Original audio package that shipped on G1
-#
+#
# This file is included from core.mk so that all devices will have these sounds
-#
+#
# TODO: Clean up for future releases
#
@@ -32,6 +32,7 @@ PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/effects/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+ $(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
$(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:system/media/audio/ringtones/BeatPlucker.ogg \
$(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:system/media/audio/notifications/CaffeineSnake.ogg
diff --git a/data/sounds/effects/VideoStop.ogg b/data/sounds/effects/VideoStop.ogg
new file mode 100644
index 000000000000..1450522f6d93
--- /dev/null
+++ b/data/sounds/effects/VideoStop.ogg
Binary files differ
diff --git a/data/sounds/effects/VideoStop.wav b/data/sounds/effects/VideoStop.wav
new file mode 100644
index 000000000000..5809d93c011d
--- /dev/null
+++ b/data/sounds/effects/VideoStop.wav
Binary files differ
diff --git a/data/sounds/effects/material/ogg/VideoStop.ogg b/data/sounds/effects/material/ogg/VideoStop.ogg
new file mode 100644
index 000000000000..e98fabc054f7
--- /dev/null
+++ b/data/sounds/effects/material/ogg/VideoStop.ogg
Binary files differ
diff --git a/data/sounds/effects/material/ogg/VideoStop_48k.ogg b/data/sounds/effects/material/ogg/VideoStop_48k.ogg
new file mode 100644
index 000000000000..b1eb780237af
--- /dev/null
+++ b/data/sounds/effects/material/ogg/VideoStop_48k.ogg
Binary files differ
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 95ae72e0c6f8..5acc1a3e09dd 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -484,7 +484,7 @@ public class Canvas {
*
* @param bounds The maximum size the offscreen bitmap needs to be
* (in local coordinates)
- * @param alpha The alpha to apply to the offscreen when when it is
+ * @param alpha The alpha to apply to the offscreen when it is
drawn during restore()
* @param saveFlags see _SAVE_FLAG constants, generally {@link #ALL_SAVE_FLAG} is recommended
* for performance reasons.
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/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 1c2c9406621d..f5b7a2e44d71 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -36,6 +36,7 @@ import android.os.Process;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.OsConstants;
@@ -1260,6 +1261,13 @@ public class MediaPlayer implements SubtitleController.Listener
*/
public void setWakeMode(Context context, int mode) {
boolean washeld = false;
+
+ /* Disable persistant wakelocks in media player based on property */
+ if (SystemProperties.getBoolean("audio.offload.ignore_setawake", false) == true) {
+ Log.w(TAG, "IGNORING setWakeMode " + mode);
+ return;
+ }
+
if (mWakeLock != null) {
if (mWakeLock.isHeld()) {
washeld = true;
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index 41b8ab2d3c40..be9fb474db00 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -240,7 +240,7 @@ public final class MediaBrowser {
/**
* Gets the root id.
* <p>
- * Note that the root id may become invalid or change when when the
+ * Note that the root id may become invalid or change when the
* browser is disconnected.
* </p>
*
@@ -270,7 +270,7 @@ public final class MediaBrowser {
/**
* Gets the media session token associated with the media browser.
* <p>
- * Note that the session token may become invalid or change when when the
+ * Note that the session token may become invalid or change when the
* browser is disconnected.
* </p>
*
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 090be88561c4..70d651f2c24a 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -187,6 +187,17 @@ bool SoundPool::startThreads()
return mDecodeThread != NULL;
}
+sp<Sample> SoundPool::findSample(int sampleID)
+{
+ Mutex::Autolock lock(&mLock);
+ return findSample_l(sampleID);
+}
+
+sp<Sample> SoundPool::findSample_l(int sampleID)
+{
+ return mSamples.valueFor(sampleID);
+}
+
SoundChannel* SoundPool::findChannel(int channelID)
{
for (int i = 0; i < mMaxChannels; ++i) {
@@ -211,18 +222,21 @@ int SoundPool::load(int fd, int64_t offset, int64_t length, int priority __unuse
{
ALOGV("load: fd=%d, offset=%" PRId64 ", length=%" PRId64 ", priority=%d",
fd, offset, length, priority);
- Mutex::Autolock lock(&mLock);
- sp<Sample> sample = new Sample(++mNextSampleID, fd, offset, length);
- mSamples.add(sample->sampleID(), sample);
- doLoad(sample);
- return sample->sampleID();
-}
-
-void SoundPool::doLoad(sp<Sample>& sample)
-{
- ALOGV("doLoad: loading sample sampleID=%d", sample->sampleID());
- sample->startLoad();
- mDecodeThread->loadSample(sample->sampleID());
+ int sampleID;
+ {
+ Mutex::Autolock lock(&mLock);
+ sampleID = ++mNextSampleID;
+ sp<Sample> sample = new Sample(sampleID, fd, offset, length);
+ mSamples.add(sampleID, sample);
+ sample->startLoad();
+ }
+ // mDecodeThread->loadSample() must be called outside of mLock.
+ // mDecodeThread->loadSample() may block on mDecodeThread message queue space;
+ // the message queue emptying may block on SoundPool::findSample().
+ //
+ // It theoretically possible that sample loads might decode out-of-order.
+ mDecodeThread->loadSample(sampleID);
+ return sampleID;
}
bool SoundPool::unload(int sampleID)
@@ -237,7 +251,6 @@ int SoundPool::play(int sampleID, float leftVolume, float rightVolume,
{
ALOGV("play sampleID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f",
sampleID, leftVolume, rightVolume, priority, loop, rate);
- sp<Sample> sample;
SoundChannel* channel;
int channelID;
@@ -247,7 +260,7 @@ int SoundPool::play(int sampleID, float leftVolume, float rightVolume,
return 0;
}
// is sample ready?
- sample = findSample(sampleID);
+ sp<Sample> sample(findSample_l(sampleID));
if ((sample == 0) || (sample->state() != Sample::READY)) {
ALOGW(" sample %d not READY", sampleID);
return 0;
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index 4aacf5335531..98d2fa25207c 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -180,6 +180,7 @@ public:
// called from SoundPoolThread
void sampleLoaded(int sampleID);
+ sp<Sample> findSample(int sampleID);
// called from AudioTrack thread
void done_l(SoundChannel* channel);
@@ -191,8 +192,7 @@ public:
private:
SoundPool() {} // no default constructor
bool startThreads();
- void doLoad(sp<Sample>& sample);
- sp<Sample> findSample(int sampleID) { return mSamples.valueFor(sampleID); }
+ sp<Sample> findSample_l(int sampleID);
SoundChannel* findChannel (int channelID);
SoundChannel* findNextChannel (int channelID);
SoundChannel* allocateChannel_l(int priority, int sampleID);
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 595928a373f9..60f5d6007ec1 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -39,7 +39,7 @@
</activity>
<activity
- android:name=".ManageRootActivity"
+ android:name=".DownloadsActivity"
android:theme="@style/DocumentsFullScreenTheme"
android:icon="@drawable/ic_doc_text">
<intent-filter>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 91ac0334d4be..abb08f599747 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -260,8 +260,7 @@ public abstract class BaseActivity extends Activity {
} else if (id == R.id.menu_settings) {
final RootInfo root = getCurrentRoot();
final Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS);
- intent.setDataAndType(DocumentsContract.buildRootUri(root.authority, root.rootId),
- DocumentsContract.Root.MIME_TYPE_ITEM);
+ intent.setDataAndType(root.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
startActivity(intent);
return true;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index 55e2f441a882..b99c8063843c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -39,13 +39,15 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
import android.support.design.widget.Snackbar;
import android.text.format.DateUtils;
import android.util.Log;
-import android.widget.Toast;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.model.RootInfo;
import libcore.io.IoUtils;
@@ -72,6 +74,12 @@ public class CopyService extends IntentService {
// TODO: Move it to a shared file when more operations are implemented.
public static final int FAILURE_COPY = 1;
+ // Parameters of the copy job. Requests to an IntentService are serialized so this code only
+ // needs to deal with one job at a time.
+ // NOTE: This must be declared by concrete type as the concrete type
+ // is required by putParcelableArrayListExtra.
+ private final ArrayList<DocumentInfo> mFailedFiles = new ArrayList<>();
+
private PowerManager mPowerManager;
private NotificationManager mNotificationManager;
@@ -80,9 +88,6 @@ public class CopyService extends IntentService {
// Jobs are serialized but a job ID is used, to avoid mixing up cancellation requests.
private String mJobId;
private volatile boolean mIsCancelled;
- // Parameters of the copy job. Requests to an IntentService are serialized so this code only
- // needs to deal with one job at a time.
- private final ArrayList<DocumentInfo> mFailedFiles;
private long mBatchSize;
private long mBytesCopied;
private long mStartTime;
@@ -97,10 +102,11 @@ public class CopyService extends IntentService {
private ContentProviderClient mSrcClient;
private ContentProviderClient mDstClient;
+ // For testing only.
+ @Nullable private TestOnlyListener mJobFinishedListener;
+
public CopyService() {
super("CopyService");
-
- mFailedFiles = new ArrayList<DocumentInfo>();
}
/**
@@ -115,7 +121,11 @@ public class CopyService extends IntentService {
final Resources res = activity.getResources();
final Intent copyIntent = new Intent(activity, CopyService.class);
copyIntent.putParcelableArrayListExtra(
- EXTRA_SRC_LIST, new ArrayList<DocumentInfo>(srcDocs));
+ EXTRA_SRC_LIST,
+ // Don't create a copy unless absolutely necessary :)
+ srcDocs instanceof ArrayList
+ ? (ArrayList<DocumentInfo>) srcDocs
+ : new ArrayList<DocumentInfo>(srcDocs));
copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) dstStack);
copyIntent.putExtra(EXTRA_TRANSFER_MODE, mode);
@@ -198,6 +208,11 @@ public class CopyService extends IntentService {
.setAutoCancel(true);
mNotificationManager.notify(mJobId, 0, errorBuilder.build());
}
+
+ if (mJobFinishedListener != null) {
+ mJobFinishedListener.onFinished(mFailedFiles);
+ }
+
if (DEBUG) Log.d(TAG, "Done cleaning up");
}
}
@@ -269,6 +284,26 @@ public class CopyService extends IntentService {
}
/**
+ * Sets a callback to be run when the next run job is finished.
+ * This is test ONLY instrumentation. The alternative is for us to add
+ * broadcast intents SOLELY for the purpose of testing.
+ * @param listener
+ */
+ @VisibleForTesting
+ void addFinishedListener(TestOnlyListener listener) {
+ this.mJobFinishedListener = listener;
+
+ }
+
+ /**
+ * Only used for testing. Is that obvious enough?
+ */
+ @VisibleForTesting
+ interface TestOnlyListener {
+ void onFinished(List<DocumentInfo> failed);
+ }
+
+ /**
* Calculates the cumulative size of all the documents in the list. Directories are recursed
* into and totaled up.
*
@@ -279,7 +314,7 @@ public class CopyService extends IntentService {
private long calculateFileSizes(List<DocumentInfo> srcs) throws RemoteException {
long result = 0;
for (DocumentInfo src : srcs) {
- if (Document.MIME_TYPE_DIR.equals(src.mimeType)) {
+ if (src.isDirectory()) {
// Directories need to be recursed into.
result += calculateFileSizesHelper(src.derivedUri);
} else {
@@ -412,8 +447,21 @@ public class CopyService extends IntentService {
*/
private void copy(DocumentInfo srcInfo, DocumentInfo dstDirInfo, int mode)
throws RemoteException {
- if (DEBUG) Log.d(TAG, "Copying " + srcInfo.displayName + " (" + srcInfo.derivedUri + ")" +
- " to " + dstDirInfo.displayName + " (" + dstDirInfo.derivedUri + ")");
+
+ String opDesc = mode == TRANSFER_MODE_COPY ? "copy" : "move";
+
+ // Guard unsupported recursive operation.
+ if (dstDirInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstDirInfo)) {
+ if (DEBUG) Log.d(TAG,
+ "Skipping recursive " + opDesc + " of directory " + dstDirInfo.derivedUri);
+ mFailedFiles.add(srcInfo);
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG,
+ "Performing " + opDesc + " of " + srcInfo.displayName
+ + " (" + srcInfo.derivedUri + ")" + " to " + dstDirInfo.displayName
+ + " (" + dstDirInfo.derivedUri + ")");
// When copying within the same provider, try to use optimized copying and moving.
// If not supported, then fallback to byte-by-byte copy/move.
@@ -450,7 +498,7 @@ public class CopyService extends IntentService {
return;
}
- if (Document.MIME_TYPE_DIR.equals(srcInfo.mimeType)) {
+ if (srcInfo.isDirectory()) {
copyDirectoryHelper(srcInfo.derivedUri, dstUri, mode);
} else {
copyFileHelper(srcInfo.derivedUri, dstUri, mode);
@@ -458,6 +506,17 @@ public class CopyService extends IntentService {
}
/**
+ * Returns true if {@code doc} is a descendant of {@code parentDoc}.
+ */
+ boolean isDescendentOf(DocumentInfo doc, DocumentInfo parentDoc) throws RemoteException {
+ if (parentDoc.isDirectory() && doc.authority.equals(parentDoc.authority)) {
+ return DocumentsContract.isChildDocument(
+ mDstClient, doc.derivedUri, parentDoc.derivedUri);
+ }
+ return false;
+ }
+
+ /**
* Handles recursion into a directory and copying its contents. Note that in linux terms, this
* does the equivalent of "cp src/* dst", not "cp -r src dst".
*
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index c6425a6e41f2..f3c3f2fcabf0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -32,6 +32,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
+import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.util.Log;
import android.view.KeyEvent;
@@ -83,8 +84,10 @@ public class CreateDirectoryFragment extends DialogFragment {
editText.setOnEditorActionListener(
new OnEditorActionListener() {
@Override
- public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
+ public boolean onEditorAction(
+ TextView view, int actionId, @Nullable KeyEvent event) {
+ if (event != null
+ && event.getKeyCode() == KeyEvent.KEYCODE_ENTER
&& event.hasNoModifiers()) {
createDirectory(editText.getText().toString());
dialog.dismiss();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
index 3045fa847b26..f224343f1ed7 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/ManageRootActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
@@ -50,8 +50,11 @@ import com.android.internal.util.Preconditions;
import java.util.Arrays;
import java.util.List;
-public class ManageRootActivity extends BaseActivity {
- private static final String TAG = "ManageRootsActivity";
+// Let's face it. MANAGE_ROOT is used almost exclusively
+// for downloads, and is specialized for this purpose.
+// So it is now thusly christened.
+public class DownloadsActivity extends BaseActivity {
+ private static final String TAG = "DownloadsActivity";
private Toolbar mToolbar;
private Spinner mToolbarStack;
@@ -59,7 +62,7 @@ public class ManageRootActivity extends BaseActivity {
private ItemSelectedListener mStackListener;
private BaseAdapter mStackAdapter;
- public ManageRootActivity() {
+ public DownloadsActivity() {
super(R.layout.manage_roots_activity, TAG);
}
@@ -250,7 +253,7 @@ public class ManageRootActivity extends BaseActivity {
finish();
}
- public static ManageRootActivity get(Fragment fragment) {
- return (ManageRootActivity) fragment.getActivity();
+ public static DownloadsActivity get(Fragment fragment) {
+ return (DownloadsActivity) fragment.getActivity();
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java b/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
index 120f6106a7a0..23074f072c99 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
@@ -37,7 +37,6 @@ public class FailureDialogFragment extends DialogFragment
implements DialogInterface.OnClickListener {
private static final String TAG = "FailureDialogFragment";
- private int mFailure;
private int mTransferMode;
private ArrayList<DocumentInfo> mFailedSrcList;
@@ -75,7 +74,6 @@ public class FailureDialogFragment extends DialogFragment
public Dialog onCreateDialog(Bundle inState) {
super.onCreate(inState);
- mFailure = getArguments().getInt(CopyService.EXTRA_FAILURE);
mTransferMode = getArguments().getInt(CopyService.EXTRA_TRANSFER_MODE);
mFailedSrcList = getArguments().getParcelableArrayList(CopyService.EXTRA_SRC_LIST);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index b0421b02bf9e..be3013b4ef72 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -741,45 +741,45 @@ public class DirectoryFragment extends Fragment {
Selection selection = mSelectionManager.getSelection(new Selection());
- final int id = item.getItemId();
- if (id == R.id.menu_open) {
- openDocuments(selection);
- mode.finish();
- return true;
+ switch (item.getItemId()) {
+ case R.id.menu_open:
+ openDocuments(selection);
+ mode.finish();
+ return true;
- } else if (id == R.id.menu_share) {
- shareDocuments(selection);
- mode.finish();
- return true;
+ case R.id.menu_share:
+ shareDocuments(selection);
+ mode.finish();
+ return true;
- } else if (id == R.id.menu_delete) {
- // Exit selection mode first, so we avoid deselecting deleted documents.
- mode.finish();
- deleteDocuments(selection);
- return true;
+ case R.id.menu_delete:
+ // Exit selection mode first, so we avoid deselecting deleted documents.
+ mode.finish();
+ deleteDocuments(selection);
+ return true;
- } else if (id == R.id.menu_copy_to) {
- transferDocuments(selection, CopyService.TRANSFER_MODE_COPY);
- mode.finish();
- return true;
+ case R.id.menu_copy_to:
+ transferDocuments(selection, CopyService.TRANSFER_MODE_COPY);
+ mode.finish();
+ return true;
- } else if (id == R.id.menu_move_to) {
- // Exit selection mode first, so we avoid deselecting deleted documents.
- mode.finish();
- transferDocuments(selection, CopyService.TRANSFER_MODE_MOVE);
- return true;
+ case R.id.menu_move_to:
+ // Exit selection mode first, so we avoid deselecting deleted documents.
+ mode.finish();
+ transferDocuments(selection, CopyService.TRANSFER_MODE_MOVE);
+ return true;
- } else if (id == R.id.menu_copy_to_clipboard) {
- copySelectionToClipboard(selection);
- mode.finish();
- return true;
+ case R.id.menu_copy_to_clipboard:
+ copySelectionToClipboard(selection);
+ return true;
- } else if (id == R.id.menu_select_all) {
- selectAllFiles();
- return true;
+ case R.id.menu_select_all:
+ selectAllFiles();
+ return true;
- } else {
- return false;
+ default:
+ if (DEBUG) Log.d(TAG, "Unhandled menu item selected: " + item);
+ return false;
}
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
index 38d3805ab188..ef6d2c9e42bc 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -176,7 +176,6 @@ public abstract class FragmentTuner {
final MenuItem moveTo = menu.findItem(R.id.menu_move_to);
open.setVisible(false);
- share.setVisible(false);
delete.setVisible(canDelete);
copyTo.setVisible(true);
copyTo.setEnabled(true);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index cc981e1eaaad..dfdc705a16c8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -25,6 +25,7 @@ import android.os.Parcelable;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsProvider;
+import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import com.android.documentsui.DocumentsApplication;
@@ -204,13 +205,18 @@ public class DocumentInfo implements Durable, Parcelable {
}
}
- private void deriveFields() {
+ @VisibleForTesting
+ void deriveFields() {
derivedUri = DocumentsContract.buildDocumentUri(authority, documentId);
}
@Override
public String toString() {
- return "Document{docId=" + documentId + ", name=" + displayName + "}";
+ return "Document{"
+ + "docId=" + documentId
+ + ", name=" + displayName
+ + ", isDirectory=" + isDirectory()
+ + "}";
}
public boolean isCreateSupported() {
@@ -237,6 +243,22 @@ public class DocumentInfo implements Durable, Parcelable {
return (flags & Document.FLAG_DIR_HIDE_GRID_TITLES) != 0;
}
+ public int hashCode() {
+ return derivedUri.hashCode() + mimeType.hashCode();
+ }
+
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (!(other instanceof DocumentInfo)) {
+ return false;
+ }
+
+ DocumentInfo that = (DocumentInfo) other;
+ // Uri + mime type should be totally unique.
+ return derivedUri.equals(that.derivedUri) && mimeType.equals(that.mimeType);
+ }
+
public static String getCursorString(Cursor cursor, String columnName) {
final int index = cursor.getColumnIndex(columnName);
return (index != -1) ? cursor.getString(index) : null;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index ae5644d7fc3b..4caa891fa24e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -23,8 +23,10 @@ import static com.android.documentsui.model.DocumentInfo.getCursorString;
import android.content.Context;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Root;
import android.text.TextUtils;
@@ -195,6 +197,10 @@ public class RootInfo implements Durable, Parcelable {
}
}
+ public Uri getUri() {
+ return DocumentsContract.buildRootUri(authority, rootId);
+ }
+
public boolean isRecents() {
return authority == null && rootId == null;
}
@@ -238,11 +244,6 @@ public class RootInfo implements Durable, Parcelable {
|| derivedType == TYPE_RECENTS || derivedType == TYPE_DOWNLOADS;
}
- @Override
- public String toString() {
- return "Root{authority=" + authority + ", rootId=" + rootId + ", title=" + title + "}";
- }
-
public Drawable loadIcon(Context context) {
if (derivedIcon != 0) {
return context.getDrawable(derivedIcon);
@@ -283,6 +284,11 @@ public class RootInfo implements Durable, Parcelable {
return Objects.hash(authority, rootId);
}
+ @Override
+ public String toString() {
+ return "Root{authority=" + authority + ", rootId=" + rootId + ", title=" + title + "}";
+ }
+
public String getDirectoryString() {
return !TextUtils.isEmpty(summary) ? summary : title;
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java
index 369ab7dc59ea..079d59914de6 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java
@@ -28,12 +28,10 @@ import android.net.Uri;
import android.os.Parcelable;
import android.os.RemoteException;
import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Document;
import android.test.MoreAsserts;
import android.test.ServiceTestCase;
import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import com.android.documentsui.model.DocumentInfo;
@@ -48,6 +46,7 @@ import libcore.io.Streams;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -55,9 +54,9 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@MediumTest
-public class CopyTest extends ServiceTestCase<CopyService> {
+public class CopyServiceTest extends ServiceTestCase<CopyService> {
- public CopyTest() {
+ public CopyServiceTest() {
super(CopyService.class);
}
@@ -72,11 +71,13 @@ public class CopyTest extends ServiceTestCase<CopyService> {
private DocumentsProviderHelper mDocHelper;
private StubProvider mStorage;
private Context mSystemContext;
+ private CopyJobListener mListener;
@Override
protected void setUp() throws Exception {
super.setUp();
+ mListener = new CopyJobListener();
setupTestContext();
mClient = mResolver.acquireContentProviderClient(AUTHORITY);
@@ -84,6 +85,8 @@ public class CopyTest extends ServiceTestCase<CopyService> {
mStorage.clearCacheAndBuildRoots();
mDocHelper = new DocumentsProviderHelper(AUTHORITY, mClient);
+
+ assertDestFileCount(0);
}
@Override
@@ -97,15 +100,13 @@ public class CopyTest extends ServiceTestCase<CopyService> {
Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
"The five boxing wizards jump quickly".getBytes());
- assertDstFileCountEquals(0);
-
startService(createCopyIntent(Lists.newArrayList(testFile)));
// 2 operations: file creation, then writing data.
mResolver.waitForChanges(2);
// Verify that one file was copied; check file contents.
- assertDstFileCountEquals(1);
+ assertDestFileCount(1);
assertCopied(srcPath);
}
@@ -114,8 +115,6 @@ public class CopyTest extends ServiceTestCase<CopyService> {
String testContent = "The five boxing wizards jump quickly";
Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", testContent.getBytes());
- assertDstFileCountEquals(0);
-
Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
startService(moveIntent);
@@ -124,7 +123,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
mResolver.waitForChanges(3);
// Verify that one file was moved; check file contents.
- assertDstFileCountEquals(1);
+ assertDestFileCount(1);
assertDoesNotExist(SRC_ROOT, srcPath);
byte[] dstContent = readFile(DST_ROOT, srcPath);
@@ -147,15 +146,13 @@ public class CopyTest extends ServiceTestCase<CopyService> {
mStorage.createFile(SRC_ROOT, srcPaths[1], "text/plain", testContent[1].getBytes()),
mStorage.createFile(SRC_ROOT, srcPaths[2], "text/plain", testContent[2].getBytes()));
- assertDstFileCountEquals(0);
-
// Copy all the test files.
startService(createCopyIntent(testFiles));
// 3 file creations, 3 file writes.
mResolver.waitForChanges(6);
- assertDstFileCountEquals(3);
+ assertDestFileCount(3);
for (String path : srcPaths) {
assertCopied(path);
}
@@ -163,29 +160,54 @@ public class CopyTest extends ServiceTestCase<CopyService> {
public void testCopyEmptyDir() throws Exception {
String srcPath = "/emptyDir";
- Uri testDir = mStorage.createFile(SRC_ROOT, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
- null);
-
- assertDstFileCountEquals(0);
+ Uri testDir = createTestDirectory(srcPath);
startService(createCopyIntent(Lists.newArrayList(testDir)));
// Just 1 operation: Directory creation.
mResolver.waitForChanges(1);
- assertDstFileCountEquals(1);
+ assertDestFileCount(1);
// Verify that the dst exists and is a directory.
File dst = mStorage.getFile(DST_ROOT, srcPath);
assertTrue(dst.isDirectory());
}
+ public void testNoCopyDirToSelf() throws Exception {
+ Uri testDir = createTestDirectory("/someDir");
+
+ Intent intent = createCopyIntent(Lists.newArrayList(testDir), testDir);
+ startService(intent);
+
+ getService().addFinishedListener(mListener);
+
+ mListener.waitForFinished();
+ mListener.assertFailedCount(1);
+ mListener.assertFileFailed("someDir");
+
+ assertDestFileCount(0);
+ }
+
+ public void testNoCopyDirToDescendent() throws Exception {
+ Uri testDir = createTestDirectory("/someDir");
+ Uri descDir = createTestDirectory("/someDir/theDescendent");
+
+ Intent intent = createCopyIntent(Lists.newArrayList(testDir), descDir);
+ startService(intent);
+
+ getService().addFinishedListener(mListener);
+
+ mListener.waitForFinished();
+ mListener.assertFailedCount(1);
+ mListener.assertFileFailed("someDir");
+
+ assertDestFileCount(0);
+ }
+
public void testMoveEmptyDir() throws Exception {
String srcPath = "/emptyDir";
- Uri testDir = mStorage.createFile(SRC_ROOT, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
- null);
-
- assertDstFileCountEquals(0);
+ Uri testDir = createTestDirectory(srcPath);
Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
@@ -194,7 +216,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
// 2 operations: Directory creation, and removal of the original.
mResolver.waitForChanges(2);
- assertDstFileCountEquals(1);
+ assertDestFileCount(1);
// Verify that the dst exists and is a directory.
File dst = mStorage.getFile(DST_ROOT, srcPath);
@@ -217,8 +239,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
srcDir + "/test2.txt"
};
// Create test dir; put some files in it.
- Uri testDir = mStorage.createFile(SRC_ROOT, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
- null);
+ Uri testDir = createTestDirectory(srcDir);
mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
mStorage.createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
mStorage.createFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes());
@@ -252,8 +273,6 @@ public class CopyTest extends ServiceTestCase<CopyService> {
Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
"The five boxing wizards jump quickly".getBytes());
- assertDstFileCountEquals(0);
-
mStorage.simulateReadErrorsForFile(testFile);
startService(createCopyIntent(Lists.newArrayList(testFile)));
@@ -262,7 +281,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
mResolver.waitForChanges(3);
// Verify that the failed copy was cleaned up.
- assertDstFileCountEquals(0);
+ assertDestFileCount(0);
}
public void testMoveFileWithReadErrors() throws Exception {
@@ -270,8 +289,6 @@ public class CopyTest extends ServiceTestCase<CopyService> {
Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
"The five boxing wizards jump quickly".getBytes());
- assertDstFileCountEquals(0);
-
mStorage.simulateReadErrorsForFile(testFile);
Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
@@ -288,7 +305,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
return;
} finally {
// Verify that the failed copy was cleaned up, and the src file wasn't removed.
- assertDstFileCountEquals(0);
+ assertDestFileCount(0);
assertExists(SRC_ROOT, srcPath);
}
// The asserts above didn't fail, but the CopyService did something unexpected.
@@ -308,8 +325,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
srcDir + "/test2.txt"
};
// Create test dir; put some files in it.
- Uri testDir = mStorage.createFile(SRC_ROOT, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
- null);
+ Uri testDir = createTestDirectory(srcDir);
mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
Uri errFile = mStorage
.createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
@@ -346,33 +362,37 @@ public class CopyTest extends ServiceTestCase<CopyService> {
assertExists(SRC_ROOT, srcFiles[1]);
}
- /**
- * Copies the given files to a pre-determined destination.
- *
- * @throws FileNotFoundException
- */
+ private Uri createTestDirectory(String dir) throws IOException {
+ return mStorage.createFile(
+ SRC_ROOT, dir, DocumentsContract.Document.MIME_TYPE_DIR, null);
+ }
+
private Intent createCopyIntent(List<Uri> srcs) throws Exception {
+ RootInfo root = mDocHelper.getRoot(DST_ROOT);
+ final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, root.documentId);
+
+ return createCopyIntent(srcs, dst);
+ }
+
+ private Intent createCopyIntent(List<Uri> srcs, Uri dst) throws Exception {
final ArrayList<DocumentInfo> srcDocs = Lists.newArrayList();
for (Uri src : srcs) {
srcDocs.add(DocumentInfo.fromUri(mResolver, src));
}
- RootInfo root = mDocHelper.getRoot(DST_ROOT);
- final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, root.documentId);
DocumentStack stack = new DocumentStack();
stack.push(DocumentInfo.fromUri(mResolver, dst));
final Intent copyIntent = new Intent(mContext, CopyService.class);
copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST, srcDocs);
copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
- // startService(copyIntent);
return copyIntent;
}
/**
* Returns a count of the files in the given directory.
*/
- private void assertDstFileCountEquals(int expected) throws RemoteException {
+ private void assertDestFileCount(int expected) throws RemoteException {
RootInfo dest = mDocHelper.getRoot(DST_ROOT);
final Uri queryUri = DocumentsContract.buildChildDocumentsUri(AUTHORITY,
dest.documentId);
@@ -449,6 +469,34 @@ public class CopyTest extends ServiceTestCase<CopyService> {
mResolver.addProvider(AUTHORITY, mStorage);
}
+ private final class CopyJobListener implements CopyService.TestOnlyListener {
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final List<DocumentInfo> failedDocs = new ArrayList<>();
+ @Override
+ public void onFinished(List<DocumentInfo> failed) {
+ failedDocs.addAll(failed);
+ latch.countDown();
+ }
+
+ public void assertFileFailed(String expectedName) {
+ for (DocumentInfo failed : failedDocs) {
+ if (expectedName.equals(failed.displayName)) {
+ return;
+ }
+ }
+ fail("Couldn't find failed file: " + expectedName);
+ }
+
+ public void waitForFinished() throws InterruptedException {
+ latch.await(500, TimeUnit.MILLISECONDS);
+ }
+
+ public void assertFailedCount(int expected) {
+ assertEquals(expected, failedDocs.size());
+ }
+ }
+
/**
* A test resolver that enables this test suite to listen for notifications that mark when copy
* operations are done.
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
new file mode 100644
index 000000000000..737a8b699fe9
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.documentsui;
+
+import static com.android.documentsui.StubProvider.DEFAULT_AUTHORITY;
+import static com.android.documentsui.StubProvider.ROOT_0_ID;
+
+import android.app.Instrumentation;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.provider.DocumentsContract;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.documentsui.model.RootInfo;
+
+@LargeTest
+public class DownloadsActivityUiTest extends InstrumentationTestCase {
+
+ private static final int TIMEOUT = 5000;
+ private static final String TAG = "DownloadsActivityUiTest";
+ private static final String TARGET_PKG = "com.android.documentsui";
+ private static final String LAUNCHER_PKG = "com.android.launcher";
+
+ private UiBot mBot;
+ private UiDevice mDevice;
+ private Context mContext;
+ private ContentResolver mResolver;
+ private DocumentsProviderHelper mDocsHelper;
+ private ContentProviderClient mClient;
+ private RootInfo mRoot;
+
+ public void setUp() throws Exception {
+ // Initialize UiDevice instance.
+ Instrumentation instrumentation = getInstrumentation();
+
+ mDevice = UiDevice.getInstance(instrumentation);
+
+ Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
+
+ // Start from the home screen.
+ mDevice.pressHome();
+ mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), TIMEOUT);
+
+ // NOTE: Must be the "target" context, else security checks in content provider will fail.
+ mContext = instrumentation.getTargetContext();
+ mResolver = mContext.getContentResolver();
+
+ mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
+ mDocsHelper = new DocumentsProviderHelper(DEFAULT_AUTHORITY, mClient);
+
+ mRoot = mDocsHelper.getRoot(ROOT_0_ID);
+
+ // Open the Downloads activity on our stub provider root.
+ Intent intent = new Intent(DocumentsContract.ACTION_MANAGE_ROOT);
+ intent.setDataAndType(mRoot.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivity(intent);
+
+ // Wait for the app to appear.
+ mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
+ mDevice.waitForIdle();
+
+ mBot = new UiBot(mDevice, TIMEOUT);
+
+ resetStorage(); // Just in case a test failed and tearDown didn't happen.
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Need to kill off the task we started.
+ super.tearDown();
+ Log.d(TAG, "Resetting storage from setUp");
+ resetStorage();
+ mClient.release();
+ }
+
+ private void resetStorage() throws RemoteException {
+ mClient.call("clear", null, null);
+ // TODO: Would be nice to have an event to wait on here.
+ mDevice.waitForIdle();
+ }
+
+ private void initTestFiles() throws RemoteException {
+ mDocsHelper.createDocument(mRoot, "text/plain", "file0.log");
+ mDocsHelper.createDocument(mRoot, "image/png", "file1.png");
+ mDocsHelper.createDocument(mRoot, "text/csv", "file2.csv");
+ }
+
+ public void testWindowTitle() throws Exception {
+ initTestFiles();
+
+ mBot.assertWindowTitle(ROOT_0_ID);
+ }
+
+ public void testFilesListed() throws Exception {
+ initTestFiles();
+
+ mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
+ }
+
+ public void testFilesList_LiveUpdate() throws Exception {
+ initTestFiles();
+
+ mDocsHelper.createDocument(mRoot, "yummers/sandwich", "Ham & Cheese.sandwich");
+ mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
+ }
+
+ public void testDeleteDocument() throws Exception {
+ initTestFiles();
+
+ mBot.clickDocument("file1.png");
+ mDevice.waitForIdle();
+ mBot.menuDelete().click();
+
+ mBot.waitForDeleteSnackbar();
+ assertFalse(mBot.hasDocuments("file1.png"));
+
+ mBot.waitForDeleteSnackbarGone();
+ assertFalse(mBot.hasDocuments("file1.png"));
+ }
+
+ public void testSupportsShare() throws Exception {
+ initTestFiles();
+
+ mBot.clickDocument("file1.png");
+ mDevice.waitForIdle();
+ assertNotNull(mBot.menuShare());
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 2d42ddc4e6b5..7a75503ef12a 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -226,6 +226,12 @@ public class StubProvider extends DocumentsProvider {
}
@Override
+ public Cursor queryChildDocumentsForManage(String parentDocumentId, String[] projection,
+ String sortOrder) throws FileNotFoundException {
+ return queryChildDocuments(parentDocumentId, projection, sortOrder);
+ }
+
+ @Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
throws FileNotFoundException {
final StubDocument parentDocument = mStorage.get(parentDocumentId);
@@ -531,6 +537,16 @@ public class StubProvider extends DocumentsProvider {
this.rootInfo = rootInfo;
mStorage.put(this.documentId, this);
}
+ @Override
+ public String toString() {
+ return "StubDocument{"
+ + "path:" + file.getPath()
+ + ", mimeType:" + mimeType
+ + ", rootInfo:" + rootInfo
+ + ", documentId:" + documentId
+ + ", parentId:" + parentId
+ + "}";
+ }
}
private static String getDocumentIdForFile(File file) {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
index ecad0617c179..68cdf128d96c 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -179,6 +179,10 @@ class UiBot {
return find(By.res("com.android.documentsui:id/menu_delete"));
}
+ UiObject2 menuShare() {
+ return find(By.res("com.android.documentsui:id/menu_share"));
+ }
+
private UiObject2 find(BySelector selector) {
mDevice.wait(Until.findObject(selector), mTimeout);
return mDevice.findObject(selector);
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java
new file mode 100644
index 000000000000..a6aba7b06816
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.documentsui.model;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class DocumentInfoTest extends AndroidTestCase {
+
+ public void testEquals() throws Exception {
+ DocumentInfo doc = createDocInfo("authority.a", "doc.1", "text/plain");
+ assertEquals(doc, doc);
+ }
+
+ public void testNotEquals_differentAuthority() throws Exception {
+ DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
+ DocumentInfo docB = createDocInfo("authority.b", "doc.1", "text/plain");
+ assertFalse(docA.equals(docB));
+ }
+
+ public void testNotEquals_differentDocId() throws Exception {
+ DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
+ DocumentInfo docB = createDocInfo("authority.a", "doc.2", "text/plain");
+ assertFalse(docA.equals(docB));
+ }
+
+ public void testNotEquals_differentMimetype() throws Exception {
+ DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
+ DocumentInfo docB = createDocInfo("authority.a", "doc.1", "image/png");
+ assertFalse(docA.equals(docB));
+ }
+
+ private DocumentInfo createDocInfo(String authority, String docId, String mimeType) {
+ DocumentInfo doc = new DocumentInfo();
+ doc.authority = authority;
+ doc.documentId = docId;
+ doc.mimeType = mimeType;
+ doc.deriveFields();
+ return doc;
+ }
+}
diff --git a/packages/ExternalStorageProvider/res/values-uz-rUZ/strings.xml b/packages/ExternalStorageProvider/res/values-uz-rUZ/strings.xml
index d1a956bf13d1..069e137f8cd2 100644
--- a/packages/ExternalStorageProvider/res/values-uz-rUZ/strings.xml
+++ b/packages/ExternalStorageProvider/res/values-uz-rUZ/strings.xml
@@ -18,5 +18,5 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="7123375275748530234">"Tashqi xotira"</string>
<string name="root_internal_storage" msgid="827844243068584127">"Ichki xotira"</string>
- <string name="root_home" msgid="7931555396767513359">"Shaxsiy"</string>
+ <string name="root_home" msgid="7931555396767513359">"Mening fayllarim"</string>
</resources>
diff --git a/packages/Keyguard/res/values/config.xml b/packages/Keyguard/res/values/config.xml
index b398ab2320b0..bde6ed531353 100644
--- a/packages/Keyguard/res/values/config.xml
+++ b/packages/Keyguard/res/values/config.xml
@@ -23,9 +23,9 @@
<!-- Allow the menu hard key to be disabled in LockScreen on some devices [DO NOT TRANSLATE] -->
<bool name="config_disableMenuKeyInLockScreen">false</bool>
- <!-- Threshold in micro amperes below which a charger is rated as "slow" -->
- <integer name="config_chargingSlowlyThreshold">1000000</integer>
+ <!-- Threshold in micro watts below which a charger is rated as "slow"; 1A @ 5V -->
+ <integer name="config_chargingSlowlyThreshold">5000000</integer>
- <!-- Threshold in micro amperes above which a charger is rated as "fast" -->
- <integer name="config_chargingFastThreshold">1500000</integer>
+ <!-- Threshold in micro watts above which a charger is rated as "fast"; 1.5A @ 5V -->
+ <integer name="config_chargingFastThreshold">7500000</integer>
</resources>
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 3d78028a72f8..8102c34755a8 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -75,6 +75,7 @@ import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
import static android.os.BatteryManager.EXTRA_HEALTH;
import static android.os.BatteryManager.EXTRA_LEVEL;
import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT;
+import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
import static android.os.BatteryManager.EXTRA_PLUGGED;
import static android.os.BatteryManager.EXTRA_STATUS;
@@ -155,6 +156,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
*/
private static final int FINGERPRINT_STATE_CANCELLING_RESTARTING = 3;
+ private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
+
private static KeyguardUpdateMonitor sInstance;
private final Context mContext;
@@ -617,10 +620,25 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0);
final int level = intent.getIntExtra(EXTRA_LEVEL, 0);
final int health = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
- final int maxChargingCurrent = intent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
+
+ final int maxChargingMicroAmp = intent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
+ int maxChargingMicroVolt = intent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
+ final int maxChargingMicroWatt;
+
+ if (maxChargingMicroVolt <= 0) {
+ maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
+ }
+ if (maxChargingMicroAmp > 0) {
+ // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor
+ // to maintain precision equally on both factors.
+ maxChargingMicroWatt = (maxChargingMicroAmp / 1000)
+ * (maxChargingMicroVolt / 1000);
+ } else {
+ maxChargingMicroWatt = -1;
+ }
final Message msg = mHandler.obtainMessage(
MSG_BATTERY_UPDATE, new BatteryStatus(status, level, plugged, health,
- maxChargingCurrent));
+ maxChargingMicroWatt));
mHandler.sendMessage(msg);
} else if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) {
SimData args = SimData.fromIntent(intent);
@@ -806,13 +824,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
public final int level;
public final int plugged;
public final int health;
- public final int maxChargingCurrent;
- public BatteryStatus(int status, int level, int plugged, int health, int maxChargingCurrent) {
+ public final int maxChargingWattage;
+ public BatteryStatus(int status, int level, int plugged, int health,
+ int maxChargingWattage) {
this.status = status;
this.level = level;
this.plugged = plugged;
this.health = health;
- this.maxChargingCurrent = maxChargingCurrent;
+ this.maxChargingWattage = maxChargingWattage;
}
/**
@@ -844,9 +863,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
}
public final int getChargingSpeed(int slowThreshold, int fastThreshold) {
- return maxChargingCurrent <= 0 ? CHARGING_UNKNOWN :
- maxChargingCurrent < slowThreshold ? CHARGING_SLOWLY :
- maxChargingCurrent > fastThreshold ? CHARGING_FAST :
+ return maxChargingWattage <= 0 ? CHARGING_UNKNOWN :
+ maxChargingWattage < slowThreshold ? CHARGING_SLOWLY :
+ maxChargingWattage > fastThreshold ? CHARGING_FAST :
CHARGING_REGULAR;
}
}
@@ -1422,7 +1441,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
}
// change in charging current while plugged in
- if (nowPluggedIn && current.maxChargingCurrent != old.maxChargingCurrent) {
+ if (nowPluggedIn && current.maxChargingWattage != old.maxChargingWattage) {
return true;
}
diff --git a/packages/MtpDocumentsProvider/res/drawable-hdpi/ic_root_mtp.png b/packages/MtpDocumentsProvider/res/drawable-hdpi/ic_root_mtp.png
new file mode 100644
index 000000000000..7691433a58eb
--- /dev/null
+++ b/packages/MtpDocumentsProvider/res/drawable-hdpi/ic_root_mtp.png
Binary files differ
diff --git a/packages/MtpDocumentsProvider/res/drawable-mdpi/ic_root_mtp.png b/packages/MtpDocumentsProvider/res/drawable-mdpi/ic_root_mtp.png
new file mode 100644
index 000000000000..1cf7b3ac0480
--- /dev/null
+++ b/packages/MtpDocumentsProvider/res/drawable-mdpi/ic_root_mtp.png
Binary files differ
diff --git a/packages/MtpDocumentsProvider/res/drawable-xhdpi/ic_root_mtp.png b/packages/MtpDocumentsProvider/res/drawable-xhdpi/ic_root_mtp.png
new file mode 100644
index 000000000000..27e35421a957
--- /dev/null
+++ b/packages/MtpDocumentsProvider/res/drawable-xhdpi/ic_root_mtp.png
Binary files differ
diff --git a/packages/MtpDocumentsProvider/res/drawable-xxhdpi/ic_root_mtp.png b/packages/MtpDocumentsProvider/res/drawable-xxhdpi/ic_root_mtp.png
new file mode 100644
index 000000000000..3df2578b263f
--- /dev/null
+++ b/packages/MtpDocumentsProvider/res/drawable-xxhdpi/ic_root_mtp.png
Binary files differ
diff --git a/packages/MtpDocumentsProvider/res/drawable-xxxhdpi/ic_root_mtp.png b/packages/MtpDocumentsProvider/res/drawable-xxxhdpi/ic_root_mtp.png
new file mode 100644
index 000000000000..fd2b79566983
--- /dev/null
+++ b/packages/MtpDocumentsProvider/res/drawable-xxxhdpi/ic_root_mtp.png
Binary files differ
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/CursorHelper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/CursorHelper.java
deleted file mode 100644
index 844b21685fa1..000000000000
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/CursorHelper.java
+++ /dev/null
@@ -1,91 +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.mtp;
-
-import android.content.res.Resources;
-import android.database.MatrixCursor;
-import android.media.MediaFile;
-import android.mtp.MtpConstants;
-import android.mtp.MtpObjectInfo;
-import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Document;
-
-/**
- * TODO Remove this class after we switch to use MtpDatabase.
- */
-final class CursorHelper {
- static final int DUMMY_HANDLE_FOR_ROOT = 0;
-
- private CursorHelper() {
- }
-
- static void addToCursor(Resources resources, MtpRoot root, MatrixCursor.RowBuilder builder) {
- final Identifier identifier = new Identifier(
- root.mDeviceId, root.mStorageId, DUMMY_HANDLE_FOR_ROOT);
- builder.add(Document.COLUMN_DOCUMENT_ID, identifier.toDocumentId());
- builder.add(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources));
- builder.add(Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
- builder.add(Document.COLUMN_LAST_MODIFIED, null);
- builder.add(Document.COLUMN_FLAGS, 0);
- builder.add(Document.COLUMN_SIZE,
- (int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE));
- }
-
- static void addToCursor(MtpObjectInfo objectInfo, Identifier rootIdentifier,
- MatrixCursor.RowBuilder builder) {
- final Identifier identifier = new Identifier(
- rootIdentifier.mDeviceId, rootIdentifier.mStorageId, objectInfo.getObjectHandle());
- final String mimeType = formatTypeToMimeType(objectInfo.getFormat());
-
- int flag = 0;
- if (objectInfo.getProtectionStatus() == 0) {
- flag |= DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
- DocumentsContract.Document.FLAG_SUPPORTS_WRITE;
- if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
- flag |= DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE;
- }
- }
- if (objectInfo.getThumbCompressedSize() > 0) {
- flag |= DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL;
- }
-
- builder.add(Document.COLUMN_DOCUMENT_ID, identifier.toDocumentId());
- builder.add(Document.COLUMN_DISPLAY_NAME, objectInfo.getName());
- builder.add(Document.COLUMN_MIME_TYPE, mimeType);
- builder.add(
- Document.COLUMN_LAST_MODIFIED,
- objectInfo.getDateModified() != 0 ? objectInfo.getDateModified() : null);
- builder.add(Document.COLUMN_FLAGS, flag);
- builder.add(Document.COLUMN_SIZE, objectInfo.getCompressedSize());
- }
-
- static String formatTypeToMimeType(int format) {
- if (format == MtpConstants.FORMAT_ASSOCIATION) {
- return DocumentsContract.Document.MIME_TYPE_DIR;
- } else {
- return MediaFile.getMimeTypeForFormatCode(format);
- }
- }
-
- static int mimeTypeToFormatType(String fileName, String mimeType) {
- if (Document.MIME_TYPE_DIR.equals(mimeType)) {
- return MtpConstants.FORMAT_ASSOCIATION;
- } else {
- return MediaFile.getFormatCode(fileName, mimeType);
- }
- }
-}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
index 1c96906a1d79..6fa0df2abc22 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -24,7 +24,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Process;
import android.provider.DocumentsContract;
-import android.util.Log;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -75,9 +74,15 @@ class DocumentLoader {
int parentHandle = parent.mObjectHandle;
// Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
// getObjectHandles if we would like to obtain children under the root.
- if (parentHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
+ if (parentHandle == Identifier.DUMMY_HANDLE_FOR_ROOT) {
parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
}
+ // TODO: Handle nit race around here.
+ // 1. getObjectHandles.
+ // 2. putNewDocument.
+ // 3. startAddingChildDocuemnts.
+ // 4. stopAddingChildDocuments - It removes the new document added at the step 2,
+ // because it is not updated between start/stopAddingChildDocuments.
task = new LoaderTask(mDatabase, parent, mMtpManager.getObjectHandles(
parent.mDeviceId, parent.mStorageId, parentHandle));
task.fillDocuments(loadDocuments(
@@ -215,8 +220,8 @@ class DocumentLoader {
throw new IOException(mError);
}
- final Cursor cursor = mDatabase.queryChildDocuments(
- columnNames, mIdentifier.mDocumentId, /* use old ID format */ true);
+ final Cursor cursor =
+ mDatabase.queryChildDocuments(columnNames, mIdentifier.mDocumentId);
cursor.setNotificationUri(resolver, createUri());
cursor.respond(extras);
@@ -250,10 +255,10 @@ class DocumentLoader {
return;
}
if (mNumLoaded == 0) {
- mDatabase.startAddingChildDocuments(mIdentifier.mDocumentId);
+ mDatabase.getMapper().startAddingChildDocuments(mIdentifier.mDocumentId);
}
try {
- mDatabase.putChildDocuments(
+ mDatabase.getMapper().putChildDocuments(
mIdentifier.mDeviceId, mIdentifier.mDocumentId, objectInfoList);
mNumLoaded += objectInfoList.length;
} catch (SQLiteException exp) {
@@ -261,7 +266,7 @@ class DocumentLoader {
mNumLoaded = 0;
}
if (getState() != STATE_LOADING) {
- mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
+ mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId);
}
}
@@ -270,13 +275,13 @@ class DocumentLoader {
mError = message;
mNumLoaded = 0;
if (lastState == STATE_LOADING) {
- mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
+ mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId);
}
}
private Uri createUri() {
return DocumentsContract.buildChildDocumentsUri(
- MtpDocumentsProvider.AUTHORITY, mIdentifier.toDocumentId());
+ MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId);
}
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
index 4238721e1238..20b3bf5db3b7 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java
@@ -16,39 +16,19 @@
package com.android.mtp;
+import java.util.Objects;
+
/**
* Static utilities for ID.
*/
class Identifier {
+ final static int DUMMY_HANDLE_FOR_ROOT = 0;
+
final int mDeviceId;
final int mStorageId;
final int mObjectHandle;
final String mDocumentId;
- static Identifier createFromRootId(String rootId) {
- final String[] components = rootId.split("_");
- return new Identifier(
- Integer.parseInt(components[0]),
- Integer.parseInt(components[1]));
- }
-
- static Identifier createFromDocumentId(String documentId) {
- final String[] components = documentId.split("_");
- return new Identifier(
- Integer.parseInt(components[0]),
- Integer.parseInt(components[1]),
- Integer.parseInt(components[2]));
- }
-
-
- Identifier(int deviceId, int storageId) {
- this(deviceId, storageId, CursorHelper.DUMMY_HANDLE_FOR_ROOT);
- }
-
- Identifier(int deviceId, int storageId, int objectHandle) {
- this(deviceId, storageId, objectHandle, null);
- }
-
Identifier(int deviceId, int storageId, int objectHandle, String documentId) {
mDeviceId = deviceId;
mStorageId = storageId;
@@ -56,16 +36,6 @@ class Identifier {
mDocumentId = documentId;
}
- // TODO: Make the ID persistent.
- String toRootId() {
- return String.format("%d_%d", mDeviceId, mStorageId);
- }
-
- // TODO: Make the ID persistent.
- String toDocumentId() {
- return String.format("%d_%d_%d", mDeviceId, mStorageId, mObjectHandle);
- }
-
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Identifier))
@@ -77,6 +47,6 @@ class Identifier {
@Override
public int hashCode() {
- return (mDeviceId << 16) ^ (mStorageId << 8) ^ mObjectHandle;
+ return Objects.hash(mDeviceId, mStorageId, mObjectHandle, mDocumentId);
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
index 9c5d6b6ad3ec..0d9d60c0b460 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -19,167 +19,235 @@ package com.android.mtp;
import static com.android.mtp.MtpDatabaseConstants.*;
import android.content.ContentValues;
-import android.content.Context;
+import android.content.res.Resources;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQueryBuilder;
+import android.mtp.MtpObjectInfo;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
-import java.util.Objects;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
-/**
- * Class that provides operations processing SQLite database directly.
- */
-class MtpDatabaseInternal {
- private static class OpenHelper extends SQLiteOpenHelper {
- public OpenHelper(Context context, int flags) {
- super(context,
- flags == FLAG_DATABASE_IN_MEMORY ? null : DATABASE_NAME,
- null,
- DATABASE_VERSION);
- }
+import java.util.HashMap;
+import java.util.Map;
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL(QUERY_CREATE_DOCUMENTS);
- db.execSQL(QUERY_CREATE_ROOT_EXTRA);
- db.execSQL(QUERY_CREATE_VIEW_ROOTS);
- }
+import static com.android.mtp.MtpDatabase.strings;
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- throw new UnsupportedOperationException();
- }
- }
- private final SQLiteDatabase mDatabase;
+/**
+ * Mapping operations for MtpDatabase.
+ * Also see the comments of {@link MtpDatabase}.
+ */
+class Mapper {
+ private final MtpDatabase mDatabase;
- MtpDatabaseInternal(Context context, int flags) {
- final OpenHelper helper = new OpenHelper(context, flags);
- mDatabase = helper.getWritableDatabase();
+ /**
+ * Mapping mode for roots/documents where we start adding child documents.
+ * Methods operate the state needs to be synchronized.
+ */
+ private final Map<String, Integer> mMappingMode = new HashMap<>();
+
+ Mapper(MtpDatabase database) {
+ mDatabase = database;
}
- void close() {
- mDatabase.close();
+ /**
+ * Invokes {@link #startAddingDocuments} for root documents.
+ * @param deviceId Device ID.
+ */
+ synchronized void startAddingRootDocuments(int deviceId) {
+ final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId);
+ Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey));
+ mMappingMode.put(
+ mappingStateKey,
+ startAddingDocuments(
+ SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId)));
}
/**
- * Queries roots information.
- * @param columnNames Column names defined in {@link android.provider.DocumentsContract.Root}.
- * @return Database cursor.
+ * Invokes {@link #startAddingDocuments} for child of specific documents.
+ * @param parentDocumentId Document ID for parent document.
*/
- Cursor queryRoots(String[] columnNames) {
- return mDatabase.query(
- VIEW_ROOTS,
- columnNames,
- COLUMN_ROW_STATE + " IN (?, ?)",
- strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
- null,
- null,
- null);
+ @VisibleForTesting
+ synchronized void startAddingChildDocuments(String parentDocumentId) {
+ final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId);
+ Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey));
+ mMappingMode.put(
+ mappingStateKey,
+ startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId));
}
/**
- * Queries root documents information.
- * @param columnNames Column names defined in
- * {@link android.provider.DocumentsContract.Document}.
- * @return Database cursor.
+ * Puts root information to database.
+ * @param deviceId Device ID
+ * @param resources Resources required to localize root name.
+ * @param roots List of root information.
+ * @return If roots are added or removed from the database.
*/
- Cursor queryRootDocuments(String[] columnNames) {
- return mDatabase.query(
- TABLE_DOCUMENTS,
- columnNames,
- COLUMN_ROW_STATE + " IN (?, ?)",
- strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
- null,
- null,
- null);
+ synchronized boolean putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ database.beginTransaction();
+ try {
+ final boolean heuristic;
+ final String mapColumn;
+ final String key = getRootDocumentsMappingStateKey(deviceId);
+ Preconditions.checkState(mMappingMode.containsKey(key));
+ switch (mMappingMode.get(key)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ heuristic = false;
+ mapColumn = COLUMN_STORAGE_ID;
+ break;
+ case MAP_BY_NAME:
+ heuristic = true;
+ mapColumn = Document.COLUMN_DISPLAY_NAME;
+ break;
+ default:
+ throw new Error("Unexpected map mode.");
+ }
+ final ContentValues[] valuesList = new ContentValues[roots.length];
+ for (int i = 0; i < roots.length; i++) {
+ if (roots[i].mDeviceId != deviceId) {
+ throw new IllegalArgumentException();
+ }
+ valuesList[i] = new ContentValues();
+ MtpDatabase.getRootDocumentValues(valuesList[i], resources, roots[i]);
+ }
+ final boolean changed = putDocuments(
+ valuesList,
+ SELECTION_ROOT_DOCUMENTS,
+ Integer.toString(deviceId),
+ heuristic,
+ mapColumn);
+ final ContentValues values = new ContentValues();
+ int i = 0;
+ for (final MtpRoot root : roots) {
+ // Use the same value for the root ID and the corresponding document ID.
+ final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID);
+ // If it fails to insert/update documents, the document ID will be set with -1.
+ // In this case we don't insert/update root extra information neither.
+ if (documentId == null) {
+ continue;
+ }
+ values.put(Root.COLUMN_ROOT_ID, documentId);
+ values.put(
+ Root.COLUMN_FLAGS,
+ Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
+ values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace);
+ values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity);
+ values.put(Root.COLUMN_MIME_TYPES, "");
+ database.replace(TABLE_ROOT_EXTRA, null, values);
+ }
+ database.setTransactionSuccessful();
+ return changed;
+ } finally {
+ database.endTransaction();
+ }
}
/**
- * Queries documents information.
- * @param columnNames Column names defined in
- * {@link android.provider.DocumentsContract.Document}.
- * @return Database cursor.
+ * Puts document information to database.
+ * @param deviceId Device ID
+ * @param parentId Parent document ID.
+ * @param documents List of document information.
*/
- Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
- return mDatabase.query(
- TABLE_DOCUMENTS,
- columnNames,
- COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_PARENT_DOCUMENT_ID + " = ?",
- strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, parentDocumentId),
- null,
- null,
- null);
+ synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
+ final boolean heuristic;
+ final String mapColumn;
+ final String key = getChildDocumentsMappingStateKey(parentId);
+ Preconditions.checkState(mMappingMode.containsKey(key));
+ switch (mMappingMode.get(key)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ heuristic = false;
+ mapColumn = COLUMN_OBJECT_HANDLE;
+ break;
+ case MAP_BY_NAME:
+ heuristic = true;
+ mapColumn = Document.COLUMN_DISPLAY_NAME;
+ break;
+ default:
+ throw new Error("Unexpected map mode.");
+ }
+ final ContentValues[] valuesList = new ContentValues[documents.length];
+ for (int i = 0; i < documents.length; i++) {
+ valuesList[i] = new ContentValues();
+ MtpDatabase.getChildDocumentValues(
+ valuesList[i], deviceId, parentId, documents[i]);
+ }
+ putDocuments(
+ valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn);
}
/**
- * Remove all rows belong to a device.
+ * Stops adding root documents.
* @param deviceId Device ID.
+ * @return True if new rows are added/removed.
*/
- void removeDeviceRows(int deviceId) {
- deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId));
+ synchronized boolean stopAddingRootDocuments(int deviceId) {
+ final String key = getRootDocumentsMappingStateKey(deviceId);
+ Preconditions.checkState(mMappingMode.containsKey(key));
+ switch (mMappingMode.get(key)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ mMappingMode.remove(key);
+ return stopAddingDocuments(
+ SELECTION_ROOT_DOCUMENTS,
+ Integer.toString(deviceId),
+ COLUMN_STORAGE_ID);
+ case MAP_BY_NAME:
+ mMappingMode.remove(key);
+ return stopAddingDocuments(
+ SELECTION_ROOT_DOCUMENTS,
+ Integer.toString(deviceId),
+ Document.COLUMN_DISPLAY_NAME);
+ default:
+ throw new Error("Unexpected mapping state.");
+ }
}
/**
- * Gets identifier from document ID.
- * @param documentId Document ID.
- * @return Identifier.
+ * Stops adding documents under the parent.
+ * @param parentId Document ID of the parent.
*/
- Identifier createIdentifier(String documentId) {
- // Currently documentId is old format.
- final Identifier oldIdentifier = Identifier.createFromDocumentId(documentId);
- final String selection;
- final String[] args;
- if (oldIdentifier.mObjectHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
- selection = COLUMN_DEVICE_ID + "= ? AND " +
- COLUMN_ROW_STATE + " IN (?, ?) AND " +
- COLUMN_STORAGE_ID + "= ? AND " +
- COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
- args = strings(
- oldIdentifier.mDeviceId,
- ROW_STATE_VALID,
- ROW_STATE_INVALIDATED,
- oldIdentifier.mStorageId);
- } else {
- selection = COLUMN_DEVICE_ID + "= ? AND " +
- COLUMN_ROW_STATE + " IN (?, ?) AND " +
- COLUMN_STORAGE_ID + "= ? AND " +
- COLUMN_OBJECT_HANDLE + " = ?";
- args = strings(
- oldIdentifier.mDeviceId,
- ROW_STATE_VALID,
- ROW_STATE_INVALIDATED,
- oldIdentifier.mStorageId,
- oldIdentifier.mObjectHandle);
+ synchronized void stopAddingChildDocuments(String parentId) {
+ final String key = getChildDocumentsMappingStateKey(parentId);
+ Preconditions.checkState(mMappingMode.containsKey(key));
+ switch (mMappingMode.get(key)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ stopAddingDocuments(
+ SELECTION_CHILD_DOCUMENTS,
+ parentId,
+ COLUMN_OBJECT_HANDLE);
+ break;
+ case MAP_BY_NAME:
+ stopAddingDocuments(
+ SELECTION_CHILD_DOCUMENTS,
+ parentId,
+ Document.COLUMN_DISPLAY_NAME);
+ break;
+ default:
+ throw new Error("Unexpected mapping state.");
}
+ mMappingMode.remove(key);
+ }
- final Cursor cursor = mDatabase.query(
- TABLE_DOCUMENTS,
- strings(Document.COLUMN_DOCUMENT_ID),
- selection,
- args,
- null,
- null,
- null,
- "1");
+ @VisibleForTesting
+ void clearMapping() {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ database.beginTransaction();
try {
- if (cursor.getCount() == 0) {
- return oldIdentifier;
- } else {
- cursor.moveToNext();
- return new Identifier(
- oldIdentifier.mDeviceId,
- oldIdentifier.mStorageId,
- oldIdentifier.mObjectHandle,
- cursor.getString(0));
- }
+ mDatabase.deleteDocumentsAndRootsRecursively(
+ COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_PENDING));
+ final ContentValues values = new ContentValues();
+ values.putNull(COLUMN_OBJECT_HANDLE);
+ values.putNull(COLUMN_STORAGE_ID);
+ values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
+ database.update(TABLE_DOCUMENTS, values, null, null);
+ database.setTransactionSuccessful();
+ mMappingMode.clear();
} finally {
- cursor.close();
+ database.endTransaction();
}
}
@@ -193,28 +261,29 @@ class MtpDatabaseInternal {
* @param arg Argument for selection.
* @return Mapping mode.
*/
- int startAddingDocuments(String selection, String arg) {
- mDatabase.beginTransaction();
+ private int startAddingDocuments(String selection, String arg) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ database.beginTransaction();
try {
// Delete all pending rows.
- deleteDocumentsAndRoots(
+ mDatabase.deleteDocumentsAndRootsRecursively(
selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING));
// Set all documents as invalidated.
final ContentValues values = new ContentValues();
values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
- mDatabase.update(TABLE_DOCUMENTS, values, selection, new String[] { arg });
+ database.update(TABLE_DOCUMENTS, values, selection, new String[] { arg });
// If we have rows that does not have MTP identifier, do heuristic mapping by name.
final boolean useNameForResolving = DatabaseUtils.queryNumEntries(
- mDatabase,
+ database,
TABLE_DOCUMENTS,
selection + " AND " + COLUMN_STORAGE_ID + " IS NULL",
new String[] { arg }) > 0;
- mDatabase.setTransactionSuccessful();
+ database.setTransactionSuccessful();
return useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER;
} finally {
- mDatabase.endTransaction();
+ database.endTransaction();
}
}
@@ -232,17 +301,18 @@ class MtpDatabaseInternal {
* @param heuristic Whether the mapping mode is heuristic.
* @return Whether the method adds new rows.
*/
- boolean putDocuments(
+ private boolean putDocuments(
ContentValues[] valuesList,
String selection,
String arg,
boolean heuristic,
String mappingKey) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
boolean added = false;
- mDatabase.beginTransaction();
+ database.beginTransaction();
try {
for (final ContentValues values : valuesList) {
- final Cursor candidateCursor = mDatabase.query(
+ final Cursor candidateCursor = database.query(
TABLE_DOCUMENTS,
strings(Document.COLUMN_DOCUMENT_ID),
selection + " AND " +
@@ -256,7 +326,7 @@ class MtpDatabaseInternal {
try {
final long rowId;
if (candidateCursor.getCount() == 0) {
- rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+ rowId = database.insert(TABLE_DOCUMENTS, null, values);
if (rowId == -1) {
throw new SQLiteException("Failed to put a document into database.");
}
@@ -264,14 +334,14 @@ class MtpDatabaseInternal {
} else if (!heuristic) {
candidateCursor.moveToNext();
final String documentId = candidateCursor.getString(0);
- rowId = mDatabase.update(
+ rowId = database.update(
TABLE_DOCUMENTS,
values,
SELECTION_DOCUMENT_ID,
strings(documentId));
} else {
values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
- rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+ rowId = database.insert(TABLE_DOCUMENTS, null, values);
}
// Document ID is a primary integer key of the table. So the returned row
// IDs should be same with the document ID.
@@ -281,22 +351,14 @@ class MtpDatabaseInternal {
}
}
- mDatabase.setTransactionSuccessful();
+ database.setTransactionSuccessful();
return added;
} finally {
- mDatabase.endTransaction();
+ database.endTransaction();
}
}
/**
- * Puts extra information for root documents.
- * @param values Values containing extra information.
- */
- void putRootExtra(ContentValues values) {
- mDatabase.replace(TABLE_ROOT_EXTRA, null, values);
- }
-
- /**
* Maps 'pending' document and 'invalidated' document that shares the same column of groupKey.
* If the database does not find corresponding 'invalidated' document, it just removes
* 'invalidated' document from the database.
@@ -305,8 +367,9 @@ class MtpDatabaseInternal {
* @param groupKey Column name used to find corresponding rows.
* @return Whether the methods adds or removed visible rows.
*/
- boolean stopAddingDocuments(String selection, String arg, String groupKey) {
- mDatabase.beginTransaction();
+ private boolean stopAddingDocuments(String selection, String arg, String groupKey) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ database.beginTransaction();
try {
// Get 1-to-1 mapping of invalidated document and pending document.
final String invalidatedIdQuery = createStateFilter(
@@ -320,7 +383,7 @@ class MtpDatabaseInternal {
// GROUP BY display_name
// HAVING count(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END) = 1 AND
// count(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END) = 1
- final Cursor mergingCursor = mDatabase.query(
+ final Cursor mergingCursor = database.query(
TABLE_DOCUMENTS,
new String[] {
"group_concat(" + invalidatedIdQuery + ")",
@@ -345,7 +408,7 @@ class MtpDatabaseInternal {
values);
values.remove(Document.COLUMN_DOCUMENT_ID);
values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
- mDatabase.update(
+ database.update(
TABLE_DOCUMENTS,
values,
SELECTION_DOCUMENT_ID,
@@ -358,7 +421,7 @@ class MtpDatabaseInternal {
values);
if (values.size() > 0) {
values.remove(Root.COLUMN_ROOT_ID);
- mDatabase.update(
+ database.update(
TABLE_ROOT_EXTRA,
values,
SELECTION_ROOT_ID,
@@ -366,14 +429,15 @@ class MtpDatabaseInternal {
}
// Delete 'pending' row.
- deleteDocumentsAndRoots(SELECTION_DOCUMENT_ID, new String[] { pendingId });
+ mDatabase.deleteDocumentsAndRootsRecursively(
+ SELECTION_DOCUMENT_ID, new String[] { pendingId });
}
mergingCursor.close();
boolean changed = false;
// Delete all invalidated rows that cannot be mapped.
- if (deleteDocumentsAndRoots(
+ if (mDatabase.deleteDocumentsAndRootsRecursively(
COLUMN_ROW_STATE + " = ? AND " + selection,
strings(ROW_STATE_INVALIDATED, arg))) {
changed = true;
@@ -384,89 +448,17 @@ class MtpDatabaseInternal {
// valid with new document ID.
values.clear();
values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
- if (mDatabase.update(
+ if (database.update(
TABLE_DOCUMENTS,
values,
COLUMN_ROW_STATE + " = ? AND " + selection,
strings(ROW_STATE_PENDING, arg)) != 0) {
changed = true;
}
- mDatabase.setTransactionSuccessful();
+ database.setTransactionSuccessful();
return changed;
} finally {
- mDatabase.endTransaction();
- }
- }
-
- /**
- * Clears MTP related identifier.
- * It clears MTP's object handle and storage ID that are not stable over MTP sessions and mark
- * the all documents as 'invalidated'. It also remove 'pending' rows as adding is cancelled
- * now.
- */
- void clearMapping() {
- mDatabase.beginTransaction();
- try {
- deleteDocumentsAndRoots(COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_PENDING));
- final ContentValues values = new ContentValues();
- values.putNull(COLUMN_OBJECT_HANDLE);
- values.putNull(COLUMN_STORAGE_ID);
- values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
- mDatabase.update(TABLE_DOCUMENTS, values, null, null);
- mDatabase.setTransactionSuccessful();
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- /**
- * {@link android.database.sqlite.SQLiteDatabase#beginTransaction()}
- */
- void beginTransaction() {
- mDatabase.beginTransaction();
- }
-
- /**
- * {@link android.database.sqlite.SQLiteDatabase#setTransactionSuccessful()}
- */
- void setTransactionSuccessful() {
- mDatabase.setTransactionSuccessful();
- }
-
- /**
- * {@link android.database.sqlite.SQLiteDatabase#endTransaction()}
- */
- void endTransaction() {
- mDatabase.endTransaction();
- }
-
- /**
- * Deletes a document, and its root information if the document is a root document.
- * @param selection Query to select documents.
- * @param args Arguments for selection.
- * @return Whether the method deletes rows.
- */
- private boolean deleteDocumentsAndRoots(String selection, String[] args) {
- mDatabase.beginTransaction();
- try {
- int deleted = 0;
- deleted += mDatabase.delete(
- TABLE_ROOT_EXTRA,
- Root.COLUMN_ROOT_ID + " IN (" + SQLiteQueryBuilder.buildQueryString(
- false,
- TABLE_DOCUMENTS,
- new String[] { Document.COLUMN_DOCUMENT_ID },
- selection,
- null,
- null,
- null,
- null) + ")",
- args);
- deleted += mDatabase.delete(TABLE_DOCUMENTS, selection, args);
- mDatabase.setTransactionSuccessful();
- return deleted != 0;
- } finally {
- mDatabase.endTransaction();
+ database.endTransaction();
}
}
@@ -478,8 +470,9 @@ class MtpDatabaseInternal {
* @param args Argument for query.
*/
private void getFirstRow(String table, String selection, String[] args, ContentValues values) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
values.clear();
- final Cursor cursor = mDatabase.query(table, null, selection, args, null, null, null, "1");
+ final Cursor cursor = database.query(table, null, selection, args, null, null, null, "1");
if (cursor.getCount() == 0) {
return;
}
@@ -490,6 +483,8 @@ class MtpDatabaseInternal {
/**
* Gets SQL expression that represents the given value or NULL depends on the row state.
+ * You must pass static constants to this methods otherwise you may be suffered from SQL
+ * injections.
* @param state Expected row state.
* @param a SQL value.
* @return Expression that represents a if the row state is expected one, and represents NULL
@@ -501,15 +496,18 @@ class MtpDatabaseInternal {
}
/**
- * Converts values into string array.
- * @param args Values converted into string array.
- * @return String array.
+ * @param deviceId Device ID.
+ * @return Key for {@link #mMappingMode}.
*/
- private static String[] strings(Object... args) {
- final String[] results = new String[args.length];
- for (int i = 0; i < args.length; i++) {
- results[i] = Objects.toString(args[i]);
- }
- return results;
+ private static String getRootDocumentsMappingStateKey(int deviceId) {
+ return "RootDocuments/" + deviceId;
+ }
+
+ /**
+ * @param parentDocumentId Document ID for the parent document.
+ * @return Key for {@link #mMappingMode}.
+ */
+ private static String getChildDocumentsMappingStateKey(String parentDocumentId) {
+ return "ChildDocuments/" + parentDocumentId;
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 3dc69ccbf1ee..51c0281c51b5 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -22,6 +22,12 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.media.MediaFile;
+import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
@@ -29,8 +35,9 @@ import android.provider.DocumentsContract.Root;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.HashMap;
-import java.util.Map;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Objects;
/**
* Database for MTP objects.
@@ -46,13 +53,13 @@ import java.util.Map;
* by comparing the directory structure and object name.
*
* To start putting documents into the database, the client needs to call
- * {@link #startAddingChildDocuments(String)} with the parent document ID. Also it needs to call
- * {@link #stopAddingChildDocuments(String)} after putting all child documents to the database.
- * (All explanations are same for root documents)
+ * {@link Mapper#startAddingChildDocuments(String)} with the parent document ID. Also it
+ * needs to call {@link Mapper#stopAddingChildDocuments(String)} after putting all child
+ * documents to the database. (All explanations are same for root documents)
*
- * database.startAddingChildDocuments();
- * database.putChildDocuments();
- * database.stopAddingChildDocuments();
+ * database.getMapper().startAddingChildDocuments();
+ * database.getMapper().putChildDocuments();
+ * database.getMapper().stopAddingChildDocuments();
*
* To update the existing documents, the client code can repeat to call the three methods again.
* The newly added rows update corresponding existing rows that have same MTP identifier like
@@ -66,170 +73,232 @@ import java.util.Map;
* the database tries to find corresponding rows by using document's name instead of MTP identifier
* at the next update cycle.
*
- * TODO: Remove @VisibleForTesting annotation when we start to use this class.
* TODO: Improve performance by SQL optimization.
*/
-@VisibleForTesting
class MtpDatabase {
- private final MtpDatabaseInternal mDatabase;
+ private final SQLiteDatabase mDatabase;
+ private final Mapper mMapper;
- /**
- * Mapping mode for roots/documents where we start adding child documents.
- * Methods operate the state needs to be synchronized.
- */
- private final Map<String, Integer> mMappingMode = new HashMap<>();
+ SQLiteDatabase getSQLiteDatabase() {
+ return mDatabase;
+ }
- @VisibleForTesting
MtpDatabase(Context context, int flags) {
- mDatabase = new MtpDatabaseInternal(context, flags);
+ final OpenHelper helper = new OpenHelper(context, flags);
+ mDatabase = helper.getWritableDatabase();
+ mMapper = new Mapper(this);
}
- /**
- * Closes the database.
- */
- @VisibleForTesting
void close() {
mDatabase.close();
}
/**
- * {@link MtpDatabaseInternal#queryRoots}
+ * Returns operations for mapping.
+ * @return Mapping operations.
+ */
+ Mapper getMapper() {
+ return mMapper;
+ }
+
+ /**
+ * Queries roots information.
+ * @param columnNames Column names defined in {@link android.provider.DocumentsContract.Root}.
+ * @return Database cursor.
*/
Cursor queryRoots(String[] columnNames) {
- return mDatabase.queryRoots(columnNames);
+ return mDatabase.query(
+ VIEW_ROOTS,
+ columnNames,
+ COLUMN_ROW_STATE + " IN (?, ?)",
+ strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
+ null,
+ null,
+ null);
}
/**
- * {@link MtpDatabaseInternal#queryRootDocuments}
+ * Queries root documents information.
+ * @param columnNames Column names defined in
+ * {@link android.provider.DocumentsContract.Document}.
+ * @return Database cursor.
*/
@VisibleForTesting
Cursor queryRootDocuments(String[] columnNames) {
- return mDatabase.queryRootDocuments(columnNames);
+ return mDatabase.query(
+ TABLE_DOCUMENTS,
+ columnNames,
+ COLUMN_ROW_STATE + " IN (?, ?)",
+ strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
+ null,
+ null,
+ null);
}
/**
- * {@link MtpDatabaseInternal#queryChildDocuments}
+ * Queries documents information.
+ * @param columnNames Column names defined in
+ * {@link android.provider.DocumentsContract.Document}.
+ * @return Database cursor.
*/
- @VisibleForTesting
Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
- return queryChildDocuments(columnNames, parentDocumentId, false);
+ return mDatabase.query(
+ TABLE_DOCUMENTS,
+ columnNames,
+ COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_PARENT_DOCUMENT_ID + " = ?",
+ strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, parentDocumentId),
+ null,
+ null,
+ null);
}
- @VisibleForTesting
- Cursor queryChildDocuments(String[] columnNames, String parentDocumentId, boolean useOldId) {
- final String[] newColumnNames = new String[columnNames.length];
+ /**
+ * Queries a single document.
+ * @param documentId
+ * @param projection
+ * @return Database cursor.
+ */
+ public Cursor queryDocument(String documentId, String[] projection) {
+ return mDatabase.query(
+ TABLE_DOCUMENTS,
+ projection,
+ SELECTION_DOCUMENT_ID,
+ strings(documentId),
+ null,
+ null,
+ null,
+ "1");
+ }
- // TODO: Temporary replace document ID with old format.
- for (int i = 0; i < columnNames.length; i++) {
- if (useOldId && DocumentsContract.Document.COLUMN_DOCUMENT_ID.equals(columnNames[i])) {
- newColumnNames[i] = COLUMN_DEVICE_ID + " || '_' || " + COLUMN_STORAGE_ID +
- " || '_' || IFNULL(" + COLUMN_OBJECT_HANDLE + ",0) AS " +
- DocumentsContract.Document.COLUMN_DOCUMENT_ID;
+ /**
+ * Remove all rows belong to a device.
+ * @param deviceId Device ID.
+ */
+ void removeDeviceRows(int deviceId) {
+ // Call non-recursive version because it anyway deletes all rows in the devices.
+ deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId));
+ }
+
+ /**
+ * Obtains parent document ID.
+ * @param documentId
+ * @return parent document ID.
+ * @throws FileNotFoundException
+ */
+ String getParentId(String documentId) throws FileNotFoundException {
+ final Cursor cursor = mDatabase.query(
+ TABLE_DOCUMENTS,
+ strings(COLUMN_PARENT_DOCUMENT_ID),
+ SELECTION_DOCUMENT_ID,
+ strings(documentId),
+ null,
+ null,
+ null,
+ "1");
+ try {
+ if (cursor.moveToNext()) {
+ return cursor.getString(0);
} else {
- newColumnNames[i] = columnNames[i];
+ throw new FileNotFoundException("Cannot find a row having ID=" + documentId);
}
+ } finally {
+ cursor.close();
}
-
- return mDatabase.queryChildDocuments(newColumnNames, parentDocumentId);
- }
-
- Identifier createIdentifier(String parentDocumentId) {
- return mDatabase.createIdentifier(parentDocumentId);
}
/**
- * {@link MtpDatabaseInternal#removeDeviceRows}
+ * Adds new document under the parent.
+ * The method does not affect invalidated and pending documents because we know the document is
+ * newly added and never mapped with existing ones.
+ * @param parentDocumentId
+ * @param info
+ * @return Document ID of added document.
*/
- void removeDeviceRows(int deviceId) {
- mDatabase.removeDeviceRows(deviceId);
+ String putNewDocument(int deviceId, String parentDocumentId, MtpObjectInfo info) {
+ final ContentValues values = new ContentValues();
+ getChildDocumentValues(values, deviceId, parentDocumentId, info);
+ mDatabase.beginTransaction();
+ try {
+ final long id = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+ mDatabase.setTransactionSuccessful();
+ return Long.toString(id);
+ } finally {
+ mDatabase.endTransaction();
+ }
}
/**
- * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for root documents.
- * @param deviceId Device ID.
+ * Deletes document and its children.
+ * @param documentId
*/
- synchronized void startAddingRootDocuments(int deviceId) {
- final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId);
- if (mMappingMode.containsKey(mappingStateKey)) {
- throw new Error("Mapping for the root has already started.");
- }
- mMappingMode.put(
- mappingStateKey,
- mDatabase.startAddingDocuments(
- SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId)));
+ void deleteDocument(String documentId) {
+ deleteDocumentsAndRootsRecursively(SELECTION_DOCUMENT_ID, strings(documentId));
}
/**
- * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for child of specific documents.
- * @param parentDocumentId Document ID for parent document.
+ * Gets identifier from document ID.
+ * @param documentId Document ID.
+ * @return Identifier.
+ * @throws FileNotFoundException
*/
- @VisibleForTesting
- synchronized void startAddingChildDocuments(String parentDocumentId) {
- final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId);
- if (mMappingMode.containsKey(mappingStateKey)) {
- throw new Error("Mapping for the root has already started.");
+ Identifier createIdentifier(String documentId) throws FileNotFoundException {
+ // Currently documentId is old format.
+ final Cursor cursor = mDatabase.query(
+ TABLE_DOCUMENTS,
+ strings(COLUMN_DEVICE_ID, COLUMN_STORAGE_ID, COLUMN_OBJECT_HANDLE),
+ SELECTION_DOCUMENT_ID,
+ strings(documentId),
+ null,
+ null,
+ null,
+ "1");
+ try {
+ if (cursor.getCount() == 0) {
+ throw new FileNotFoundException("ID is not found.");
+ } else {
+ cursor.moveToNext();
+ return new Identifier(
+ cursor.getInt(0),
+ cursor.getInt(1),
+ cursor.isNull(2) ? Identifier.DUMMY_HANDLE_FOR_ROOT : cursor.getInt(2),
+ documentId);
+ }
+ } finally {
+ cursor.close();
}
- mMappingMode.put(
- mappingStateKey,
- mDatabase.startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId));
}
/**
- * Puts root information to database.
- * @param deviceId Device ID
- * @param resources Resources required to localize root name.
- * @param roots List of root information.
- * @return If roots are added or removed from the database.
+ * Deletes a document, and its root information if the document is a root document.
+ * @param selection Query to select documents.
+ * @param args Arguments for selection.
+ * @return Whether the method deletes rows.
*/
- synchronized boolean putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) {
+ boolean deleteDocumentsAndRootsRecursively(String selection, String[] args) {
mDatabase.beginTransaction();
try {
- final boolean heuristic;
- final String mapColumn;
- switch (mMappingMode.get(getRootDocumentsMappingStateKey(deviceId))) {
- case MAP_BY_MTP_IDENTIFIER:
- heuristic = false;
- mapColumn = COLUMN_STORAGE_ID;
- break;
- case MAP_BY_NAME:
- heuristic = true;
- mapColumn = Document.COLUMN_DISPLAY_NAME;
- break;
- default:
- throw new Error("Unexpected map mode.");
- }
- final ContentValues[] valuesList = new ContentValues[roots.length];
- for (int i = 0; i < roots.length; i++) {
- if (roots[i].mDeviceId != deviceId) {
- throw new IllegalArgumentException();
+ boolean changed = false;
+ final Cursor cursor = mDatabase.query(
+ TABLE_DOCUMENTS,
+ strings(Document.COLUMN_DOCUMENT_ID),
+ selection,
+ args,
+ null,
+ null,
+ null);
+ try {
+ while (cursor.moveToNext()) {
+ if (deleteDocumentsAndRootsRecursively(
+ COLUMN_PARENT_DOCUMENT_ID + "=?",
+ strings(cursor.getString(0)))) {
+ changed = true;
+ }
}
- valuesList[i] = new ContentValues();
- getRootDocumentValues(valuesList[i], resources, roots[i]);
+ } finally {
+ cursor.close();
}
- final boolean changed = mDatabase.putDocuments(
- valuesList,
- SELECTION_ROOT_DOCUMENTS,
- Integer.toString(deviceId),
- heuristic,
- mapColumn);
- final ContentValues values = new ContentValues();
- int i = 0;
- for (final MtpRoot root : roots) {
- // Use the same value for the root ID and the corresponding document ID.
- final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID);
- // If it fails to insert/update documents, the document ID will be set with -1.
- // In this case we don't insert/update root extra information neither.
- if (documentId == null) {
- continue;
- }
- values.put(Root.COLUMN_ROOT_ID, documentId);
- values.put(
- Root.COLUMN_FLAGS,
- Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
- values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace);
- values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity);
- values.put(Root.COLUMN_MIME_TYPES, "");
- mDatabase.putRootExtra(values);
+ if (deleteDocumentsAndRoots(selection, args)) {
+ changed = true;
}
mDatabase.setTransactionSuccessful();
return changed;
@@ -239,94 +308,80 @@ class MtpDatabase {
}
/**
- * Puts document information to database.
- * @param deviceId Device ID
- * @param parentId Parent document ID.
- * @param documents List of document information.
+ * Returns the set of device ID stored in the database.
*/
- @VisibleForTesting
- synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
- final boolean heuristic;
- final String mapColumn;
- switch (mMappingMode.get(getChildDocumentsMappingStateKey(parentId))) {
- case MAP_BY_MTP_IDENTIFIER:
- heuristic = false;
- mapColumn = COLUMN_OBJECT_HANDLE;
- break;
- case MAP_BY_NAME:
- heuristic = true;
- mapColumn = Document.COLUMN_DISPLAY_NAME;
- break;
- default:
- throw new Error("Unexpected map mode.");
- }
- final ContentValues[] valuesList = new ContentValues[documents.length];
- for (int i = 0; i < documents.length; i++) {
- valuesList[i] = new ContentValues();
- getChildDocumentValues(valuesList[i], deviceId, parentId, documents[i]);
+ int[] getDeviceIds() {
+ final Cursor cursor = mDatabase.query(
+ true,
+ TABLE_DOCUMENTS,
+ strings(COLUMN_DEVICE_ID),
+ null,
+ null,
+ null,
+ null,
+ null,
+ null);
+ try {
+ final int[] ids = new int[cursor.getCount()];
+ for (int i = 0; i < ids.length; i++) {
+ cursor.moveToNext();
+ ids[i] = cursor.getInt(0);
+ }
+ return ids;
+ } finally {
+ cursor.close();
}
- mDatabase.putDocuments(
- valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn);
}
- /**
- * Clears mapping between MTP identifier and document/root ID.
- */
- @VisibleForTesting
- synchronized void clearMapping() {
- mDatabase.clearMapping();
- mMappingMode.clear();
+ private boolean deleteDocumentsAndRoots(String selection, String[] args) {
+ mDatabase.beginTransaction();
+ try {
+ int deleted = 0;
+ deleted += mDatabase.delete(
+ TABLE_ROOT_EXTRA,
+ Root.COLUMN_ROOT_ID + " IN (" + SQLiteQueryBuilder.buildQueryString(
+ false,
+ TABLE_DOCUMENTS,
+ new String[] { Document.COLUMN_DOCUMENT_ID },
+ selection,
+ null,
+ null,
+ null,
+ null) + ")",
+ args);
+ deleted += mDatabase.delete(TABLE_DOCUMENTS, selection, args);
+ mDatabase.setTransactionSuccessful();
+ // TODO Remove mappingState.
+ return deleted != 0;
+ } finally {
+ mDatabase.endTransaction();
+ }
}
- /**
- * Stops adding root documents.
- * @param deviceId Device ID.
- * @return True if new rows are added/removed.
- */
- synchronized boolean stopAddingRootDocuments(int deviceId) {
- final String mappingModeKey = getRootDocumentsMappingStateKey(deviceId);
- switch (mMappingMode.get(mappingModeKey)) {
- case MAP_BY_MTP_IDENTIFIER:
- mMappingMode.remove(mappingModeKey);
- return mDatabase.stopAddingDocuments(
- SELECTION_ROOT_DOCUMENTS,
- Integer.toString(deviceId),
- COLUMN_STORAGE_ID);
- case MAP_BY_NAME:
- mMappingMode.remove(mappingModeKey);
- return mDatabase.stopAddingDocuments(
- SELECTION_ROOT_DOCUMENTS,
- Integer.toString(deviceId),
- Document.COLUMN_DISPLAY_NAME);
- default:
- throw new Error("Unexpected mapping state.");
+ private static class OpenHelper extends SQLiteOpenHelper {
+ public OpenHelper(Context context, int flags) {
+ super(context,
+ flags == FLAG_DATABASE_IN_MEMORY ? null : DATABASE_NAME,
+ null,
+ DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(QUERY_CREATE_DOCUMENTS);
+ db.execSQL(QUERY_CREATE_ROOT_EXTRA);
+ db.execSQL(QUERY_CREATE_VIEW_ROOTS);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ throw new UnsupportedOperationException();
}
}
- /**
- * Stops adding documents under the parent.
- * @param parentId Document ID of the parent.
- */
@VisibleForTesting
- synchronized void stopAddingChildDocuments(String parentId) {
- final String mappingModeKey = getChildDocumentsMappingStateKey(parentId);
- switch (mMappingMode.get(mappingModeKey)) {
- case MAP_BY_MTP_IDENTIFIER:
- mDatabase.stopAddingDocuments(
- SELECTION_CHILD_DOCUMENTS,
- parentId,
- COLUMN_OBJECT_HANDLE);
- break;
- case MAP_BY_NAME:
- mDatabase.stopAddingDocuments(
- SELECTION_CHILD_DOCUMENTS,
- parentId,
- Document.COLUMN_DISPLAY_NAME);
- break;
- default:
- throw new Error("Unexpected mapping state.");
- }
- mMappingMode.remove(mappingModeKey);
+ static void deleteDatabase(Context context) {
+ context.deleteDatabase(DATABASE_NAME);
}
/**
@@ -335,8 +390,7 @@ class MtpDatabase {
* @param resources Resources used to get localized root name.
* @param root Root to be converted {@link ContentValues}.
*/
- private static void getRootDocumentValues(
- ContentValues values, Resources resources, MtpRoot root) {
+ static void getRootDocumentValues(ContentValues values, Resources resources, MtpRoot root) {
values.clear();
values.put(COLUMN_DEVICE_ID, root.mDeviceId);
values.put(COLUMN_STORAGE_ID, root.mStorageId);
@@ -347,7 +401,7 @@ class MtpDatabase {
values.put(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources));
values.putNull(Document.COLUMN_SUMMARY);
values.putNull(Document.COLUMN_LAST_MODIFIED);
- values.putNull(Document.COLUMN_ICON);
+ values.put(Document.COLUMN_ICON, R.drawable.ic_root_mtp);
values.put(Document.COLUMN_FLAGS, 0);
values.put(Document.COLUMN_SIZE,
(int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE));
@@ -360,10 +414,12 @@ class MtpDatabase {
* @param parentId Parent document ID of the object.
* @param info MTP object info.
*/
- private void getChildDocumentValues(
+ static void getChildDocumentValues(
ContentValues values, int deviceId, String parentId, MtpObjectInfo info) {
values.clear();
- final String mimeType = CursorHelper.formatTypeToMimeType(info.getFormat());
+ final String mimeType = info.getFormat() == MtpConstants.FORMAT_ASSOCIATION ?
+ DocumentsContract.Document.MIME_TYPE_DIR :
+ MediaFile.getMimeTypeForFormatCode(info.getFormat());
int flag = 0;
if (info.getProtectionStatus() == 0) {
flag |= Document.FLAG_SUPPORTS_DELETE |
@@ -391,19 +447,11 @@ class MtpDatabase {
values.put(Document.COLUMN_SIZE, info.getCompressedSize());
}
- /**
- * @param deviceId Device ID.
- * @return Key for {@link #mMappingMode}.
- */
- private static String getRootDocumentsMappingStateKey(int deviceId) {
- return "RootDocuments/" + deviceId;
- }
-
- /**
- * @param parentDocumentId Document ID for the parent document.
- * @return Key for {@link #mMappingMode}.
- */
- private static String getChildDocumentsMappingStateKey(String parentDocumentId) {
- return "ChildDocuments/" + parentDocumentId;
+ static String[] strings(Object... args) {
+ final String[] results = new String[args.length];
+ for (int i = 0; i < args.length; i++) {
+ results[i] = Objects.toString(args[i]);
+ }
+ return results;
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
index 97c1d293172c..0ead2d5331b7 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
@@ -24,7 +24,7 @@ import android.provider.DocumentsContract.Root;
*/
class MtpDatabaseConstants {
static final int DATABASE_VERSION = 1;
- static final String DATABASE_NAME = null;
+ static final String DATABASE_NAME = "database";
static final int FLAG_DATABASE_IN_MEMORY = 1;
static final int FLAG_DATABASE_IN_FILE = 0;
@@ -129,11 +129,7 @@ class MtpDatabaseConstants {
Root.COLUMN_TITLE + "," +
TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY + " AS " +
Root.COLUMN_SUMMARY + "," +
- // Temporary replace COLUMN_DOCUMENT_ID with old format.
TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " +
- Root.COLUMN_DOCUMENT_ID + "_," +
- TABLE_DOCUMENTS + "." + COLUMN_DEVICE_ID + "|| '_' ||" +
- TABLE_DOCUMENTS + "." + COLUMN_STORAGE_ID + "||'_0' AS " +
Root.COLUMN_DOCUMENT_ID + "," +
TABLE_ROOT_EXTRA + "." + Root.COLUMN_AVAILABLE_BYTES + "," +
TABLE_ROOT_EXTRA + "." + Root.COLUMN_CAPACITY_BYTES + "," +
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 7c0676f23a94..9511e15f21c8 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -20,8 +20,9 @@ import android.content.ContentResolver;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.database.Cursor;
-import android.database.MatrixCursor;
import android.graphics.Point;
+import android.media.MediaFile;
+import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
@@ -56,11 +57,13 @@ public class MtpDocumentsProvider extends DocumentsProvider {
Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
};
+ private final Object mDeviceListLock = new Object();
+
private static MtpDocumentsProvider sSingleton;
private MtpManager mMtpManager;
private ContentResolver mResolver;
- @GuardedBy("mDeviceToolkits")
+ @GuardedBy("mDeviceListLock")
private Map<Integer, DeviceToolkit> mDeviceToolkits;
private RootScanner mRootScanner;
private Resources mResources;
@@ -82,6 +85,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
+ resume();
return true;
}
@@ -97,6 +101,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
mDatabase = database;
mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
+ resume();
}
@Override
@@ -116,39 +121,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
if (projection == null) {
projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
}
- final Identifier identifier = Identifier.createFromDocumentId(documentId);
-
- if (identifier.mObjectHandle != CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
- MtpObjectInfo objectInfo;
- try {
- objectInfo = mMtpManager.getObjectInfo(
- identifier.mDeviceId, identifier.mObjectHandle);
- } catch (IOException e) {
- throw new FileNotFoundException(e.getMessage());
- }
- final MatrixCursor cursor = new MatrixCursor(projection);
- CursorHelper.addToCursor(
- objectInfo,
- new Identifier(identifier.mDeviceId, identifier.mStorageId),
- cursor.newRow());
- return cursor;
- } else {
- MtpRoot[] roots;
- try {
- roots = mMtpManager.getRoots(identifier.mDeviceId);
- } catch (IOException e) {
- throw new FileNotFoundException(e.getMessage());
- }
- for (final MtpRoot root : roots) {
- if (identifier.mStorageId != root.mStorageId)
- continue;
- final MatrixCursor cursor = new MatrixCursor(projection);
- CursorHelper.addToCursor(mResources, root, cursor.newRow());
- return cursor;
- }
- }
-
- throw new FileNotFoundException();
+ return mDatabase.queryDocument(documentId, projection);
}
@Override
@@ -170,7 +143,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
public ParcelFileDescriptor openDocument(
String documentId, String mode, CancellationSignal signal)
throws FileNotFoundException {
- final Identifier identifier = Identifier.createFromDocumentId(documentId);
+ final Identifier identifier = mDatabase.createIdentifier(documentId);
try {
switch (mode) {
case "r":
@@ -195,7 +168,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
String documentId,
Point sizeHint,
CancellationSignal signal) throws FileNotFoundException {
- final Identifier identifier = Identifier.createFromDocumentId(documentId);
+ final Identifier identifier = mDatabase.createIdentifier(documentId);
try {
return new AssetFileDescriptor(
getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
@@ -209,22 +182,24 @@ public class MtpDocumentsProvider extends DocumentsProvider {
@Override
public void deleteDocument(String documentId) throws FileNotFoundException {
try {
- final Identifier identifier = Identifier.createFromDocumentId(documentId);
- final int parentHandle =
- mMtpManager.getParent(identifier.mDeviceId, identifier.mObjectHandle);
+ final Identifier identifier = mDatabase.createIdentifier(documentId);
+ final Identifier parentIdentifier =
+ mDatabase.createIdentifier(mDatabase.getParentId(documentId));
mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
- final Identifier parentIdentifier = new Identifier(
- identifier.mDeviceId, identifier.mStorageId, parentHandle);
+ mDatabase.deleteDocument(documentId);
getDocumentLoader(parentIdentifier).clearTask(parentIdentifier);
- notifyChildDocumentsChange(parentIdentifier.toDocumentId());
+ notifyChildDocumentsChange(parentIdentifier.mDocumentId);
} catch (IOException error) {
+ for (final StackTraceElement element : error.getStackTrace()) {
+ Log.e("hirono", element.toString());
+ }
throw new FileNotFoundException(error.getMessage());
}
}
@Override
public void onTrimMemory(int level) {
- synchronized (mDeviceToolkits) {
+ synchronized (mDeviceListLock) {
for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
toolkit.mDocumentLoader.clearCompletedTasks();
}
@@ -235,19 +210,23 @@ public class MtpDocumentsProvider extends DocumentsProvider {
public String createDocument(String parentDocumentId, String mimeType, String displayName)
throws FileNotFoundException {
try {
- final Identifier parentId = Identifier.createFromDocumentId(parentDocumentId);
+ final Identifier parentId = mDatabase.createIdentifier(parentDocumentId);
final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe();
pipe[0].close(); // 0 bytes for a new document.
- final int objectHandle = mMtpManager.createDocument(
- parentId.mDeviceId,
- new MtpObjectInfo.Builder()
- .setStorageId(parentId.mStorageId)
- .setParent(parentId.mObjectHandle)
- .setFormat(CursorHelper.mimeTypeToFormatType(displayName, mimeType))
- .setName(displayName)
- .build(), pipe[1]);
- final String documentId = new Identifier(parentId.mDeviceId, parentId.mStorageId,
- objectHandle).toDocumentId();
+ final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
+ MtpConstants.FORMAT_ASSOCIATION :
+ MediaFile.getFormatCode(displayName, mimeType);
+ final MtpObjectInfo info = new MtpObjectInfo.Builder()
+ .setStorageId(parentId.mStorageId)
+ .setParent(parentId.mObjectHandle)
+ .setFormat(formatCode)
+ .setName(displayName)
+ .build();
+ final int objectHandle = mMtpManager.createDocument(parentId.mDeviceId, info, pipe[1]);
+ final MtpObjectInfo infoWithHandle =
+ new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build();
+ final String documentId = mDatabase.putNewDocument(
+ parentId.mDeviceId, parentDocumentId, infoWithHandle);
getDocumentLoader(parentId).clearTask(parentId);
notifyChildDocumentsChange(parentDocumentId);
return documentId;
@@ -258,47 +237,26 @@ public class MtpDocumentsProvider extends DocumentsProvider {
}
void openDevice(int deviceId) throws IOException {
- synchronized (mDeviceToolkits) {
+ synchronized (mDeviceListLock) {
mMtpManager.openDevice(deviceId);
- mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
+ mDeviceToolkits.put(
+ deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
}
mRootScanner.resume();
}
void closeDevice(int deviceId) throws IOException, InterruptedException {
- // TODO: Flush the device before closing (if not closed externally).
- synchronized (mDeviceToolkits) {
- getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
- mDeviceToolkits.remove(deviceId);
+ synchronized (mDeviceListLock) {
+ closeDeviceInternal(deviceId);
mDatabase.removeDeviceRows(deviceId);
- mMtpManager.closeDevice(deviceId);
}
mRootScanner.notifyChange();
- if (!hasOpenedDevices()) {
- mRootScanner.pause();
- }
- }
-
- synchronized void closeAllDevices() throws InterruptedException {
- boolean closed = false;
- for (int deviceId : mMtpManager.getOpenedDeviceIds()) {
- try {
- mDatabase.removeDeviceRows(deviceId);
- mMtpManager.closeDevice(deviceId);
- getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
- closed = true;
- } catch (IOException d) {
- Log.d(TAG, "Failed to close the MTP device: " + deviceId);
- }
- }
- if (closed) {
- mRootScanner.notifyChange();
- mRootScanner.pause();
- }
}
boolean hasOpenedDevices() {
- return mMtpManager.getOpenedDeviceIds().length != 0;
+ synchronized (mDeviceListLock) {
+ return mMtpManager.getOpenedDeviceIds().length != 0;
+ }
}
/**
@@ -306,14 +264,18 @@ public class MtpDocumentsProvider extends DocumentsProvider {
*/
@Override
public void shutdown() {
- try {
- closeAllDevices();
- } catch (InterruptedException e) {
- // It should fail unit tests by throwing runtime exception.
- throw new RuntimeException(e.getMessage());
- } finally {
- mDatabase.close();
- super.shutdown();
+ synchronized (mDeviceListLock) {
+ try {
+ for (final int id : mMtpManager.getOpenedDeviceIds()) {
+ closeDeviceInternal(id);
+ }
+ } catch (InterruptedException|IOException e) {
+ // It should fail unit tests by throwing runtime exception.
+ throw new RuntimeException(e);
+ } finally {
+ mDatabase.close();
+ super.shutdown();
+ }
}
}
@@ -324,8 +286,35 @@ public class MtpDocumentsProvider extends DocumentsProvider {
false);
}
+ /**
+ * Reopens MTP devices based on database state.
+ */
+ private void resume() {
+ synchronized (mDeviceListLock) {
+ mDatabase.getMapper().clearMapping();
+ final int[] ids = mDatabase.getDeviceIds();
+ for (final int id : ids) {
+ try {
+ openDevice(id);
+ } catch (IOException exception) {
+ mDatabase.removeDeviceRows(id);
+ }
+ }
+ }
+ }
+
+ private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException {
+ // TODO: Flush the device before closing (if not closed externally).
+ getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
+ mDeviceToolkits.remove(deviceId);
+ mMtpManager.closeDevice(deviceId);
+ if (!hasOpenedDevices()) {
+ mRootScanner.pause();
+ }
+ }
+
private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
- synchronized (mDeviceToolkits) {
+ synchronized (mDeviceListLock) {
final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
if (toolkit == null) {
throw new FileNotFoundException();
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
index 723dc149c15d..9b3c20fdefbe 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
@@ -55,22 +55,20 @@ public class MtpDocumentsService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- if (intent == null) {
- // If intent is null, the service was restarted.
- // TODO: Recover opened devices here.
- return START_STICKY;
- }
- if (intent.getAction().equals(ACTION_OPEN_DEVICE)) {
- final UsbDevice device = intent.<UsbDevice>getParcelableExtra(EXTRA_DEVICE);
- try {
- final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
- provider.openDevice(device.getDeviceId());
- return START_STICKY;
- } catch (IOException error) {
- Log.e(MtpDocumentsProvider.TAG, error.getMessage());
+ // If intent is null, the service was restarted.
+ if (intent != null) {
+ if (intent.getAction().equals(ACTION_OPEN_DEVICE)) {
+ final UsbDevice device = intent.<UsbDevice>getParcelableExtra(EXTRA_DEVICE);
+ try {
+ final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
+ provider.openDevice(device.getDeviceId());
+ return START_STICKY;
+ } catch (IOException error) {
+ Log.e(MtpDocumentsProvider.TAG, error.getMessage());
+ }
+ } else {
+ Log.e(MtpDocumentsProvider.TAG, "Received unknown intent action.");
}
- } else {
- Log.w(MtpDocumentsProvider.TAG, "Received unknown intent action.");
}
stopSelfIfNeeded();
return Service.START_NOT_STICKY;
@@ -78,14 +76,8 @@ public class MtpDocumentsService extends Service {
@Override
public void onDestroy() {
- final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
unregisterReceiver(mReceiver);
mReceiver = null;
- try {
- provider.closeAllDevices();
- } catch (InterruptedException e) {
- Log.e(MtpDocumentsProvider.TAG, e.getMessage());
- }
super.onDestroy();
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
index 714936dff198..cd52f31bf046 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
@@ -32,6 +32,7 @@ import com.android.internal.annotations.VisibleForTesting;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.ArrayList;
/**
* The model wrapping android.mtp API.
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
index affaebd05c16..16523bca454c 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
@@ -22,10 +22,8 @@ import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index b0962dd148b9..df2ab0173a6d 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -113,13 +113,14 @@ final class RootScanner {
for (int deviceId : deviceIds) {
try {
final MtpRoot[] roots = mManager.getRoots(deviceId);
- mDatabase.startAddingRootDocuments(deviceId);
+ mDatabase.getMapper().startAddingRootDocuments(deviceId);
try {
- if (mDatabase.putRootDocuments(deviceId, mResources, roots)) {
+ if (mDatabase.getMapper().putRootDocuments(
+ deviceId, mResources, roots)) {
changed = true;
}
} finally {
- if (mDatabase.stopAddingRootDocuments(deviceId)) {
+ if (mDatabase.getMapper().stopAddingRootDocuments(deviceId)) {
changed = true;
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index a80eb51b43a0..f0b4343a9afd 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -40,11 +40,11 @@ public class DocumentLoaderTest extends AndroidTestCase {
@Override
public void setUp() {
mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, new TestResources(), new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, new TestResources(), new MtpRoot[] {
new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
mManager = new BlockableTestMtpManager(getContext());
mResolver = new TestContentResolver();
mLoader = new DocumentLoader(mManager, mResolver, mDatabase);
@@ -57,7 +57,7 @@ public class DocumentLoaderTest extends AndroidTestCase {
public void testBasic() throws Exception {
final Uri uri = DocumentsContract.buildChildDocumentsUri(
- MtpDocumentsProvider.AUTHORITY, mParentIdentifier.toDocumentId());
+ MtpDocumentsProvider.AUTHORITY, mParentIdentifier.mDocumentId);
setUpDocument(mManager, 40);
mManager.blockDocument(0, 15);
mManager.blockDocument(0, 35);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index 25dd1c83766e..67b06729d389 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -20,10 +20,15 @@ import android.database.Cursor;
import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import java.io.FileNotFoundException;
+
+import static com.android.mtp.MtpDatabase.strings;
+
@SmallTest
public class MtpDatabaseTest extends AndroidTestCase {
private final String[] COLUMN_NAMES = new String[] {
@@ -55,8 +60,8 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
public void testPutRootDocuments() throws Exception {
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""),
new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"")
@@ -75,7 +80,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
assertEquals("displayName", "Device Storage", cursor.getString(5));
assertTrue("summary", cursor.isNull(6));
assertTrue("lastModified", cursor.isNull(7));
- assertTrue("icon", cursor.isNull(8));
+ assertEquals("icon", R.drawable.ic_root_mtp, cursor.getInt(8));
assertEquals("flag", 0, cursor.getInt(9));
assertEquals("size", 1000, cursor.getInt(10));
@@ -97,7 +102,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_ICON,
Root.COLUMN_TITLE,
Root.COLUMN_SUMMARY,
- Root.COLUMN_DOCUMENT_ID + "_",
+ Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_AVAILABLE_BYTES,
Root.COLUMN_CAPACITY_BYTES
});
@@ -106,7 +111,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.moveToNext();
assertEquals(1, cursor.getInt(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
- assertTrue(cursor.isNull(2));
+ assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
assertEquals("Device Storage", cursor.getString(3));
assertTrue(cursor.isNull(4));
assertEquals(1, cursor.getInt(5));
@@ -116,7 +121,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.moveToNext();
assertEquals(2, cursor.getInt(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
- assertTrue(cursor.isNull(2));
+ assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
assertEquals("Device Storage", cursor.getString(3));
assertTrue(cursor.isNull(4));
assertEquals(2, cursor.getInt(5));
@@ -126,7 +131,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.moveToNext();
assertEquals(3, cursor.getInt(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
- assertTrue(cursor.isNull(2));
+ assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
assertEquals("Device /@#%&<>Storage", cursor.getString(3));
assertTrue(cursor.isNull(4));
assertEquals(3, cursor.getInt(5));
@@ -147,8 +152,8 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
public void testPutChildDocuments() throws Exception {
- mDatabase.startAddingChildDocuments("parentId");
- mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId");
+ mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
@@ -222,8 +227,8 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""),
new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "")
});
@@ -254,7 +259,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -282,8 +287,8 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""),
new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "")
});
@@ -321,7 +326,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -356,13 +361,13 @@ public class MtpDatabaseTest extends AndroidTestCase {
MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
};
- mDatabase.startAddingChildDocuments("parentId");
- mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId");
+ mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
{
final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
@@ -386,8 +391,8 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- mDatabase.startAddingChildDocuments("parentId");
- mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId");
+ mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
});
@@ -404,7 +409,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- mDatabase.stopAddingChildDocuments("parentId");
+ mDatabase.getMapper().stopAddingChildDocuments("parentId");
{
final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
@@ -433,12 +438,12 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_ROOT_ID,
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.startAddingRootDocuments(0);
- mDatabase.startAddingRootDocuments(1);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingRootDocuments(1);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, "")
});
- mDatabase.putRootDocuments(1, resources, new MtpRoot[] {
+ mDatabase.getMapper().putRootDocuments(1, resources, new MtpRoot[] {
new MtpRoot(1, 100, "Device", "Storage", 0, 0, "")
});
@@ -468,18 +473,18 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingRootDocuments(0);
- mDatabase.startAddingRootDocuments(1);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingRootDocuments(1);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "")
});
- mDatabase.putRootDocuments(1, resources, new MtpRoot[] {
+ mDatabase.getMapper().putRootDocuments(1, resources, new MtpRoot[] {
new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "")
});
- mDatabase.stopAddingRootDocuments(0);
- mDatabase.stopAddingRootDocuments(1);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(1);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -514,25 +519,25 @@ public class MtpDatabaseTest extends AndroidTestCase {
MtpDatabaseConstants.COLUMN_OBJECT_HANDLE
};
- mDatabase.startAddingChildDocuments("parentId1");
- mDatabase.startAddingChildDocuments("parentId2");
- mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId1");
+ mDatabase.getMapper().startAddingChildDocuments("parentId2");
+ mDatabase.getMapper().putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+ mDatabase.getMapper().putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
createDocument(101, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingChildDocuments("parentId1");
- mDatabase.startAddingChildDocuments("parentId2");
- mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId1");
+ mDatabase.getMapper().startAddingChildDocuments("parentId2");
+ mDatabase.getMapper().putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+ mDatabase.getMapper().putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.stopAddingChildDocuments("parentId1");
+ mDatabase.getMapper().stopAddingChildDocuments("parentId1");
{
final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId1");
@@ -563,23 +568,23 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -611,18 +616,18 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -653,17 +658,17 @@ public class MtpDatabaseTest extends AndroidTestCase {
public void testReplaceExistingRoots() {
// The client code should be able to replace existing rows with new information.
// Add one.
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
// Replace it.
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
{
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
@@ -692,26 +697,155 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
}
- public void _testFailToReplaceExisitingUnmappedRoots() {
+ public void testFailToReplaceExisitingUnmappedRoots() {
// The client code should not be able to replace rows before resolving 'unmapped' rows.
// Add one.
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
+ final Cursor oldCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID));
+ assertEquals(1, oldCursor.getCount());
+
// Add one.
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 101, "Device", "Storage B", 1000, 1000, ""),
});
// Add one more before resolving unmapped documents.
- try {
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
- new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
- });
- fail();
- } catch (Throwable e) {
- assertTrue(e instanceof Error);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 102, "Device", "Storage B", 1000, 1000, ""),
+ });
+ mDatabase.getMapper().stopAddingRootDocuments(0);
+
+ // Because the roots shares the same name, the roots should have new IDs.
+ final Cursor newCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID));
+ assertEquals(2, newCursor.getCount());
+ oldCursor.moveToNext();
+ newCursor.moveToNext();
+ assertFalse(oldCursor.getString(0).equals(newCursor.getString(0)));
+ newCursor.moveToNext();
+ assertFalse(oldCursor.getString(0).equals(newCursor.getString(0)));
+
+ oldCursor.close();
+ newCursor.close();
+ }
+
+ public void testQueryDocument() {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ });
+ mDatabase.getMapper().stopAddingRootDocuments(0);
+
+ final Cursor cursor = mDatabase.queryDocument("1", strings(Document.COLUMN_DISPLAY_NAME));
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals("Device Storage A", cursor.getString(0));
+ cursor.close();
+ }
+
+ public void testGetParentId() throws FileNotFoundException {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ });
+ mDatabase.getMapper().stopAddingRootDocuments(0);
+
+ mDatabase.getMapper().startAddingChildDocuments("1");
+ mDatabase.getMapper().putChildDocuments(
+ 0,
+ "1",
+ new MtpObjectInfo[] {
+ createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
+ });
+ mDatabase.getMapper().stopAddingChildDocuments("1");
+
+ assertEquals("1", mDatabase.getParentId("2"));
+ }
+
+ public void testDeleteDocument() {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ });
+ mDatabase.getMapper().stopAddingRootDocuments(0);
+
+ mDatabase.getMapper().startAddingChildDocuments("1");
+ mDatabase.getMapper().putChildDocuments(
+ 0,
+ "1",
+ new MtpObjectInfo[] {
+ createDocument(200, "dir", MtpConstants.FORMAT_ASSOCIATION, 1024),
+ });
+ mDatabase.getMapper().stopAddingChildDocuments("1");
+
+ mDatabase.getMapper().startAddingChildDocuments("2");
+ mDatabase.getMapper().putChildDocuments(
+ 0,
+ "2",
+ new MtpObjectInfo[] {
+ createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
+ });
+ mDatabase.getMapper().stopAddingChildDocuments("2");
+
+ mDatabase.deleteDocument("2");
+
+ {
+ // Do not query deleted documents.
+ final Cursor cursor =
+ mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1");
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+
+ {
+ // Child document should be deleted also.
+ final Cursor cursor =
+ mDatabase.queryDocument("3", strings(Document.COLUMN_DOCUMENT_ID));
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+ }
+
+ public void testPutNewDocument() {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
+ new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
+ });
+ mDatabase.getMapper().stopAddingRootDocuments(0);
+
+ assertEquals(
+ "2",
+ mDatabase.putNewDocument(
+ 0, "1", createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024)));
+
+ {
+ final Cursor cursor =
+ mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1");
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals("2", cursor.getString(0));
+ cursor.close();
+ }
+
+ // The new document should not be mapped with existing invalidated document.
+ mDatabase.getMapper().clearMapping();
+ mDatabase.getMapper().startAddingChildDocuments("1");
+ mDatabase.putNewDocument(
+ 0,
+ "1",
+ createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024));
+ mDatabase.getMapper().stopAddingChildDocuments("1");
+
+ {
+ final Cursor cursor =
+ mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1");
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals("3", cursor.getString(0));
+ cursor.close();
}
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index cabb08de9a04..dc6f79e37e62 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -23,10 +23,14 @@ import android.net.Uri;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsContract;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import static com.android.mtp.MtpDatabase.strings;
@SmallTest
public class MtpDocumentsProviderTest extends AndroidTestCase {
@@ -42,17 +46,16 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
public void setUp() throws IOException {
mResolver = new TestContentResolver();
mMtpManager = new TestMtpManager(getContext());
- mProvider = new MtpDocumentsProvider();
- mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
- mProvider.onCreateForTesting(mResources, mMtpManager, mResolver, mDatabase);
}
@Override
public void tearDown() {
mProvider.shutdown();
+ MtpDatabase.deleteDatabase(getContext());
}
public void testOpenAndCloseDevice() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mMtpManager.setRoots(0, new MtpRoot[] {
new MtpRoot(
@@ -73,6 +76,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
}
public void testOpenAndCloseErrorDevice() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
try {
mProvider.openDevice(1);
fail();
@@ -104,6 +108,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
}
public void testQueryRoots() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mMtpManager.addValidDevice(1);
mMtpManager.setRoots(0, new MtpRoot[] {
@@ -135,10 +140,9 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
cursor.moveToNext();
assertEquals("1", cursor.getString(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
- // TODO: Add storage icon for MTP devices.
- assertTrue(cursor.isNull(2) /* icon */);
+ assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
assertEquals("Device A Storage A", cursor.getString(3));
- assertEquals("0_1_0", cursor.getString(4));
+ assertEquals("1", cursor.getString(4));
assertEquals(1024, cursor.getInt(5));
}
@@ -151,15 +155,15 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
cursor.moveToNext();
assertEquals("2", cursor.getString(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
- // TODO: Add storage icon for MTP devices.
- assertTrue(cursor.isNull(2) /* icon */);
+ assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
assertEquals("Device B Storage B", cursor.getString(3));
- assertEquals("1_1_0", cursor.getString(4));
+ assertEquals("2", cursor.getString(4));
assertEquals(2048, cursor.getInt(5));
}
}
public void testQueryRoots_error() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mMtpManager.addValidDevice(1);
// Not set roots for device 0 so that MtpManagerMock#getRoots throws IOException.
@@ -183,32 +187,41 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
cursor.moveToNext();
assertEquals("1", cursor.getString(0));
assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
- // TODO: Add storage icon for MTP devices.
- assertTrue(cursor.isNull(2) /* icon */);
+ assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
assertEquals("Device B Storage B", cursor.getString(3));
- assertEquals("1_1_0", cursor.getString(4));
+ assertEquals("1", cursor.getString(4));
assertEquals(2048, cursor.getInt(5));
}
}
- public void testQueryDocument() throws IOException {
+ public void testQueryDocument() throws IOException, InterruptedException, TimeoutException {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
- mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
- .setObjectHandle(2)
- .setStorageId(1)
- .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
- .setName("image.jpg")
- .setDateModified(1422716400000L)
- .setCompressedSize(1024 * 1024 * 5)
- .setThumbCompressedSize(1024 * 50)
- .build());
- final Cursor cursor = mProvider.queryDocument("0_1_2", null);
+
+ setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
+ setupDocuments(
+ 0,
+ 0,
+ MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
+ "1",
+ new MtpObjectInfo[] {
+ new MtpObjectInfo.Builder()
+ .setObjectHandle(100)
+ .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
+ .setName("image.jpg")
+ .setDateModified(1422716400000L)
+ .setCompressedSize(1024 * 1024 * 5)
+ .setThumbCompressedSize(50 * 1024)
+ .build()
+ });
+
+ final Cursor cursor = mProvider.queryDocument("2", null);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
- assertEquals("0_1_2", cursor.getString(0));
+ assertEquals("2", cursor.getString(0));
assertEquals("image/jpeg", cursor.getString(1));
assertEquals("image.jpg", cursor.getString(2));
assertEquals(1422716400000L, cursor.getLong(3));
@@ -220,21 +233,33 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
assertEquals(1024 * 1024 * 5, cursor.getInt(5));
}
- public void testQueryDocument_directory() throws IOException {
+ public void testQueryDocument_directory()
+ throws IOException, InterruptedException, TimeoutException {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
- mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
- .setObjectHandle(2)
- .setStorageId(1)
- .setFormat(MtpConstants.FORMAT_ASSOCIATION)
- .setName("directory")
- .setDateModified(1422716400000L)
- .build());
- final Cursor cursor = mProvider.queryDocument("0_1_2", null);
+
+ setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
+ setupDocuments(
+ 0,
+ 0,
+ MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
+ "1",
+ new MtpObjectInfo[] {
+ new MtpObjectInfo.Builder()
+ .setObjectHandle(2)
+ .setStorageId(1)
+ .setFormat(MtpConstants.FORMAT_ASSOCIATION)
+ .setName("directory")
+ .setDateModified(1422716400000L)
+ .build()
+ });
+
+ final Cursor cursor = mProvider.queryDocument("2", null);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
- assertEquals("0_1_2", cursor.getString(0));
+ assertEquals("2", cursor.getString(0));
assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
assertEquals("directory", cursor.getString(2));
assertEquals(1422716400000L, cursor.getLong(3));
@@ -246,10 +271,13 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
assertEquals(0, cursor.getInt(5));
}
- public void testQueryDocument_forRoot() throws IOException {
+ public void testQueryDocument_forRoot()
+ throws IOException, InterruptedException, TimeoutException {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
- mMtpManager.setRoots(0, new MtpRoot[] {
+
+ setupRoots(0, new MtpRoot[] {
new MtpRoot(
0 /* deviceId */,
1 /* storageId */,
@@ -259,11 +287,11 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
4096 /* total space */,
"" /* no volume identifier */)
});
- final Cursor cursor = mProvider.queryDocument("0_1_0", null);
+ final Cursor cursor = mProvider.queryDocument("1", null);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
- assertEquals("0_1_0", cursor.getString(0));
+ assertEquals("1", cursor.getString(0));
assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
assertEquals("Device A Storage A", cursor.getString(2));
assertTrue(cursor.isNull(3));
@@ -272,44 +300,47 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
}
public void testQueryChildDocuments() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
- mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, mResources, new MtpRoot[] {
- new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
- });
- mDatabase.stopAddingRootDocuments(0);
-
- mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
- .setObjectHandle(1)
- .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
- .setName("image.jpg")
- .setCompressedSize(1024 * 1024 * 5)
- .setThumbCompressedSize(5 * 1024)
- .setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY)
- .build());
-
- final Cursor cursor = mProvider.queryChildDocuments("0_0_0", null, null);
+ setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
+ setupDocuments(
+ 0,
+ 0,
+ MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
+ "1",
+ new MtpObjectInfo[] {
+ new MtpObjectInfo.Builder()
+ .setObjectHandle(100)
+ .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
+ .setName("image.jpg")
+ .setCompressedSize(1024 * 1024 * 5)
+ .setThumbCompressedSize(5 * 1024)
+ .setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY)
+ .build()
+ });
+
+ final Cursor cursor = mProvider.queryChildDocuments("1", null, null);
assertEquals(1, cursor.getCount());
assertTrue(cursor.moveToNext());
- assertEquals("0_0_1", cursor.getString(0));
+ assertEquals("2", cursor.getString(0));
assertEquals("image/jpeg", cursor.getString(1));
assertEquals("image.jpg", cursor.getString(2));
assertEquals(0, cursor.getLong(3));
assertEquals(DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4));
assertEquals(1024 * 1024 * 5, cursor.getInt(5));
- assertFalse(cursor.moveToNext());
+ cursor.close();
}
public void testQueryChildDocuments_cursorError() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
try {
- mProvider.queryChildDocuments("0_0_0", null, null);
+ mProvider.queryChildDocuments("1", null, null);
fail();
} catch (Throwable error) {
assertTrue(error instanceof FileNotFoundException);
@@ -317,44 +348,134 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
}
public void testQueryChildDocuments_documentError() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
+ setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
try {
- mProvider.queryChildDocuments("0_0_0", null, null);
+ mProvider.queryChildDocuments("1", null, null);
fail();
} catch (Throwable error) {
assertTrue(error instanceof FileNotFoundException);
}
}
- public void testDeleteDocument() throws IOException {
+ public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
- mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
- .setObjectHandle(1)
- .setParent(2)
- .build());
- mProvider.deleteDocument("0_0_1");
+ setupRoots(0, new MtpRoot[] {
+ new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
+ });
+ setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
+ new MtpObjectInfo.Builder()
+ .setName("test.txt")
+ .setObjectHandle(1)
+ .setParent(-1)
+ .build()
+ });
+
+ mProvider.deleteDocument("2");
assertEquals(1, mResolver.getChangeCount(
DocumentsContract.buildChildDocumentsUri(
- MtpDocumentsProvider.AUTHORITY, "0_0_2")));
+ MtpDocumentsProvider.AUTHORITY, "1")));
}
- public void testDeleteDocument_error() throws IOException {
+ public void testDeleteDocument_error()
+ throws IOException, InterruptedException, TimeoutException {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
- mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
- .setObjectHandle(2)
- .build());
+ setupRoots(0, new MtpRoot[] {
+ new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
+ });
+ setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
+ new MtpObjectInfo.Builder()
+ .setName("test.txt")
+ .setObjectHandle(1)
+ .setParent(-1)
+ .build()
+ });
try {
- mProvider.deleteDocument("0_0_1");
+ mProvider.deleteDocument("3");
fail();
} catch (Throwable e) {
assertTrue(e instanceof IOException);
}
assertEquals(0, mResolver.getChangeCount(
DocumentsContract.buildChildDocumentsUri(
- MtpDocumentsProvider.AUTHORITY, "0_0_2")));
+ MtpDocumentsProvider.AUTHORITY, "1")));
+ }
+
+ @MediumTest
+ public void testPauseAndResume() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
+ mMtpManager.addValidDevice(0);
+ mProvider.openDevice(0);
+ setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")});
+
+ {
+ final Cursor cursor = mProvider.queryRoots(
+ strings(DocumentsContract.Root.COLUMN_ROOT_ID));
+ cursor.moveToNext();
+ assertEquals(1, cursor.getInt(0));
+ }
+
+ mProvider.shutdown();
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
+
+ {
+ // We can still fetch roots after relaunching the provider.
+ final Cursor cursor = mProvider.queryRoots(
+ strings(DocumentsContract.Root.COLUMN_ROOT_ID));
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals(1, cursor.getInt(0));
+ assertEquals(1, mMtpManager.getOpenedDeviceIds().length);
+ }
+ }
+
+ private void setupProvider(int flag) {
+ mDatabase = new MtpDatabase(getContext(), flag);
+ mProvider = new MtpDocumentsProvider();
+ mProvider.onCreateForTesting(mResources, mMtpManager, mResolver, mDatabase);
+ }
+
+ private String[] getStrings(Cursor cursor) {
+ try {
+ final String[] results = new String[cursor.getCount()];
+ for (int i = 0; cursor.moveToNext(); i++) {
+ results[i] = cursor.getString(0);
+ }
+ return results;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private String[] setupRoots(int deviceId, MtpRoot[] roots)
+ throws FileNotFoundException, InterruptedException, TimeoutException {
+ final int changeCount = mResolver.getChangeCount(ROOTS_URI);
+ mMtpManager.setRoots(deviceId, roots);
+ mResolver.waitForNotification(ROOTS_URI, changeCount + 1);
+ return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID)));
+ }
+
+ private String[] setupDocuments(
+ int deviceId,
+ int storageId,
+ int parentHandle,
+ String parentDocumentId,
+ MtpObjectInfo[] objects) throws FileNotFoundException {
+ final int[] handles = new int[objects.length];
+ int i = 0;
+ for (final MtpObjectInfo info : objects) {
+ handles[i] = info.getObjectHandle();
+ mMtpManager.setObjectInfo(deviceId, info);
+ }
+ mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles);
+ return getStrings(mProvider.queryChildDocuments(
+ parentDocumentId, strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID), null));
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
index 554777160792..a045d06facf0 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
@@ -16,26 +16,18 @@
package com.android.mtp;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.test.InstrumentationTestCase;
import java.io.IOException;
-import java.util.HashMap;
-import java.util.concurrent.CountDownLatch;
@RealDeviceTest
public class MtpManagerTest extends InstrumentationTestCase {
- private static final String ACTION_USB_PERMISSION =
- "com.android.mtp.USB_PERMISSION";
+
private static final int TIMEOUT_MS = 1000;
UsbManager mUsbManager;
MtpManager mManager;
@@ -45,10 +37,8 @@ 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());
+ mUsbDevice = TestUtil.setupMtpDevice(getInstrumentation(), mUsbManager, mManager);
}
@Override
@@ -56,6 +46,11 @@ public class MtpManagerTest extends InstrumentationTestCase {
mManager.closeDevice(mUsbDevice.getDeviceId());
}
+ @Override
+ public TestResultInstrumentation getInstrumentation() {
+ return (TestResultInstrumentation) super.getInstrumentation();
+ }
+
public void testCancelEvent() throws Exception {
final CancellationSignal signal = new CancellationSignal();
final Thread thread = new Thread() {
@@ -64,7 +59,7 @@ public class MtpManagerTest extends InstrumentationTestCase {
try {
mManager.readEvent(mUsbDevice.getDeviceId(), signal);
} catch (OperationCanceledException | IOException e) {
- show(e.getMessage());
+ getInstrumentation().show(e.getMessage());
}
}
};
@@ -74,72 +69,6 @@ public class MtpManagerTest extends InstrumentationTestCase {
thread.join(TIMEOUT_MS);
}
- private void requestPermission(UsbDevice device) throws InterruptedException {
- if (mUsbManager.hasPermission(device)) {
- return;
- }
- final CountDownLatch latch = new CountDownLatch(1);
- final BroadcastReceiver receiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- latch.countDown();
- getInstrumentation().getTargetContext().unregisterReceiver(this);
- }
- };
- getInstrumentation().getTargetContext().registerReceiver(
- receiver, new IntentFilter(ACTION_USB_PERMISSION));
- mUsbManager.requestPermission(device, PendingIntent.getBroadcast(
- getInstrumentation().getTargetContext(),
- 0 /* requstCode */,
- new Intent(ACTION_USB_PERMISSION),
- 0 /* flags */));
- latch.await();
- assertTrue(mUsbManager.hasPermission(device));
- }
-
- private UsbDevice findDevice() throws InterruptedException {
- while (true) {
- final HashMap<String,UsbDevice> devices = mUsbManager.getDeviceList();
- if (devices.size() == 0) {
- show("Wait for devices.");
- Thread.sleep(1000);
- continue;
- }
- final UsbDevice device = devices.values().iterator().next();
- requestPermission(device);
- final UsbDeviceConnection connection = mUsbManager.openDevice(device);
- if (connection == null) {
- fail("Cannot open USB connection.");
- }
- for (int i = 0; i < device.getInterfaceCount(); i++) {
- // Since the test runs real environment, we need to call claim interface with
- // force = true to rob interfaces from other applications.
- connection.claimInterface(device.getInterface(i), true);
- connection.releaseInterface(device.getInterface(i));
- }
- connection.close();
- return device;
- }
- }
-
- private void waitForStorages(MtpManager manager, int deviceId) throws Exception {
- while (true) {
- if (manager.getRoots(deviceId).length == 0) {
- show("Wait for storages.");
- Thread.sleep(1000);
- continue;
- }
- return;
- }
- }
-
- private void show(String message) {
- if (!(getInstrumentation() instanceof TestResultInstrumentation)) {
- return;
- }
- ((TestResultInstrumentation) getInstrumentation()).show(message);
- }
-
private Context getContext() {
return getInstrumentation().getContext();
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
index 7c947f5be147..ccdea033ab6c 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
@@ -44,13 +44,13 @@ public class PipeManagerTest extends AndroidTestCase {
public void testReadDocument_basic() throws Exception {
mtpManager.setImportFileBytes(0, 1, HELLO_BYTES);
final ParcelFileDescriptor descriptor = mPipeManager.readDocument(
- mtpManager, new Identifier(0, 0, 1));
+ mtpManager, new Identifier(0, 0, 1, null));
assertDescriptor(descriptor, HELLO_BYTES);
}
public void testReadDocument_error() throws Exception {
final ParcelFileDescriptor descriptor =
- mPipeManager.readDocument(mtpManager, new Identifier(0, 0, 1));
+ mPipeManager.readDocument(mtpManager, new Identifier(0, 0, 1, null));
assertDescriptorError(descriptor);
}
@@ -62,7 +62,7 @@ public class PipeManagerTest extends AndroidTestCase {
// Upload testing bytes.
final ParcelFileDescriptor descriptor = mPipeManager.writeDocument(
- getContext(), mtpManager, new Identifier(0, 0, 1));
+ getContext(), mtpManager, new Identifier(0, 0, 1, null));
final ParcelFileDescriptor.AutoCloseOutputStream outputStream =
new ParcelFileDescriptor.AutoCloseOutputStream(descriptor);
outputStream.write(HELLO_BYTES, 0, HELLO_BYTES.length);
@@ -94,13 +94,13 @@ public class PipeManagerTest extends AndroidTestCase {
public void testReadThumbnail_basic() throws Exception {
mtpManager.setThumbnail(0, 1, HELLO_BYTES);
final ParcelFileDescriptor descriptor = mPipeManager.readThumbnail(
- mtpManager, new Identifier(0, 0, 1));
+ mtpManager, new Identifier(0, 0, 1, null));
assertDescriptor(descriptor, HELLO_BYTES);
}
public void testReadThumbnail_error() throws Exception {
final ParcelFileDescriptor descriptor =
- mPipeManager.readThumbnail(mtpManager, new Identifier(0, 0, 1));
+ mPipeManager.readThumbnail(mtpManager, new Identifier(0, 0, 1, null));
assertDescriptorError(descriptor);
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
index 0fb0f34eec65..3e64f9a2917a 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.mtp;
import android.os.Bundle;
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java
new file mode 100644
index 000000000000..e6c12cb9e10b
--- /dev/null
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java
@@ -0,0 +1,134 @@
+/*
+ * 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.mtp;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbManager;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
+import junit.framework.Assert;
+
+/**
+ * Static utility methods for testing.
+ */
+class TestUtil {
+ private static final String ACTION_USB_PERMISSION =
+ "com.android.mtp.USB_PERMISSION";
+
+ private TestUtil() {}
+
+ /**
+ * Requests permission for a MTP device and returns the first MTP device that has at least one
+ * storage.
+ * @throws Exception
+ */
+ static UsbDevice setupMtpDevice(
+ TestResultInstrumentation instrumentation,
+ UsbManager usbManager,
+ MtpManager manager) throws Exception {
+ for (int i = 0; i < 2; i++) {
+ final UsbDevice device = findMtpDevice(instrumentation, usbManager);
+ manager.openDevice(device.getDeviceId());
+ try {
+ waitForStorages(instrumentation, manager, device.getDeviceId());
+ return device;
+ } 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;
+ }
+ }
+ throw new IOException("Failed to obtain MTP devices");
+ }
+
+ private static UsbDevice findMtpDevice(
+ TestResultInstrumentation instrumentation,
+ UsbManager usbManager) throws InterruptedException {
+ while (true) {
+ final HashMap<String,UsbDevice> devices = usbManager.getDeviceList();
+ if (devices.size() == 0) {
+ instrumentation.show("Wait for devices.");
+ Thread.sleep(1000);
+ continue;
+ }
+ final UsbDevice device = devices.values().iterator().next();
+ requestPermission(instrumentation, usbManager, device);
+ final UsbDeviceConnection connection = usbManager.openDevice(device);
+ if (connection == null) {
+ Assert.fail("Cannot open USB connection.");
+ return null;
+ }
+ for (int i = 0; i < device.getInterfaceCount(); i++) {
+ // Since the test runs real environment, we need to call claim interface with
+ // force = true to rob interfaces from other applications.
+ connection.claimInterface(device.getInterface(i), true);
+ connection.releaseInterface(device.getInterface(i));
+ }
+ connection.close();
+ return device;
+ }
+ }
+
+ private static void requestPermission(
+ final TestResultInstrumentation instrumentation,
+ UsbManager usbManager,
+ UsbDevice device) throws InterruptedException {
+ if (usbManager.hasPermission(device)) {
+ return;
+ }
+ final CountDownLatch latch = new CountDownLatch(1);
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ latch.countDown();
+ instrumentation.getTargetContext().unregisterReceiver(this);
+ }
+ };
+ instrumentation.getTargetContext().registerReceiver(
+ receiver, new IntentFilter(ACTION_USB_PERMISSION));
+ usbManager.requestPermission(device, PendingIntent.getBroadcast(
+ instrumentation.getTargetContext(),
+ 0 /* requstCode */,
+ new Intent(ACTION_USB_PERMISSION),
+ 0 /* flags */));
+ latch.await();
+ Assert.assertTrue(usbManager.hasPermission(device));
+ }
+
+ private static void waitForStorages(
+ TestResultInstrumentation instrumentation,
+ MtpManager manager,
+ int deviceId) throws Exception {
+ while (true) {
+ if (manager.getRoots(deviceId).length == 0) {
+ instrumentation.show("Wait for storages.");
+ Thread.sleep(1000);
+ continue;
+ }
+ return;
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index f006ccb94247..82fd51233ef2 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -16,6 +16,8 @@
package com.android.printspooler.model;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.Notification.Action;
import android.app.Notification.InboxStyle;
@@ -129,68 +131,69 @@ final class NotificationController {
mContext.getString(R.string.cancel), createCancelIntent(printJob)).build();
}
- private void createPrintingNotification(PrintJobInfo printJob) {
+ /**
+ * Create a notification for a print job.
+ *
+ * @param printJob the job the notification is for
+ * @param firstAction the first action shown in the notification
+ * @param secondAction the second action shown in the notification
+ */
+ private void createNotification(@NonNull PrintJobInfo printJob, @Nullable Action firstAction,
+ @Nullable Action secondAction) {
Notification.Builder builder = new Notification.Builder(mContext)
.setContentIntent(createContentIntent(printJob.getId()))
.setSmallIcon(computeNotificationIcon(printJob))
.setContentTitle(computeNotificationTitle(printJob))
- .addAction(createCancelAction(printJob))
- .setContentText(printJob.getPrinterName())
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.setShowWhen(true)
.setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color));
+
+ if (firstAction != null) {
+ builder.addAction(firstAction);
+ }
+
+ if (secondAction != null) {
+ builder.addAction(secondAction);
+ }
+
+ if (printJob.getState() == PrintJobInfo.STATE_STARTED) {
+ float progress = printJob.getProgress();
+ if (progress >= 0) {
+ builder.setProgress(Integer.MAX_VALUE, (int)(Integer.MAX_VALUE * progress),
+ false);
+ }
+ }
+
+ CharSequence status = printJob.getStatus();
+ if (status != null) {
+ builder.setContentText(status);
+ } else {
+ builder.setContentText(printJob.getPrinterName());
+ }
+
mNotificationManager.notify(0, builder.build());
}
+ private void createPrintingNotification(PrintJobInfo printJob) {
+ createNotification(printJob, createCancelAction(printJob), null);
+ }
+
private void createFailedNotification(PrintJobInfo printJob) {
Action.Builder restartActionBuilder = new Action.Builder(
Icon.createWithResource(mContext, R.drawable.ic_restart),
mContext.getString(R.string.restart), createRestartIntent(printJob.getId()));
- Notification.Builder builder = new Notification.Builder(mContext)
- .setContentIntent(createContentIntent(printJob.getId()))
- .setSmallIcon(computeNotificationIcon(printJob))
- .setContentTitle(computeNotificationTitle(printJob))
- .addAction(createCancelAction(printJob))
- .addAction(restartActionBuilder.build())
- .setContentText(printJob.getPrinterName())
- .setWhen(System.currentTimeMillis())
- .setOngoing(true)
- .setShowWhen(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- mNotificationManager.notify(0, builder.build());
+ createNotification(printJob, createCancelAction(printJob), restartActionBuilder.build());
}
private void createBlockedNotification(PrintJobInfo printJob) {
- Notification.Builder builder = new Notification.Builder(mContext)
- .setContentIntent(createContentIntent(printJob.getId()))
- .setSmallIcon(computeNotificationIcon(printJob))
- .setContentTitle(computeNotificationTitle(printJob))
- .addAction(createCancelAction(printJob))
- .setContentText(printJob.getPrinterName())
- .setWhen(System.currentTimeMillis())
- .setOngoing(true)
- .setShowWhen(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- mNotificationManager.notify(0, builder.build());
+ createNotification(printJob, createCancelAction(printJob), null);
}
private void createCancellingNotification(PrintJobInfo printJob) {
- Notification.Builder builder = new Notification.Builder(mContext)
- .setContentIntent(createContentIntent(printJob.getId()))
- .setSmallIcon(computeNotificationIcon(printJob))
- .setContentTitle(computeNotificationTitle(printJob))
- .setContentText(printJob.getPrinterName())
- .setWhen(System.currentTimeMillis())
- .setOngoing(true)
- .setShowWhen(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- mNotificationManager.notify(0, builder.build());
+ createNotification(printJob, null, null);
}
private void createStackedNotification(List<PrintJobInfo> printJobs) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
index 7adcfec728e7..b92a389c1679 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
@@ -16,6 +16,9 @@
package com.android.printspooler.model;
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
@@ -497,7 +500,7 @@ public final class PrintSpoolerService extends Service {
success = true;
printJob.setState(state);
- printJob.setStateReason(error);
+ printJob.setStatus(error);
printJob.setCancelling(false);
if (DEBUG_PRINT_JOB_LIFECYCLE) {
@@ -547,6 +550,35 @@ public final class PrintSpoolerService extends Service {
return success;
}
+ /**
+ * Set the progress for a print job.
+ *
+ * @param printJobId ID of the print job to update
+ * @param progress the new progress
+ */
+ public void setProgress(@NonNull PrintJobId printJobId,
+ @FloatRange(from=0.0, to=1.0) float progress) {
+ synchronized (mLock) {
+ getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setProgress(progress);
+
+ mNotificationController.onUpdateNotifications(mPrintJobs);
+ }
+ }
+
+ /**
+ * Set the status for a print job.
+ *
+ * @param printJobId ID of the print job to update
+ * @param status the new status
+ */
+ public void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
+ synchronized (mLock) {
+ getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setStatus(status);
+
+ mNotificationController.onUpdateNotifications(mPrintJobs);
+ }
+ }
+
public boolean hasActivePrintJobsLocked() {
final int printJobCount = mPrintJobs.size();
for (int i = 0; i < printJobCount; i++) {
@@ -693,6 +725,8 @@ public final class PrintSpoolerService extends Service {
private static final String ATTR_COPIES = "copies";
private static final String ATTR_PRINTER_NAME = "printerName";
private static final String ATTR_STATE_REASON = "stateReason";
+ private static final String ATTR_STATUS = "status";
+ private static final String ATTR_PROGRESS = "progress";
private static final String ATTR_CANCELLING = "cancelling";
private static final String TAG_ADVANCED_OPTIONS = "advancedOptions";
@@ -801,13 +835,19 @@ public final class PrintSpoolerService extends Service {
if (!TextUtils.isEmpty(printerName)) {
serializer.attribute(null, ATTR_PRINTER_NAME, printerName);
}
- String stateReason = printJob.getStateReason();
- if (!TextUtils.isEmpty(stateReason)) {
- serializer.attribute(null, ATTR_STATE_REASON, stateReason);
- }
serializer.attribute(null, ATTR_CANCELLING, String.valueOf(
printJob.isCancelling()));
+ float progress = printJob.getProgress();
+ if (progress != Float.NaN) {
+ serializer.attribute(null, ATTR_PROGRESS, String.valueOf(progress));
+ }
+
+ CharSequence status = printJob.getStatus();
+ if (!TextUtils.isEmpty(status)) {
+ serializer.attribute(null, ATTR_STATUS, status.toString());
+ }
+
PrinterId printerId = printJob.getPrinterId();
if (printerId != null) {
serializer.startTag(null, TAG_PRINTER_ID);
@@ -1025,8 +1065,25 @@ public final class PrintSpoolerService extends Service {
printJob.setCopies(Integer.parseInt(copies));
String printerName = parser.getAttributeValue(null, ATTR_PRINTER_NAME);
printJob.setPrinterName(printerName);
+
+ String progressString = parser.getAttributeValue(null, ATTR_PROGRESS);
+ if (progressString != null) {
+ float progress = Float.parseFloat(progressString);
+
+ if (progress != Float.NaN) {
+ printJob.setProgress(progress);
+ }
+ }
+
+ CharSequence status = parser.getAttributeValue(null, ATTR_STATUS);
+ printJob.setStatus(status);
+
+ // stateReason is deprecated, but might be used by old print jobs
String stateReason = parser.getAttributeValue(null, ATTR_STATE_REASON);
- printJob.setStateReason(stateReason);
+ if (stateReason != null) {
+ printJob.setStatus(stateReason);
+ }
+
String cancelling = parser.getAttributeValue(null, ATTR_CANCELLING);
printJob.setCancelling(!TextUtils.isEmpty(cancelling)
? Boolean.parseBoolean(cancelling) : false);
@@ -1323,6 +1380,18 @@ public final class PrintSpoolerService extends Service {
.removeApprovedService(serviceToRemove);
}
+ @Override
+ public void setProgress(@NonNull PrintJobId printJobId,
+ @FloatRange(from=0.0, to=1.0) float progress) throws RemoteException {
+ PrintSpoolerService.this.setProgress(printJobId, progress);
+ }
+
+ @Override
+ public void setStatus(@NonNull PrintJobId printJobId,
+ @Nullable CharSequence status) throws RemoteException {
+ PrintSpoolerService.this.setStatus(printJobId, status);
+ }
+
public PrintSpoolerService getService() {
return PrintSpoolerService.this;
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 67e170fa3064..652156542575 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -556,8 +556,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
// Make sure the IME is not on the way of preview as
// the user may have used it to type copies or range.
- InputMethodManager imm = (InputMethodManager) getSystemService(
- Context.INPUT_METHOD_SERVICE);
+ InputMethodManager imm = getSystemService(InputMethodManager.class);
imm.hideSoftInputFromWindow(mDestinationSpinner.getWindowToken(), 0);
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index f4c15bd0690a..ab0b2f1acf85 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -33,10 +33,12 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.database.ContentObserver;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.print.PrintManager;
import android.print.PrinterId;
import android.print.PrinterInfo;
@@ -84,9 +86,7 @@ public final class SelectPrinterActivity extends Activity {
private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID";
- /**
- * If there are any enabled print services
- */
+ /** If there are any enabled print services */
private boolean mHasEnabledPrintServices;
private final ArrayList<PrintServiceInfo> mAddPrinterServices =
@@ -98,6 +98,9 @@ public final class SelectPrinterActivity extends Activity {
private AnnounceFilterResult mAnnounceFilterResult;
+ /** Monitor if new print services get enabled or disabled */
+ private ContentObserver mPrintServicesObserver;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -227,14 +230,50 @@ public final class SelectPrinterActivity extends Activity {
return false;
}
- @Override
- public void onResume() {
- super.onResume();
+ /**
+ * Adjust the UI if the enabled print services changed.
+ */
+ private synchronized void onPrintServicesUpdate() {
updateServicesWithAddPrinterActivity();
updateEmptyView((DestinationAdapter)mListView.getAdapter());
invalidateOptionsMenu();
}
+ /**
+ * Register listener for changes to the enabled print services.
+ */
+ private void registerServiceMonitor() {
+ mPrintServicesObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onPrintServicesUpdate();
+ }
+ };
+
+ getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ENABLED_PRINT_SERVICES), false,
+ mPrintServicesObserver);
+ }
+
+ /**
+ * Unregister {@link #mPrintServicesObserver listener for changes to the enabled print services}
+ * or nothing if the listener is not registered.
+ */
+ private void unregisterServiceMonitorIfNeeded() {
+ if (mPrintServicesObserver != null) {
+ getContentResolver().unregisterContentObserver(mPrintServicesObserver);
+
+ mPrintServicesObserver = null;
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ registerServiceMonitor();
+ onPrintServicesUpdate();
+ }
+
@Override
public void onPause() {
if (mAnnounceFilterResult != null) {
@@ -244,6 +283,12 @@ public final class SelectPrinterActivity extends Activity {
}
@Override
+ public void onStop() {
+ unregisterServiceMonitorIfNeeded();
+ super.onStop();
+ }
+
+ @Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_add_printer) {
showAddPrinterSelectionDialog();
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 8fa5abf09df2..98a171eafa8a 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -72,7 +72,7 @@
<string name="bluetooth_pairing_accept" msgid="6163520056536604875">"Sparuj"</string>
<string name="bluetooth_pairing_accept_all_caps" msgid="6061699265220789149">"SPARUJ"</string>
<string name="bluetooth_pairing_decline" msgid="4185420413578948140">"Anuluj"</string>
- <string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"Parowanie spowoduje przyznanie dostępu do historii połączeń i Twoich kontaktów w trakcie połączenia."</string>
+ <string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"Sparowanie powoduje przyznanie dostępu do historii połączeń i Twoich kontaktów w trakcie połączenia."</string>
<string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"Nie można sparować z urządzeniem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
<string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"Nie można sparować z urządzeniem <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ze względu na błędny kod PIN lub klucz."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"Nie można skomunikować się z urządzeniem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 029962e57e39..c3c287c7a51d 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -38,7 +38,7 @@
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"กำลังตัดการเชื่อมต่อ..."</string>
<string name="bluetooth_connecting" msgid="8555009514614320497">"กำลังเชื่อมต่อ…"</string>
<string name="bluetooth_connected" msgid="6038755206916626419">"เชื่อมต่อแล้ว"</string>
- <string name="bluetooth_pairing" msgid="1426882272690346242">"กำลังกำหนดค่าอุปกรณ์ให้ตรงกัน..."</string>
+ <string name="bluetooth_pairing" msgid="1426882272690346242">"กำลังจับคู่อุปกรณ์..."</string>
<string name="bluetooth_connected_no_headset" msgid="2866994875046035609">"เชื่อมต่อแล้ว (ยกเว้นเสียงโทรศัพท์)"</string>
<string name="bluetooth_connected_no_a2dp" msgid="4576188601581440337">"เชื่อมต่อแล้ว (ยกเว้นเสียงสื่อ)"</string>
<string name="bluetooth_connected_no_map" msgid="6504436917057479986">"เชื่อมต่อแล้ว (ไม่มีการเข้าถึงข้อความ)"</string>
@@ -69,14 +69,14 @@
<string name="bluetooth_headset_profile_summary_use_for" msgid="8705753622443862627">"ใช้สำหรับระบบเสียงของโทรศัพท์"</string>
<string name="bluetooth_opp_profile_summary_use_for" msgid="1255674547144769756">"ใช้สำหรับการโอนไฟล์"</string>
<string name="bluetooth_hid_profile_summary_use_for" msgid="232727040453645139">"ใช้สำหรับการป้อนข้อมูล"</string>
- <string name="bluetooth_pairing_accept" msgid="6163520056536604875">"กำหนดค่าอุปกรณ์ให้ตรงกัน"</string>
+ <string name="bluetooth_pairing_accept" msgid="6163520056536604875">"จับคู่อุปกรณ์"</string>
<string name="bluetooth_pairing_accept_all_caps" msgid="6061699265220789149">"จับคู่อุปกรณ์"</string>
<string name="bluetooth_pairing_decline" msgid="4185420413578948140">"ยกเลิก"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="4982239145676394429">"การจับคู่อุปกรณ์จะให้สิทธิ์การเข้าถึงที่อยู่ติดต่อและประวัติการโทรเมื่อเชื่อมต่อแล้ว"</string>
<string name="bluetooth_pairing_error_message" msgid="3748157733635947087">"ไม่สามารถจับคู่กับ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
<string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"ไม่สามารถจับคู่กับ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ได้เพราะ PIN หรือรหัสผ่านไม่ถูกต้อง"</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"ไม่สามารถเชื่อมต่อกับ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
- <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"การกำหนดค่าอุปกรณ์ให้ตรงกันถูกปฏิเสธโดย <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+ <string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ปฏิเสธการจับคู่อุปกรณ์"</string>
<string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-Fi ปิดอยู่"</string>
<string name="accessibility_no_wifi" msgid="8834610636137374508">"ไม่ได้เชื่อมต่อ Wi-Fi"</string>
<string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"สัญญาณ Wi-Fi 1 ขีด"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardTile.java b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardTile.java
index 6fef134f452c..347e9f742d29 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardTile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardTile.java
@@ -74,6 +74,11 @@ public class DashboardTile implements Parcelable {
*/
public int priority;
+ /**
+ * The metaData from the activity that defines this tile.
+ */
+ public Bundle metaData;
+
public DashboardTile() {
// Empty
}
@@ -107,6 +112,7 @@ public class DashboardTile implements Parcelable {
dest.writeBundle(extras);
dest.writeString(category);
dest.writeInt(priority);
+ dest.writeBundle(metaData);
}
public void readFromParcel(Parcel in) {
@@ -125,6 +131,7 @@ public class DashboardTile implements Parcelable {
extras = in.readBundle();
category = in.readString();
priority = in.readInt();
+ metaData = in.readBundle();
}
DashboardTile(Parcel in) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 6102befdda99..581c810c7444 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -18,9 +18,15 @@ package com.android.settingslib.drawer;
import android.annotation.LayoutRes;
import android.annotation.Nullable;
import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.TypedArray;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.widget.DrawerLayout;
+import android.util.Log;
import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -33,21 +39,30 @@ import android.widget.ListView;
import android.widget.Toolbar;
import com.android.settingslib.R;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class SettingsDrawerActivity extends Activity {
+ protected static final boolean DEBUG_TIMING = false;
+ private static final String TAG = "SettingsDrawerActivity";
+
+ private static List<DashboardCategory> sDashboardCategories;
+ private static HashMap<Pair<String, String>, DashboardTile> sTileCache;
+
+ private final PackageReceiver mPackageReceiver = new PackageReceiver();
+ private final List<CategoryListener> mCategoryListeners = new ArrayList<>();
+
private SettingsDrawerAdapter mDrawerAdapter;
- // Hold on to a cache of tiles to avoid loading the info multiple times.
- private final HashMap<Pair<String, String>, DashboardTile> mTileCache = new HashMap<>();
- private List<DashboardCategory> mDashboardCategories;
private DrawerLayout mDrawerLayout;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ long startTime = System.currentTimeMillis();
+
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.setContentView(R.layout.settings_with_drawer);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
@@ -62,6 +77,10 @@ public class SettingsDrawerActivity extends Activity {
mDrawerLayout = null;
return;
}
+ if (sDashboardCategories == null) {
+ sTileCache = new HashMap<>();
+ sDashboardCategories = TileUtils.getCategories(this, sTileCache);
+ }
setActionBar(toolbar);
mDrawerAdapter = new SettingsDrawerAdapter(this);
ListView listView = (ListView) findViewById(R.id.left_drawer);
@@ -72,6 +91,8 @@ public class SettingsDrawerActivity extends Activity {
onTileClicked(mDrawerAdapter.getTile(position));
};
});
+ if (DEBUG_TIMING) Log.d(TAG, "onCreate took " + (System.currentTimeMillis() - startTime)
+ + " ms");
}
@Override
@@ -88,7 +109,29 @@ public class SettingsDrawerActivity extends Activity {
protected void onResume() {
super.onResume();
- updateDrawer();
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addDataScheme("package");
+ registerReceiver(mPackageReceiver, filter);
+
+ new CategoriesUpdater().execute();
+ }
+
+ @Override
+ protected void onPause() {
+ unregisterReceiver(mPackageReceiver);
+
+ super.onPause();
+ }
+
+ public void addCategoryListener(CategoryListener listener) {
+ mCategoryListeners.add(listener);
+ }
+
+ public void remCategoryListener(CategoryListener listener) {
+ mCategoryListeners.remove(listener);
}
public void openDrawer() {
@@ -134,15 +177,23 @@ public class SettingsDrawerActivity extends Activity {
}
}
- public List<DashboardCategory> getDashboardCategories(boolean force) {
- if (force) {
- mDashboardCategories = TileUtils.getCategories(this, mTileCache);
+ public List<DashboardCategory> getDashboardCategories() {
+ return sDashboardCategories;
+ }
+
+ protected void onCategoriesChanged() {
+ updateDrawer();
+ final int N = mCategoryListeners.size();
+ for (int i = 0; i < N; i++) {
+ mCategoryListeners.get(i).onCategoriesChanged();
}
- return mDashboardCategories;
}
public boolean openTile(DashboardTile tile) {
closeDrawer();
+ if (tile == null) {
+ return false;
+ }
int numUserHandles = tile.userHandle.size();
if (numUserHandles > 1) {
ProfileSelectDialog.show(getFragmentManager(), tile);
@@ -164,4 +215,28 @@ public class SettingsDrawerActivity extends Activity {
public void onProfileTileOpen() {
finish();
}
+
+ public interface CategoryListener {
+ void onCategoriesChanged();
+ }
+
+ private class CategoriesUpdater extends AsyncTask<Void, Void, List<DashboardCategory>> {
+ @Override
+ protected List<DashboardCategory> doInBackground(Void... params) {
+ return TileUtils.getCategories(SettingsDrawerActivity.this, sTileCache);
+ }
+
+ @Override
+ protected void onPostExecute(List<DashboardCategory> dashboardCategories) {
+ sDashboardCategories = dashboardCategories;
+ onCategoriesChanged();
+ }
+ }
+
+ private class PackageReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ new CategoriesUpdater().execute();
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
index fef716c44c57..24c6ae8356ab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
@@ -38,7 +38,7 @@ public class SettingsDrawerAdapter extends BaseAdapter {
}
void updateCategories() {
- List<DashboardCategory> categories = mActivity.getDashboardCategories(true);
+ List<DashboardCategory> categories = mActivity.getDashboardCategories();
mItems.clear();
for (int i = 0; i < categories.size(); i++) {
Item category = new Item();
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index 18e8d31f59d3..6b366801e76a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -14,6 +14,7 @@ import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
@@ -35,6 +36,7 @@ import java.util.Map;
public class TileUtils {
private static final boolean DEBUG = false;
+ private static final boolean DEBUG_TIMING = true;
private static final String LOG_TAG = "TileUtils";
@@ -105,12 +107,9 @@ public class TileUtils {
private static final String SETTING_PKG = "com.android.settings";
- public static List<DashboardCategory> getCategories(Context context) {
- return getCategories(context, new HashMap<Pair<String, String>, DashboardTile>());
- }
-
public static List<DashboardCategory> getCategories(Context context,
HashMap<Pair<String, String>, DashboardTile> cache) {
+ final long startTime = System.currentTimeMillis();
ArrayList<DashboardTile> tiles = new ArrayList<>();
UserManager userManager = UserManager.get(context);
for (UserHandle user : userManager.getUserProfiles()) {
@@ -143,6 +142,8 @@ public class TileUtils {
Collections.sort(category.tiles, TILE_COMPARATOR);
}
Collections.sort(categories, CATEGORY_COMPARATOR);
+ if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took "
+ + (System.currentTimeMillis() - startTime) + " ms");
return categories;
}
@@ -207,7 +208,9 @@ public class TileUtils {
activityInfo.packageName, activityInfo.name);
tile.category = categoryKey;
tile.priority = requireSettings ? resolved.priority : 0;
- updateTileData(context, tile);
+ tile.metaData = activityInfo.metaData;
+ updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
+ pm);
if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
addedCache.put(key, tile);
@@ -231,64 +234,52 @@ public class TileUtils {
return null;
}
- private static boolean updateTileData(Context context, DashboardTile tile) {
- Intent intent = tile.intent;
- if (intent != null) {
- // Find the activity that is in the system image
- PackageManager pm = context.getPackageManager();
- List<ResolveInfo> list = tile.userHandle.size() != 0
- ? pm.queryIntentActivitiesAsUser(intent, PackageManager.GET_META_DATA,
- tile.userHandle.get(0).getIdentifier())
- : pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
- int listSize = list.size();
- for (int i = 0; i < listSize; i++) {
- ResolveInfo resolveInfo = list.get(i);
- if (resolveInfo.activityInfo.applicationInfo.isSystemApp()) {
- int icon = 0;
- CharSequence title = null;
- String summary = null;
-
- // Get the activity's meta-data
- try {
- Resources res = pm.getResourcesForApplication(
- resolveInfo.activityInfo.packageName);
- Bundle metaData = resolveInfo.activityInfo.metaData;
-
- if (res != null && metaData != null) {
- if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
- icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
- }
- if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
- title = metaData.getString(META_DATA_PREFERENCE_TITLE);
- }
- if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
- summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
- }
- }
- } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
- if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);
+ private static boolean updateTileData(Context context, DashboardTile tile,
+ ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm) {
+ if (applicationInfo.isSystemApp()) {
+ int icon = 0;
+ CharSequence title = null;
+ String summary = null;
+
+ // Get the activity's meta-data
+ try {
+ Resources res = pm.getResourcesForApplication(
+ applicationInfo.packageName);
+ Bundle metaData = activityInfo.metaData;
+
+ if (res != null && metaData != null) {
+ if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
+ icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
}
-
- // Set the preference title to the activity's label if no
- // meta-data is found
- if (TextUtils.isEmpty(title)) {
- title = resolveInfo.loadLabel(pm).toString();
+ if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
+ title = metaData.getString(META_DATA_PREFERENCE_TITLE);
}
- if (icon == 0) {
- icon = resolveInfo.activityInfo.icon;
+ if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
+ summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
}
-
- // Set icon, title and summary for the preference
- tile.icon = Icon.createWithResource(resolveInfo.activityInfo.packageName, icon);
- tile.title = title;
- tile.summary = summary;
- // Replace the intent with this specific activity
- tile.intent = new Intent().setClassName(resolveInfo.activityInfo.packageName,
- resolveInfo.activityInfo.name);
-
- return true;
}
+ } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
+ if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);
}
+
+ // Set the preference title to the activity's label if no
+ // meta-data is found
+ if (TextUtils.isEmpty(title)) {
+ title = activityInfo.loadLabel(pm).toString();
+ }
+ if (icon == 0) {
+ icon = activityInfo.icon;
+ }
+
+ // Set icon, title and summary for the preference
+ tile.icon = Icon.createWithResource(activityInfo.packageName, icon);
+ tile.title = title;
+ tile.summary = summary;
+ // Replace the intent with this specific activity
+ tile.intent = new Intent().setClassName(activityInfo.packageName,
+ activityInfo.name);
+
+ return true;
}
return false;
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/SettingsProvider/res/values-it/strings.xml b/packages/SettingsProvider/res/values-it/strings.xml
index 40735ccf7d1b..ba1431d821fa 100644
--- a/packages/SettingsProvider/res/values-it/strings.xml
+++ b/packages/SettingsProvider/res/values-it/strings.xml
@@ -19,5 +19,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_label" msgid="4567566098528588863">"Archiviazione impostazioni"</string>
+ <string name="app_label" msgid="4567566098528588863">"Memoria impostazioni"</string>
</resources>
diff --git a/packages/Shell/Android.mk b/packages/Shell/Android.mk
index 5bd48c63433c..f8c13d63327f 100644
--- a/packages/Shell/Android.mk
+++ b/packages/Shell/Android.mk
@@ -3,7 +3,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
@@ -12,3 +12,5 @@ LOCAL_CERTIFICATE := platform
LOCAL_PRIVILEGED_MODULE := true
include $(BUILD_PACKAGE)
+
+include $(LOCAL_PATH)/tests/Android.mk
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 8c39ee654e6d..25346acd1600 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
- * Copyright (c) 2014 Google Inc.
+ * 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.
@@ -144,8 +144,13 @@
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>
+
+ <service
+ android:name=".BugreportProgressService"
+ android:exported="false"/>
</application>
</manifest>
diff --git a/packages/Shell/res/values-af/strings.xml b/packages/Shell/res/values-af/strings.xml
index d1998bdd39f4..8430bf0d4e91 100644
--- a/packages/Shell/res/values-af/strings.xml
+++ b/packages/Shell/res/values-af/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Tuisskerm"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Besig met foutverslag"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Foutverslag vasgevang"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Swiep na links om jou foutverslag te deel"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Raak om jou foutverslag te deel"</string>
diff --git a/packages/Shell/res/values-am/strings.xml b/packages/Shell/res/values-am/strings.xml
index 6f905a90043b..9400f370139c 100644
--- a/packages/Shell/res/values-am/strings.xml
+++ b/packages/Shell/res/values-am/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"ቀፎ"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"የሳንካ ሪፓርት በሂደት ላይ"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"የሳንካ ሪፖርት ተይዟል"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"የሳንካ ሪፖርትዎን ለማጋራት ወደ ግራ ያንሸራትቱ"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"የሳንካ ሪፖርትዎን ለማጋራት ይንክኩ"</string>
diff --git a/packages/Shell/res/values-ar/strings.xml b/packages/Shell/res/values-ar/strings.xml
index 76100b5684c3..2161a7610420 100644
--- a/packages/Shell/res/values-ar/strings.xml
+++ b/packages/Shell/res/values-ar/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"تقرير الخطأ قيد التقدم"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"تم الحصول على تقرير الأخطاء"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"مرر بسرعة لليمين لمشاركة تقرير الخطأ"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"المس لمشاركة تقرير الأخطاء"</string>
diff --git a/packages/Shell/res/values-az-rAZ/strings.xml b/packages/Shell/res/values-az-rAZ/strings.xml
index d8176f5bcbf4..76ee4bf43cca 100644
--- a/packages/Shell/res/values-az-rAZ/strings.xml
+++ b/packages/Shell/res/values-az-rAZ/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Baq hesabatı davam edir"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Baq raport alındı"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Baq raportunu paylaşmaq üçün sola sürüşdürün"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Xətanı şikayətini paylaşmaq üçün toxunun"</string>
diff --git a/packages/Shell/res/values-bg/strings.xml b/packages/Shell/res/values-bg/strings.xml
index fc2dad03e601..bbfae69ab928 100644
--- a/packages/Shell/res/values-bg/strings.xml
+++ b/packages/Shell/res/values-bg/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Команден ред"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"Отчетът за програмни грешки е записан"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Прекарайте пръст наляво, за да споделите сигнала си за програмна грешка"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Докоснете, за да споделите отчета си за програмни грешки"</string>
diff --git a/packages/Shell/res/values-bn-rBD/strings.xml b/packages/Shell/res/values-bn-rBD/strings.xml
index 5aa3e9d4a0d1..5ca583594c40 100644
--- a/packages/Shell/res/values-bn-rBD/strings.xml
+++ b/packages/Shell/res/values-bn-rBD/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"শেল"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"ত্রুটির প্রতিবেদন করা হচ্ছে"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"ত্রুটির প্রতিবেদন নেওয়া হয়েছে"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"আপনার বাগ রিপোর্ট শেয়ার করতে বামে সোয়াইপ করুন"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"আপনার ত্রুটির প্রতিবেদন ভাগ করতে স্পর্শ করুন"</string>
diff --git a/packages/Shell/res/values-ca/strings.xml b/packages/Shell/res/values-ca/strings.xml
index bef1a674b2f0..1e6ec539783b 100644
--- a/packages/Shell/res/values-ca/strings.xml
+++ b/packages/Shell/res/values-ca/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Protecció"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Informe d\'errors en curs"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"S\'ha registrat l\'informe d\'error"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Llisca cap a l\'esquerra per compartir l\'informe d\'errors."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Toca aquí per compartir el teu informe d\'error."</string>
diff --git a/packages/Shell/res/values-cs/strings.xml b/packages/Shell/res/values-cs/strings.xml
index 46c8f4e5c680..336c21f49a17 100644
--- a/packages/Shell/res/values-cs/strings.xml
+++ b/packages/Shell/res/values-cs/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Probíhá zpracování zprávy o chybě"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Bylo vytvořeno chybové hlášení"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Chcete-li hlášení chyby sdílet, přejeďte doleva."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Chybové hlášení můžete sdílet klepnutím."</string>
diff --git a/packages/Shell/res/values-da/strings.xml b/packages/Shell/res/values-da/strings.xml
index 7a87b9b405c8..6f658944865a 100644
--- a/packages/Shell/res/values-da/strings.xml
+++ b/packages/Shell/res/values-da/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Fejlrapporten er under udførelse"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Fejlrapporten er registreret"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Stryg til venstre for at dele din fejlrapport"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Tryk for at dele din fejlrapport"</string>
diff --git a/packages/Shell/res/values-de/strings.xml b/packages/Shell/res/values-de/strings.xml
index 7a25c4da835d..03c616637bf0 100644
--- a/packages/Shell/res/values-de/strings.xml
+++ b/packages/Shell/res/values-de/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Fehlerbericht in Bearbeitung"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Fehlerbericht erfasst"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Wischen Sie nach links, um Ihren Fehlerbericht zu teilen."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Tippen, um Fehlerbericht zu teilen"</string>
diff --git a/packages/Shell/res/values-el/strings.xml b/packages/Shell/res/values-el/strings.xml
index 02e37f761e2d..ceec189a3148 100644
--- a/packages/Shell/res/values-el/strings.xml
+++ b/packages/Shell/res/values-el/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Κέλυφος"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Αναφορά σφάλματος σε εξέλιξη"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Η λήψη της αναφοράς ήταν επιτυχής"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Σύρετε προς τα αριστερά για κοινή χρήση της αναφοράς σφαλμάτων"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Αγγίξτε για να μοιραστείτε τη αναφορά σφαλμάτων"</string>
diff --git a/packages/Shell/res/values-en-rAU/strings.xml b/packages/Shell/res/values-en-rAU/strings.xml
index 1b55115bf7c4..99c9f4f654a3 100644
--- a/packages/Shell/res/values-en-rAU/strings.xml
+++ b/packages/Shell/res/values-en-rAU/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Bug report in progress"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Bug report captured"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Swipe left to share your bug report"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Touch to share your bug report"</string>
diff --git a/packages/Shell/res/values-en-rGB/strings.xml b/packages/Shell/res/values-en-rGB/strings.xml
index 1b55115bf7c4..99c9f4f654a3 100644
--- a/packages/Shell/res/values-en-rGB/strings.xml
+++ b/packages/Shell/res/values-en-rGB/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Bug report in progress"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Bug report captured"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Swipe left to share your bug report"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Touch to share your bug report"</string>
diff --git a/packages/Shell/res/values-en-rIN/strings.xml b/packages/Shell/res/values-en-rIN/strings.xml
index 1b55115bf7c4..99c9f4f654a3 100644
--- a/packages/Shell/res/values-en-rIN/strings.xml
+++ b/packages/Shell/res/values-en-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Bug report in progress"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Bug report captured"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Swipe left to share your bug report"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Touch to share your bug report"</string>
diff --git a/packages/Shell/res/values-es-rUS/strings.xml b/packages/Shell/res/values-es-rUS/strings.xml
index 1937349355cc..263459e3f460 100644
--- a/packages/Shell/res/values-es-rUS/strings.xml
+++ b/packages/Shell/res/values-es-rUS/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Informe de errores en progreso"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Informe de errores capturado"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Desliza el dedo hacia la izquierda para compartir el informe de errores."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Toca para compartir tu informe de errores."</string>
diff --git a/packages/Shell/res/values-es/strings.xml b/packages/Shell/res/values-es/strings.xml
index 002bea9a1d30..3b37d4046321 100644
--- a/packages/Shell/res/values-es/strings.xml
+++ b/packages/Shell/res/values-es/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Informe de errores en curso"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Informe de error registrado"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Desliza el dedo hacia la izquierda para compartir el informe de error"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Toca para compartir tu informe de error"</string>
diff --git a/packages/Shell/res/values-et-rEE/strings.xml b/packages/Shell/res/values-et-rEE/strings.xml
index a16875c3afc8..652de64e1254 100644
--- a/packages/Shell/res/values-et-rEE/strings.xml
+++ b/packages/Shell/res/values-et-rEE/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Kest"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Veaaruande töötlemine on pooleli"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Veaaruanne jäädvustati"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Veaaruande jagamiseks pühkige vasakule"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Veaaruande jagamiseks puudutage"</string>
diff --git a/packages/Shell/res/values-eu-rES/strings.xml b/packages/Shell/res/values-eu-rES/strings.xml
index e7f1766a73e4..3234786e91b1 100644
--- a/packages/Shell/res/values-eu-rES/strings.xml
+++ b/packages/Shell/res/values-eu-rES/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell-interfazea"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Abian da akatsen txostena egiteko prozesua"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Akatsen txostena jaso da"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Programa-akatsen txostena partekatzeko, pasatu hatza ezkerrera"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Akatsen txostena partekatzeko, ukitu"</string>
diff --git a/packages/Shell/res/values-fa/strings.xml b/packages/Shell/res/values-fa/strings.xml
index 9138b28c2cc7..ff58c25e9d03 100644
--- a/packages/Shell/res/values-fa/strings.xml
+++ b/packages/Shell/res/values-fa/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"گزارش اشکال در حال انجام است"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"گزارش اشکال دریافت شد"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"برای اشتراک‌گذاری گزارش اشکال، به تندی آن را به چپ بکشید"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"جهت اشتراک‌گذاری گزارش اشکال خود لمس کنید"</string>
diff --git a/packages/Shell/res/values-fi/strings.xml b/packages/Shell/res/values-fi/strings.xml
index 079433dbd4c5..df6851c8f470 100644
--- a/packages/Shell/res/values-fi/strings.xml
+++ b/packages/Shell/res/values-fi/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Komentotulkki"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Virheraportti käynnissä"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Virheraportti tallennettu"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Jaa virheraportti pyyhkäisemällä vasemmalle"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Jaa virheraportti koskettamalla tätä"</string>
diff --git a/packages/Shell/res/values-fr-rCA/strings.xml b/packages/Shell/res/values-fr-rCA/strings.xml
index 4ead085ac32d..5f581644c0bf 100644
--- a/packages/Shell/res/values-fr-rCA/strings.xml
+++ b/packages/Shell/res/values-fr-rCA/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Création du rapport de bogue en cours..."</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Rapport de bogue enregistré"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Faites glisser le doigt vers la gauche pour partager votre rapport de bogue."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Appuyer ici pour partager votre rapport de bogue"</string>
diff --git a/packages/Shell/res/values-fr/strings.xml b/packages/Shell/res/values-fr/strings.xml
index 14c1d3b5f263..9562c6cf3cb7 100644
--- a/packages/Shell/res/values-fr/strings.xml
+++ b/packages/Shell/res/values-fr/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Rapport de bug en cours"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Rapport de bug enregistré"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Faites glisser le doigt vers la gauche pour partager votre rapport d\'erreur."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Appuyez ici pour partager le rapport de bug"</string>
diff --git a/packages/Shell/res/values-gl-rES/strings.xml b/packages/Shell/res/values-gl-rES/strings.xml
index 61b0335bbfc5..1f8da5d245c1 100644
--- a/packages/Shell/res/values-gl-rES/strings.xml
+++ b/packages/Shell/res/values-gl-rES/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Informe de erro en curso"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Informe de erros rexistrado"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Pasa o dedo á esquerda para compartir o teu informe de erros"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Toca aquí para compartir o teu informe de erros"</string>
diff --git a/packages/Shell/res/values-gu-rIN/strings.xml b/packages/Shell/res/values-gu-rIN/strings.xml
index 746ac4560ef9..7a94dd75d10b 100644
--- a/packages/Shell/res/values-gu-rIN/strings.xml
+++ b/packages/Shell/res/values-gu-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"શેલ"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"બગ રિપોર્ટ ચાલુ છે"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"બગ રિપોર્ટ કેપ્ચર કરી"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"તમારી બગ રિપોર્ટ શેર કરવા માટે ડાબે સ્વાઇપ કરો"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"તમારી બગ રિપોર્ટ શેર કરવા માટે ટચ કરો"</string>
diff --git a/packages/Shell/res/values-hi/strings.xml b/packages/Shell/res/values-hi/strings.xml
index 97db6078c38f..273b4841b5d6 100644
--- a/packages/Shell/res/values-hi/strings.xml
+++ b/packages/Shell/res/values-hi/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"शेल"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"बग रिपोर्ट प्रगति में है"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"बग रिपोर्ट कैप्चर कर ली गई"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"अपनी बग रिपोर्ट साझा करने के लिए बाएं स्वाइप करें"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"अपनी बग रिपोर्ट साझा करने के लिए स्पर्श करें"</string>
diff --git a/packages/Shell/res/values-hr/strings.xml b/packages/Shell/res/values-hr/strings.xml
index b1fad848870c..3836edb70537 100644
--- a/packages/Shell/res/values-hr/strings.xml
+++ b/packages/Shell/res/values-hr/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Ljuska"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Izvješće o programskoj pogrešci u tijeku"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Prijava programske pogreške snimljena je"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Prijeđite prstom ulijevo da biste poslali izvješće o programskim pogreškama"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Dodirnite za dijeljenje prijave programske pogreške"</string>
diff --git a/packages/Shell/res/values-hu/strings.xml b/packages/Shell/res/values-hu/strings.xml
index 7dae6c1c38a0..9191e02bc553 100644
--- a/packages/Shell/res/values-hu/strings.xml
+++ b/packages/Shell/res/values-hu/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Héj"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Hibajelentés folyamatban"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Programhiba-jelentés rögzítve"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Húzza ujját balra a hibajelentés megosztásához"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Érintse meg a programhiba-jelentés megosztásához"</string>
diff --git a/packages/Shell/res/values-hy-rAM/strings.xml b/packages/Shell/res/values-hy-rAM/strings.xml
index 149b677a222b..bfa9b049531a 100644
--- a/packages/Shell/res/values-hy-rAM/strings.xml
+++ b/packages/Shell/res/values-hy-rAM/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Խեցի"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"Վրիպակի զեկույց է ստացվել"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Սահեցրեք ձախ՝ սխալի հաշվետվությունը համօգտագործելու համար"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Հպեք` ձեր վրիպակի մասին զեկույցը տարածելու համար"</string>
diff --git a/packages/Shell/res/values-in/strings.xml b/packages/Shell/res/values-in/strings.xml
index 534badcae0b9..9a13d8bc1968 100644
--- a/packages/Shell/res/values-in/strings.xml
+++ b/packages/Shell/res/values-in/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Kerangka"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Laporan bug sedang berlangsung"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Laporan bug tercatat"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Gesek ke kiri untuk membagikan laporan bug Anda"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Sentuh untuk membagikan laporan bug Anda"</string>
diff --git a/packages/Shell/res/values-is-rIS/strings.xml b/packages/Shell/res/values-is-rIS/strings.xml
index ce742f6ab795..4304c8e47d45 100644
--- a/packages/Shell/res/values-is-rIS/strings.xml
+++ b/packages/Shell/res/values-is-rIS/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Skipanalína"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Verið er að útbúa villutilkynningu"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Villutilkynning útbúin"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Strjúktu til vinstri til að deila villuskýrslunni"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Snertu til að deila villutilkynningunni"</string>
diff --git a/packages/Shell/res/values-it/strings.xml b/packages/Shell/res/values-it/strings.xml
index 38e360f00b83..63d39d0d4bf0 100644
--- a/packages/Shell/res/values-it/strings.xml
+++ b/packages/Shell/res/values-it/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Segnalazione di bug in corso"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Segnalazione di bug acquisita"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Scorri verso sinistra per condividere il rapporto sui bug"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Tocca per condividere la segnalazione di bug"</string>
diff --git a/packages/Shell/res/values-iw/strings.xml b/packages/Shell/res/values-iw/strings.xml
index ab9aecf19045..94a167f236d6 100644
--- a/packages/Shell/res/values-iw/strings.xml
+++ b/packages/Shell/res/values-iw/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"מעטפת"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"הדוח על הבאג מתבצע כעת"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"דוח הבאגים צולם"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"החלק שמאלה כדי לשתף את דוח הבאגים"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"גע כדי לשתף את דוח הבאגים שלך"</string>
diff --git a/packages/Shell/res/values-ja/strings.xml b/packages/Shell/res/values-ja/strings.xml
index 619ee4f3259a..77adb37f548f 100644
--- a/packages/Shell/res/values-ja/strings.xml
+++ b/packages/Shell/res/values-ja/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"シェル"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"バグレポートを処理しています"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"バグレポートが記録されました"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"バグレポートを共有するには左にスワイプ"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"バグレポートを共有するにはタップします"</string>
diff --git a/packages/Shell/res/values-ka-rGE/strings.xml b/packages/Shell/res/values-ka-rGE/strings.xml
index f98045f242a8..2521d3ee365e 100644
--- a/packages/Shell/res/values-ka-rGE/strings.xml
+++ b/packages/Shell/res/values-ka-rGE/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"გარეკანი"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"ანგარიში ხარვეზების შესახებ შექმნილია"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"გაასრიალეთ მარცხნივ თქვენი ხარვეზის შეტყობინების გასაზიარებლად"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"შეეხეთ თქვენი ხარვეზების ანგარიშის გასაზიარებლად"</string>
diff --git a/packages/Shell/res/values-kk-rKZ/strings.xml b/packages/Shell/res/values-kk-rKZ/strings.xml
index a24f2ccf64db..5ce7815a992f 100644
--- a/packages/Shell/res/values-kk-rKZ/strings.xml
+++ b/packages/Shell/res/values-kk-rKZ/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Қабыршық"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"Вирус туралы баянат қабылданды"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Қате туралы есепті бөлісу үшін солға жанаңыз"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Бөліс үшін, вирус туралы баянатты түртіңіз."</string>
diff --git a/packages/Shell/res/values-km-rKH/strings.xml b/packages/Shell/res/values-km-rKH/strings.xml
index 2439248a5a7c..2814a73fdf83 100644
--- a/packages/Shell/res/values-km-rKH/strings.xml
+++ b/packages/Shell/res/values-km-rKH/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"សែល"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"កំពុងដំណើរការរបាយការណ៍កំហុស"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"បាន​ចាប់​យក​របាយការណ៍​កំហុស"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"អូស​ទៅ​ឆ្វេង​​ ដើម្បី​ចែក​រំលែក​របាយការណ៍​កំហុស​របស់​អ្នក"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"ប៉ះ​ ដើម្បី​ចែក​រំលែក​របាយការណ៍​កំហុស​របស់​អ្នក"</string>
diff --git a/packages/Shell/res/values-kn-rIN/strings.xml b/packages/Shell/res/values-kn-rIN/strings.xml
index bca9c45db051..be34c8515179 100644
--- a/packages/Shell/res/values-kn-rIN/strings.xml
+++ b/packages/Shell/res/values-kn-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"ಶೆಲ್"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"ದೋಷ ವರದಿ ಪ್ರಗತಿಯಲ್ಲಿದೆ"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"ದೋಷದ ವರದಿಯನ್ನು ಸೆರೆಹಿಡಿಯಲಾಗಿದೆ"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"ನಿಮ್ಮ ದೋಷ ವರದಿಯನ್ನು ಹಂಚಿಕೊಳ್ಳಲು ಎಡಕ್ಕೆ ಸ್ವೈಪ್‌ ಮಾಡಿ"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"ನಿಮ್ಮ ದೋಷದ ವರದಿಯನ್ನು ಹಂಚಿಕೊಳ್ಳಲು ಸ್ಪರ್ಶಿಸಿ"</string>
diff --git a/packages/Shell/res/values-ko/strings.xml b/packages/Shell/res/values-ko/strings.xml
index a3a0bf39d16b..46c0daa8367d 100644
--- a/packages/Shell/res/values-ko/strings.xml
+++ b/packages/Shell/res/values-ko/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"셸"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"버그 신고서 캡처됨"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"왼쪽으로 스와이프하여 버그 신고서를 공유하세요."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"버그 신고서를 공유하려면 터치하세요."</string>
diff --git a/packages/Shell/res/values-ky-rKG/strings.xml b/packages/Shell/res/values-ky-rKG/strings.xml
index 622920afca8e..aa8c5385a4bc 100644
--- a/packages/Shell/res/values-ky-rKG/strings.xml
+++ b/packages/Shell/res/values-ky-rKG/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Командалык кабык"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"Ката тууралуу билдирүү түзүлдү"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Ката жөнүндө кабар менен бөлүшүү үчүн солго серпип коюңуз"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Ката тууралуу билдирүүңүздү жөнөтүш үчүн, тийиңиз"</string>
diff --git a/packages/Shell/res/values-lo-rLA/strings.xml b/packages/Shell/res/values-lo-rLA/strings.xml
index 537f1971ab14..caf11c4ae40c 100644
--- a/packages/Shell/res/values-lo-rLA/strings.xml
+++ b/packages/Shell/res/values-lo-rLA/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"ລາຍງານບັນຫາພວມດຳເນີນຢູ່"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"ລາຍງານຈຸດບົກພ່ອງຖືກເກັບກຳແລ້ວ"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"​ປັດ​ໄປ​ຊ້າຍ​ເພື່ອ​ສົ່ງ​ລາຍ​ງານ​ຂໍ້​ຜິດ​ພາດ​ຂອງ​ທ່ານ"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"ແຕະເພື່ອສົ່ງການລາຍງານປັນຫາຂອງທ່ານ"</string>
diff --git a/packages/Shell/res/values-lt/strings.xml b/packages/Shell/res/values-lt/strings.xml
index 6965e4c786d9..e70cf9f7fa6f 100644
--- a/packages/Shell/res/values-lt/strings.xml
+++ b/packages/Shell/res/values-lt/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Apvalkalas"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"Riktų ataskaita užfiksuota"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Perbraukite kairėn, kad bendrintumėte rikto ataskaitą"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Palieskite, kad bendrintumėte riktų ataskaitą"</string>
diff --git a/packages/Shell/res/values-lv/strings.xml b/packages/Shell/res/values-lv/strings.xml
index efc40f351bcf..c5d10bba3bc8 100644
--- a/packages/Shell/res/values-lv/strings.xml
+++ b/packages/Shell/res/values-lv/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Aizsargs"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"Izveidots kļūdu pārskats"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Velciet pa kreisi, lai kopīgotu savu kļūdu ziņojumu."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Pieskarieties, lai kopīgotu kļūdu pārskatu."</string>
diff --git a/packages/Shell/res/values-mk-rMK/strings.xml b/packages/Shell/res/values-mk-rMK/strings.xml
index 7571c2cd5be1..4baa86a5c04b 100644
--- a/packages/Shell/res/values-mk-rMK/strings.xml
+++ b/packages/Shell/res/values-mk-rMK/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Обвивка"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Извештајот за грешка е во тек"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Извештајот за грешка е снимен"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Повлечете налево за да споделите пријава за грешка"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Допри да се сподели твојот извештај за грешка"</string>
diff --git a/packages/Shell/res/values-ml-rIN/strings.xml b/packages/Shell/res/values-ml-rIN/strings.xml
index 4779e4d71952..5dd7763a4cf6 100644
--- a/packages/Shell/res/values-ml-rIN/strings.xml
+++ b/packages/Shell/res/values-ml-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"ഷെൽ"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"ബഗ് റിപ്പോർട്ട് പുരോഗതിയിലാണ്"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"ബഗ് റിപ്പോർട്ട് ക്യാപ്‌ചർ ചെയ്‌തു"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"നിങ്ങളുടെ ബഗ് റിപ്പോർട്ട് പങ്കിടുന്നതിന് ഇടത്തേയ്‌ക്ക് സ്വൈപ്പുചെയ്യുക"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"നിങ്ങളുടെ ബഗ് റിപ്പോർട്ട് പങ്കിടാൻ സ്‌പർശിക്കുക"</string>
diff --git a/packages/Shell/res/values-mn-rMN/strings.xml b/packages/Shell/res/values-mn-rMN/strings.xml
index 71ad7f6c8dbb..f7a9688fddda 100644
--- a/packages/Shell/res/values-mn-rMN/strings.xml
+++ b/packages/Shell/res/values-mn-rMN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Шел"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Алдааны тайлан үргэлжилж байна"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Алдааны мэдээлэл хүлээн авав"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Өөрийн согог репортыг хуваалцахын тулд зүүн шудрана уу"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Та алдааны мэдэгдлийг хуваалцах бол хүрнэ үү"</string>
diff --git a/packages/Shell/res/values-mr-rIN/strings.xml b/packages/Shell/res/values-mr-rIN/strings.xml
index c32bb5be7319..e0db42b76082 100644
--- a/packages/Shell/res/values-mr-rIN/strings.xml
+++ b/packages/Shell/res/values-mr-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"शेल"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"दोष अहवाल प्रगतीपथावर आहे"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"दोष अहवाल कॅप्‍चर केला"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"आपला दोष अहवाल सामायिक करण्यासाठी डावीकडे स्वाइप करा"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"आपला दोष अहवाल सामायिक करण्‍यासाठी स्‍पर्श करा"</string>
diff --git a/packages/Shell/res/values-ms-rMY/strings.xml b/packages/Shell/res/values-ms-rMY/strings.xml
index 852a9e44fad6..bbbb42a5c973 100644
--- a/packages/Shell/res/values-ms-rMY/strings.xml
+++ b/packages/Shell/res/values-ms-rMY/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"Laporan pepijat telah ditangkap"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Leret ke kiri untuk berkongsi laporan pepijat anda"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Sentuh untuk berkongsi laporan pepijat anda"</string>
diff --git a/packages/Shell/res/values-my-rMM/strings.xml b/packages/Shell/res/values-my-rMM/strings.xml
index 4ba9037ce2a2..d49dc1519898 100644
--- a/packages/Shell/res/values-my-rMM/strings.xml
+++ b/packages/Shell/res/values-my-rMM/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"အခွံ"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"ချွတ်ယွင်းချက် အစီရင်ခံစာ ပြုလုပ်ဆဲ"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"အမှားအယွင်းမှတ်တမ်းကို အောင်မြင်စွာ သိမ်းဆည်းပြီး"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"သင်၏ ဘာဂ် အစီရင်ခံစာကို မျှပေးရန် ဘယ်ဘက်သို့ ပွတ်ဆွဲရန်"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"အမှားအယွင်း မှတ်တမ်းကို မျှဝေရန် ထိလိုက်ပါ"</string>
diff --git a/packages/Shell/res/values-nb/strings.xml b/packages/Shell/res/values-nb/strings.xml
index c543e49cbb9f..d3aafdd62a61 100644
--- a/packages/Shell/res/values-nb/strings.xml
+++ b/packages/Shell/res/values-nb/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Kommandoliste"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Feilrapport pågår"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Feilrapporten er lagret"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Sveip til venstre for å dele feilrapporten din"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Trykk for å dele feilrapporten din"</string>
diff --git a/packages/Shell/res/values-ne-rNP/strings.xml b/packages/Shell/res/values-ne-rNP/strings.xml
index f57112ac329a..fb81e558f558 100644
--- a/packages/Shell/res/values-ne-rNP/strings.xml
+++ b/packages/Shell/res/values-ne-rNP/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"सेल"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"बग प्रतिवेदन समातियो"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"तपाईँको बग रिपोर्ट साझेदारी गर्न बायाँ स्वाइप गर्नुहोस्"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"तपाईंको बग रिपोर्ट साझेदारी गर्न छुनुहोस्"</string>
diff --git a/packages/Shell/res/values-nl/strings.xml b/packages/Shell/res/values-nl/strings.xml
index 1519454c2b48..a097feabfa70 100644
--- a/packages/Shell/res/values-nl/strings.xml
+++ b/packages/Shell/res/values-nl/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Bugrapport wordt uitgevoerd"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Foutenrapport vastgelegd"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Veeg naar links om je bugmelding te delen"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Raak aan om je foutenrapport te delen"</string>
diff --git a/packages/Shell/res/values-pa-rIN/strings.xml b/packages/Shell/res/values-pa-rIN/strings.xml
index 3f59bb9ea90a..54e30d6f58cb 100644
--- a/packages/Shell/res/values-pa-rIN/strings.xml
+++ b/packages/Shell/res/values-pa-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"ਸ਼ੈਲ"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"ਬੱਗ ਰਿਪੋਰਟ \'ਤੇ ਕਾਰਵਾਈ ਹੋ ਰਹੀ ਹੈ"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"ਬਗ ਰਿਪੋਰਟ ਕੈਪਚਰ ਕੀਤੀ"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"ਤੁਹਾਡੀ ਬਗ ਰਿਪੋਰਟ ਸ਼ੇਅਰ ਕਰਨ ਲਈ ਖੱਬੇ ਪਾਸੇ ਸਵਾਈਪ ਕਰੋ"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"ਆਪਣੀ ਬਗ ਰਿਪੋਰਟ ਸ਼ੇਅਰ ਕਰਨ ਲਈ ਛੋਹਵੋ"</string>
diff --git a/packages/Shell/res/values-pl/strings.xml b/packages/Shell/res/values-pl/strings.xml
index accc92e83634..599f8c19e3e6 100644
--- a/packages/Shell/res/values-pl/strings.xml
+++ b/packages/Shell/res/values-pl/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Powłoka"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Trwa zgłaszanie błędu"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Raport o błędach został zapisany"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Przesuń palcem w lewo, by udostępnić swoje zgłoszenie błędu"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Kliknij, by udostępnić raport o błędach"</string>
diff --git a/packages/Shell/res/values-pt-rBR/strings.xml b/packages/Shell/res/values-pt-rBR/strings.xml
index 7d4b0d310a9e..a1a9ad1fe690 100644
--- a/packages/Shell/res/values-pt-rBR/strings.xml
+++ b/packages/Shell/res/values-pt-rBR/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Relatório de bugs em andamento"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Relatório de bugs capturado"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Deslize para a esquerda para compartilhar seu relatório de bugs"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Toque para compartilhar seu relatório de bugs"</string>
diff --git a/packages/Shell/res/values-pt-rPT/strings.xml b/packages/Shell/res/values-pt-rPT/strings.xml
index 007d6834f3ce..2e25f8c98ab4 100644
--- a/packages/Shell/res/values-pt-rPT/strings.xml
+++ b/packages/Shell/res/values-pt-rPT/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Relatório de erro em curso"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Relatório de erros capturado"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Deslizar rapidamente para a esquerda para partilhar o seu relatório de erros"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Toque para partilhar o relatório de erros"</string>
diff --git a/packages/Shell/res/values-pt/strings.xml b/packages/Shell/res/values-pt/strings.xml
index 7d4b0d310a9e..a1a9ad1fe690 100644
--- a/packages/Shell/res/values-pt/strings.xml
+++ b/packages/Shell/res/values-pt/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Relatório de bugs em andamento"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Relatório de bugs capturado"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Deslize para a esquerda para compartilhar seu relatório de bugs"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Toque para compartilhar seu relatório de bugs"</string>
diff --git a/packages/Shell/res/values-ro/strings.xml b/packages/Shell/res/values-ro/strings.xml
index e7395e1fc4f1..d8a3b92a2b1d 100644
--- a/packages/Shell/res/values-ro/strings.xml
+++ b/packages/Shell/res/values-ro/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Raportul de eroare se creează"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Raportul despre erori a fost creat"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Glisați la stânga pentru a trimite raportul de erori"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Atingeți pentru a permite accesul la raportul despre erori"</string>
diff --git a/packages/Shell/res/values-ru/strings.xml b/packages/Shell/res/values-ru/strings.xml
index c9276cf10483..436a5905f66e 100644
--- a/packages/Shell/res/values-ru/strings.xml
+++ b/packages/Shell/res/values-ru/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Оболочка"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Отправка сообщения об ошибке..."</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Отчет об ошибке сохранен"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Проведите влево, чтобы отправить отчет"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Нажмите, чтобы отправить отчет об ошибках"</string>
diff --git a/packages/Shell/res/values-si-rLK/strings.xml b/packages/Shell/res/values-si-rLK/strings.xml
index 937c1bc8014b..a0bc5f68bf98 100644
--- a/packages/Shell/res/values-si-rLK/strings.xml
+++ b/packages/Shell/res/values-si-rLK/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"ෂෙල්"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"දෝෂය වාර්තා කිරීම සිදු කරමින් පවතී"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"දෝෂ වාර්තාව ලබාගන්නා ලදි"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"ඔබගේ දෝෂ වාර්තාව බෙදාගැනීමට වමට ස්වයිප් කරන්න"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"ඔබගේ දෝෂ වාර්තාව බෙදා ගැනීමට ස්පර්ශ කරන්න"</string>
diff --git a/packages/Shell/res/values-sk/strings.xml b/packages/Shell/res/values-sk/strings.xml
index cd52efc0e1ef..e568946b0dc9 100644
--- a/packages/Shell/res/values-sk/strings.xml
+++ b/packages/Shell/res/values-sk/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Prostredie"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Prebieha hlásenie chyby"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Hlásenie o chybách bolo vytvorené"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Ak chcete hlásenie o chybe zdieľať, prejdite prstom doľava."</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Hlásenie o chybách môžete zdielať klepnutím"</string>
diff --git a/packages/Shell/res/values-sl/strings.xml b/packages/Shell/res/values-sl/strings.xml
index 25553f20bab3..6ac5ac8cd8ef 100644
--- a/packages/Shell/res/values-sl/strings.xml
+++ b/packages/Shell/res/values-sl/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Lupina"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Poročanje o napakah poteka"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Poročilo o napaki je posneto"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Povlecite v levo, če želite poslati sporočilo o napaki"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Dotaknite se, če želite deliti sporočilo o napaki z drugimi"</string>
diff --git a/packages/Shell/res/values-sq-rAL/strings.xml b/packages/Shell/res/values-sq-rAL/strings.xml
index add53d85ae02..54f2aad0abe3 100644
--- a/packages/Shell/res/values-sq-rAL/strings.xml
+++ b/packages/Shell/res/values-sq-rAL/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Guaska"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Raporti i gabimeve në kod është në progres"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Raporti i defektit në kod u regjistrua"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Rrëshqit majtas për të ndarë raportin e defektit në kod"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Prek për të ndarë raportin e defektit në kod"</string>
diff --git a/packages/Shell/res/values-sr/strings.xml b/packages/Shell/res/values-sr/strings.xml
index bb60f3f06468..81cde0007c4b 100644
--- a/packages/Shell/res/values-sr/strings.xml
+++ b/packages/Shell/res/values-sr/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Прављење извештаја о грешци је у току"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Извештај о грешци је снимљен"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Превуците улево да бисте делили извештај о грешкама"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Додирните да бисте делили извештај о грешци"</string>
diff --git a/packages/Shell/res/values-sv/strings.xml b/packages/Shell/res/values-sv/strings.xml
index dd23e16e2259..2287319992f6 100644
--- a/packages/Shell/res/values-sv/strings.xml
+++ b/packages/Shell/res/values-sv/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Skal"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Felrapportering pågår"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Felrapporten har skapats"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Svep åt vänster om du vill dela felrapporten"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Tryck om du vill dela felrapporten"</string>
diff --git a/packages/Shell/res/values-sw/strings.xml b/packages/Shell/res/values-sw/strings.xml
index 3773cd70db3a..adb97dde6768 100644
--- a/packages/Shell/res/values-sw/strings.xml
+++ b/packages/Shell/res/values-sw/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Ganda"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Mchakato wa kuripoti hitilafu unaendelea"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Ripoti ya hitilafu imenaswa"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Telezesha kidole kushoto ili ushiriki ripoti yako ya hitilafu"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Gusa ili ushiriki ripoti yako ya hitilafu"</string>
diff --git a/packages/Shell/res/values-ta-rIN/strings.xml b/packages/Shell/res/values-ta-rIN/strings.xml
index 244c92949720..15a6242c4370 100644
--- a/packages/Shell/res/values-ta-rIN/strings.xml
+++ b/packages/Shell/res/values-ta-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"ஷெல்"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"பிழை அறிக்கை செயலிலுள்ளது"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"பிழை அறிக்கைகள் படமெடுக்கப்பட்டன"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"பிழை அறிக்கையைப் பகிர இடது புறமாகத் தேய்க்கவும்"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"உங்கள் பிழை அறிக்கையைப் பகிர, தொடவும்"</string>
diff --git a/packages/Shell/res/values-te-rIN/strings.xml b/packages/Shell/res/values-te-rIN/strings.xml
index cd9b4b7eaebc..cd951a267ff2 100644
--- a/packages/Shell/res/values-te-rIN/strings.xml
+++ b/packages/Shell/res/values-te-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"షెల్"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"బగ్ నివేదిక ప్రోగ్రెస్‌లో ఉంది"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"బగ్ నివేదిక క్యాప్చర్ చేయబడింది"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"మీ బగ్ నివేదికను భాగస్వామ్యం చేయడానికి ఎడమవైపుకు స్వైప్ చేయండి"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"మీ బగ్ నివేదికను భాగస్వామ్యం చేయడానికి తాకండి"</string>
diff --git a/packages/Shell/res/values-th/strings.xml b/packages/Shell/res/values-th/strings.xml
index 35b7ec9a3d8e..c61ffeb251e0 100644
--- a/packages/Shell/res/values-th/strings.xml
+++ b/packages/Shell/res/values-th/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"รายงานข้อบกพร่องอยู่ระหว่างดำเนินการ"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"จับภาพรายงานข้อบกพร่องแล้ว"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"กวาดไปทางซ้ายเพื่อแชร์รายงานข้อบกพร่อง"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"แตะเพื่อแชร์รายงานข้อบกพร่องของคุณ"</string>
diff --git a/packages/Shell/res/values-tl/strings.xml b/packages/Shell/res/values-tl/strings.xml
index 21df2069cd23..3e9a68d2c680 100644
--- a/packages/Shell/res/values-tl/strings.xml
+++ b/packages/Shell/res/values-tl/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Kasalukuyang ginagawa ang ulat ng bug"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Na-capture ang ulat ng bug"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Mag-swipe pakaliwa upang ibahagi ang iyong ulat ng bug"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Pindutin upang ibahagi ang iyong ulat ng bug"</string>
diff --git a/packages/Shell/res/values-tr/strings.xml b/packages/Shell/res/values-tr/strings.xml
index fa8b82b75191..f90dae03cc7a 100644
--- a/packages/Shell/res/values-tr/strings.xml
+++ b/packages/Shell/res/values-tr/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Kabuk"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Hata raporu hazırlanıyor"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Hata raporu kaydedildi"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Hata raporunuzu paylaşmak için hızlıca sola kaydırın"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Hata raporunuzu paylaşmak için dokunun"</string>
diff --git a/packages/Shell/res/values-uk/strings.xml b/packages/Shell/res/values-uk/strings.xml
index ba667f20bf81..dac4fe0fe5c0 100644
--- a/packages/Shell/res/values-uk/strings.xml
+++ b/packages/Shell/res/values-uk/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Оболонка"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Генерується повідомлення про помилку"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Звіт про помилки створено"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Проведіть пальцем ліворуч, щоб надіслати звіт про помилки"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Торкніться, щоб надіслати звіт про помилки"</string>
diff --git a/packages/Shell/res/values-ur-rPK/strings.xml b/packages/Shell/res/values-ur-rPK/strings.xml
index 255aaf8c55ab..206f02a8a9c7 100644
--- a/packages/Shell/res/values-ur-rPK/strings.xml
+++ b/packages/Shell/res/values-ur-rPK/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"شیل"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"بگ رپورٹ پر پیشرفت جاری ہے"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"بَگ رپورٹ کیپچر کر لی گئی"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"اپنی بگ رپورٹ کا اشتراک کرنے کیلئے بائیں سوائپ کریں"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"اپنی بَگ رپورٹ کا اشتراک کرنے کیلئے ٹچ کریں"</string>
diff --git a/packages/Shell/res/values-uz-rUZ/strings.xml b/packages/Shell/res/values-uz-rUZ/strings.xml
index 998387e099a3..0b8c1fb1fc1f 100644
--- a/packages/Shell/res/values-uz-rUZ/strings.xml
+++ b/packages/Shell/res/values-uz-rUZ/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Terminal"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Xatoliklar hisoboti yuborilmoqda…"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Xatolik hisobotini yozib olindi"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Xatolik hisobotini yuborish uchun barmog‘ingiz bilan chapga suring"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Xatolik hisobotini bo‘lishish uchun barmog‘ingizni tegizing."</string>
diff --git a/packages/Shell/res/values-vi/strings.xml b/packages/Shell/res/values-vi/strings.xml
index 5c11b13684dd..9ef7cf966718 100644
--- a/packages/Shell/res/values-vi/strings.xml
+++ b/packages/Shell/res/values-vi/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Đang tiến hành báo cáo lỗi"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Báo cáo lỗi đã được chụp"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Vuốt sang trái để chia sẻ báo cáo lỗi của bạn"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Chạm để chia sẻ báo cáo lỗi của bạn"</string>
diff --git a/packages/Shell/res/values-zh-rCN/strings.xml b/packages/Shell/res/values-zh-rCN/strings.xml
index 0a2f81bcb42a..0928bb3fd3c3 100644
--- a/packages/Shell/res/values-zh-rCN/strings.xml
+++ b/packages/Shell/res/values-zh-rCN/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"Shell"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"已抓取错误报告"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"向左滑动即可分享错误报告"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"触摸即可分享您的错误报告"</string>
diff --git a/packages/Shell/res/values-zh-rHK/strings.xml b/packages/Shell/res/values-zh-rHK/strings.xml
index 227d93785960..26a84c357ffb 100644
--- a/packages/Shell/res/values-zh-rHK/strings.xml
+++ b/packages/Shell/res/values-zh-rHK/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"命令介面"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"已擷取錯誤報告"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"向左滑動即可分享錯誤報告"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"輕觸即可分享您的錯誤報告"</string>
diff --git a/packages/Shell/res/values-zh-rTW/strings.xml b/packages/Shell/res/values-zh-rTW/strings.xml
index b9f8ee8b4944..f79e58e83ee2 100644
--- a/packages/Shell/res/values-zh-rTW/strings.xml
+++ b/packages/Shell/res/values-zh-rTW/strings.xml
@@ -17,6 +17,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"殼層"</string>
+ <!-- no translation found for bugreport_in_progress_title (6125357428413919520) -->
+ <skip />
<string name="bugreport_finished_title" msgid="2293711546892863898">"已擷取錯誤報告"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"向左滑動即可分享錯誤報告"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"輕觸即可分享您的錯誤報告"</string>
diff --git a/packages/Shell/res/values-zu/strings.xml b/packages/Shell/res/values-zu/strings.xml
index b675858e19f6..2e209e1630ce 100644
--- a/packages/Shell/res/values-zu/strings.xml
+++ b/packages/Shell/res/values-zu/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="3701846017049540910">"I-Shell"</string>
+ <string name="bugreport_in_progress_title" msgid="6125357428413919520">"Umbiko wesiphazamisi uyaqhubeka"</string>
<string name="bugreport_finished_title" msgid="2293711546892863898">"Umbiko wesiphazamisi uthwetshuliwe"</string>
<string name="bugreport_finished_text" product="watch" msgid="8389172248433597683">"Swayiphela kwesokunxele ukuze wabelane umbiko wesiphazamiso sakho"</string>
<string name="bugreport_finished_text" product="default" msgid="3559904746859400732">"Thinta ukuze wabelane ngombiko wakho wesiphazamisi"</string>
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
new file mode 100644
index 000000000000..d0e91d22f87a
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.shell;
+
+import static com.android.shell.BugreportPrefs.STATE_SHOW;
+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;
+
+import libcore.io.Streams;
+
+import com.google.android.collect.Lists;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ClipData;
+import android.content.Context;
+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) {
+ // 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;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onDestroy() {
+ mServiceLooper.quit();
+ super.onDestroy();
+ }
+
+ @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);
+ }
+ }
+ }
+
+ /**
+ * 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 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),
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
+ if (!isPlainText) {
+ // Already zipped, send it right away.
+ sendBugreportNotification(context, pid, bugreportFile, screenshotFile);
+ } else {
+ // Asynchronously zip the file first, then send it.
+ sendZippedBugreportNotification(context, pid, bugreportFile, screenshotFile);
+ }
+ }
+
+ private static Intent buildWarningIntent(Context context, Intent sendIntent) {
+ final Intent intent = new Intent(context, BugreportWarningActivity.class);
+ intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
+ return intent;
+ }
+
+ /**
+ * Build {@link Intent} that can be used to share the given bugreport.
+ */
+ private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
+ final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+ final String mimeType = "application/vnd.android.bugreport";
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.setType(mimeType);
+
+ intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
+
+ // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
+ // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
+ // create the ClipData object with the attachments URIs.
+ String messageBody = String.format("Build info: %s\nSerial number:%s",
+ SystemProperties.get("ro.build.description"), SystemProperties.get("ro.serialno"));
+ intent.putExtra(Intent.EXTRA_TEXT, messageBody);
+ final ClipData clipData = new ClipData(null, new String[] { mimeType },
+ new ClipData.Item(null, null, null, bugreportUri));
+ final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
+ if (screenshotUri != null) {
+ clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+ attachments.add(screenshotUri);
+ }
+ intent.setClipData(clipData);
+ intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
+
+ final Account sendToAccount = findSendToAccount(context);
+ if (sendToAccount != null) {
+ intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
+ }
+
+ return intent;
+ }
+
+ /**
+ * Sends a bugreport notitication.
+ */
+ 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.
+ final Uri bugreportUri = getUri(context, bugreportFile);
+ final Uri screenshotUri = getUri(context, screenshotFile);
+
+ Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
+ Intent notifIntent;
+
+ // Send through warning dialog by default
+ if (getWarningState(context, STATE_SHOW) == STATE_SHOW) {
+ notifIntent = buildWarningIntent(context, sendIntent);
+ } else {
+ notifIntent = sendIntent;
+ }
+ 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(title)
+ .setTicker(title)
+ .setContentText(context.getString(R.string.bugreport_finished_text))
+ .setContentIntent(PendingIntent.getActivity(
+ context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT))
+ .setAutoCancel(true)
+ .setLocalOnly(true)
+ .setColor(context.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+
+ NotificationManager.from(context).notify(TAG, pid, builder.build());
+ }
+
+ /**
+ * Sends a zipped bugreport notification.
+ */
+ private static void sendZippedBugreportNotification(final Context context,
+ 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, pid, zippedFile, screenshotFile);
+ return null;
+ }
+ }.execute();
+ }
+
+ /**
+ * Zips a bugreport file, returning the path to the new file (or to the
+ * original in case of failure).
+ */
+ private static File zipBugreport(File bugreportFile) {
+ String bugreportPath = bugreportFile.getAbsolutePath();
+ String zippedPath = bugreportPath.replace(".txt", ".zip");
+ 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)))) {
+ ZipEntry entry = new ZipEntry(bugreportFile.getName());
+ entry.setTime(bugreportFile.lastModified());
+ zos.putNextEntry(entry);
+ int totalBytes = Streams.copy(is, zos);
+ Log.v(TAG, "size of original bugreport: " + totalBytes + " bytes");
+ zos.closeEntry();
+ // Delete old file;
+ boolean deleted = bugreportFile.delete();
+ if (deleted) {
+ Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
+ } else {
+ Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
+ }
+ return bugreportZippedFile;
+ } catch (IOException e) {
+ Log.e(TAG, "exception zipping file " + zippedPath, e);
+ return bugreportFile; // Return original.
+ }
+ }
+
+ /**
+ * Find the best matching {@link Account} based on build properties.
+ */
+ private static Account findSendToAccount(Context context) {
+ final AccountManager am = (AccountManager) context.getSystemService(
+ Context.ACCOUNT_SERVICE);
+
+ String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
+ if (!preferredDomain.startsWith("@")) {
+ preferredDomain = "@" + preferredDomain;
+ }
+
+ final Account[] accounts = am.getAccounts();
+ Account foundAccount = null;
+ for (Account account : accounts) {
+ if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
+ if (!preferredDomain.isEmpty()) {
+ // if we have a preferred domain and it matches, return; otherwise keep
+ // looking
+ if (account.name.endsWith(preferredDomain)) {
+ return account;
+ } else {
+ foundAccount = account;
+ }
+ // if we don't have a preferred domain, just return since it looks like
+ // an email address
+ } else {
+ return account;
+ }
+ }
+ }
+ return foundAccount;
+ }
+
+ private static Uri getUri(Context context, File file) {
+ return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
+ }
+
+ static File getFileExtra(Intent intent, String key) {
+ final String path = intent.getStringExtra(key);
+ if (path != null) {
+ return new File(path);
+ } else {
+ 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 c26437285050..5133162a1ec9 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -16,53 +16,25 @@
package com.android.shell;
-import static com.android.shell.BugreportPrefs.STATE_SHOW;
-import static com.android.shell.BugreportPrefs.getWarningState;
+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;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.content.BroadcastReceiver;
-import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Configuration;
-import android.net.Uri;
import android.os.AsyncTask;
import android.os.FileUtils;
-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.widget.Toast;
-
-import com.google.android.collect.Lists;
-import libcore.io.Streams;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-import java.util.ArrayList;
/**
* Receiver that handles finished bugreports, usually by attaching them to an
- * {@link Intent#ACTION_SEND}.
+ * {@link Intent#ACTION_SEND_MULTIPLE}.
*/
public class BugreportReceiver extends BroadcastReceiver {
- private static final String TAG = "Shell";
-
- private static final String AUTHORITY = "com.android.shell";
-
- private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
- private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
/**
* Always keep the newest 8 bugreport files; 4 reports and 4 screenshots are
@@ -77,15 +49,20 @@ public class BugreportReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- final Configuration conf = context.getResources().getConfiguration();
- final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
- final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+ // Clean up older bugreports in background
+ cleanupOldFiles(intent);
- if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
- triggerLocalNotification(context, bugreportFile, screenshotFile);
- }
+ // Delegate intent handling to service.
+ Intent serviceIntent = new Intent(context, BugreportProgressService.class);
+ serviceIntent.putExtra(EXTRA_ORIGINAL_INTENT, intent);
+ context.startService(serviceIntent);
+ }
- // Clean up older bugreports in background
+ 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>() {
@Override
@@ -97,201 +74,4 @@ public class BugreportReceiver extends BroadcastReceiver {
}
}.execute();
}
-
- /**
- * 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 void triggerLocalNotification(final Context context, 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),
- Toast.LENGTH_LONG).show();
- return;
- }
-
- boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
- if (!isPlainText) {
- // Already zipped, send it right away.
- sendBugreportNotification(context, bugreportFile, screenshotFile);
- } else {
- // Asynchronously zip the file first, then send it.
- sendZippedBugreportNotification(context, bugreportFile, screenshotFile);
- }
- }
-
- private static Intent buildWarningIntent(Context context, Intent sendIntent) {
- final Intent intent = new Intent(context, BugreportWarningActivity.class);
- intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
- return intent;
- }
-
- /**
- * Build {@link Intent} that can be used to share the given bugreport.
- */
- private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
- final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
- final String mimeType = "application/vnd.android.bugreport";
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- intent.setType(mimeType);
-
- intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
-
- // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
- // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
- // create the ClipData object with the attachments URIs.
- String messageBody = String.format("Build info: %s\nSerial number:%s",
- SystemProperties.get("ro.build.description"), SystemProperties.get("ro.serialno"));
- intent.putExtra(Intent.EXTRA_TEXT, messageBody);
- final ClipData clipData = new ClipData(null, new String[] { mimeType },
- new ClipData.Item(null, null, null, bugreportUri));
- final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
- if (screenshotUri != null) {
- clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
- attachments.add(screenshotUri);
- }
- intent.setClipData(clipData);
- intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
-
- final Account sendToAccount = findSendToAccount(context);
- if (sendToAccount != null) {
- intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
- }
-
- return intent;
- }
-
- /**
- * Sends a bugreport notitication.
- */
- private static void sendBugreportNotification(Context context, File bugreportFile,
- File screenshotFile) {
- // Files are kept on private storage, so turn into Uris that we can
- // grant temporary permissions for.
- final Uri bugreportUri = getUri(context, bugreportFile);
- final Uri screenshotUri = getUri(context, screenshotFile);
-
- Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
- Intent notifIntent;
-
- // Send through warning dialog by default
- if (getWarningState(context, STATE_SHOW) == STATE_SHOW) {
- notifIntent = buildWarningIntent(context, sendIntent);
- } else {
- notifIntent = sendIntent;
- }
- notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- 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))
- .setContentText(context.getString(R.string.bugreport_finished_text))
- .setContentIntent(PendingIntent.getActivity(
- context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT))
- .setAutoCancel(true)
- .setLocalOnly(true)
- .setColor(context.getColor(
- com.android.internal.R.color.system_notification_accent_color));
-
- NotificationManager.from(context).notify(TAG, 0, builder.build());
- }
-
- /**
- * Sends a zipped bugreport notification.
- */
- private static void sendZippedBugreportNotification(final Context context,
- 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);
- return null;
- }
- }.execute();
- }
-
- /**
- * Zips a bugreport file, returning the path to the new file (or to the
- * original in case of failure).
- */
- private static File zipBugreport(File bugreportFile) {
- String bugreportPath = bugreportFile.getAbsolutePath();
- String zippedPath = bugreportPath.replace(".txt", ".zip");
- 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)))) {
- ZipEntry entry = new ZipEntry(bugreportFile.getName());
- entry.setTime(bugreportFile.lastModified());
- zos.putNextEntry(entry);
- int totalBytes = Streams.copy(is, zos);
- Log.v(TAG, "size of original bugreport: " + totalBytes + " bytes");
- zos.closeEntry();
- // Delete old file;
- boolean deleted = bugreportFile.delete();
- if (deleted) {
- Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
- } else {
- Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
- }
- return bugreportZippedFile;
- } catch (IOException e) {
- Log.e(TAG, "exception zipping file " + zippedPath, e);
- return bugreportFile; // Return original.
- }
- }
-
- /**
- * Find the best matching {@link Account} based on build properties.
- */
- private static Account findSendToAccount(Context context) {
- final AccountManager am = (AccountManager) context.getSystemService(
- Context.ACCOUNT_SERVICE);
-
- String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
- if (!preferredDomain.startsWith("@")) {
- preferredDomain = "@" + preferredDomain;
- }
-
- final Account[] accounts = am.getAccounts();
- Account foundAccount = null;
- for (Account account : accounts) {
- if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
- if (!preferredDomain.isEmpty()) {
- // if we have a preferred domain and it matches, return; otherwise keep
- // looking
- if (account.name.endsWith(preferredDomain)) {
- return account;
- } else {
- foundAccount = account;
- }
- // if we don't have a preferred domain, just return since it looks like
- // an email address
- } else {
- return account;
- }
- }
- }
- return foundAccount;
- }
-
- private static Uri getUri(Context context, File file) {
- return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
- }
-
- private static File getFileExtra(Intent intent, String key) {
- final String path = intent.getStringExtra(key);
- if (path != null) {
- return new File(path);
- } else {
- return null;
- }
- }
}
diff --git a/packages/Shell/tests/Android.mk b/packages/Shell/tests/Android.mk
new file mode 100644
index 000000000000..62a37bc70f4e
--- /dev/null
+++ b/packages/Shell/tests/Android.mk
@@ -0,0 +1,20 @@
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# TODO: update and/or remove
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator
+#LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target ub-uiautomator
+
+LOCAL_PACKAGE_NAME := ShellTests
+LOCAL_INSTRUMENTATION_FOR := Shell
+
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/packages/Shell/tests/AndroidManifest.xml b/packages/Shell/tests/AndroidManifest.xml
new file mode 100644
index 000000000000..54b0802e1504
--- /dev/null
+++ b/packages/Shell/tests/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.shell.tests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <activity
+ android:name="com.android.shell.ActionSendMultipleConsumerActivity"
+ android:label="ActionSendMultipleConsumer"
+ android:theme="@android:style/Theme.NoDisplay"
+ android:noHistory="true"
+ android:excludeFromRecents="true">
+ <intent-filter>
+ <action android:name="android.intent.action.SEND_MULTIPLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="*/*" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.shell"
+ android:label="Tests for Shell" />
+
+</manifest>
diff --git a/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java b/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java
new file mode 100644
index 000000000000..e3e99b0d5711
--- /dev/null
+++ b/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.shell;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+
+/**
+ * Activity responsible for handling ACTION_SEND_MULTIPLE intents and passing them back to the test
+ * case class (through a {@link CustomActionSendMultipleListener}).
+ */
+public class ActionSendMultipleConsumerActivity extends Activity {
+
+ private static final String CUSTOM_ACTION_SEND_MULTIPLE_INTENT =
+ "com.android.shell.tests.CUSTOM_ACTION_SEND_MULTIPLE";
+
+ private static CustomActionSendMultipleListener sListener;
+
+ static final String UI_NAME = "ActionSendMultipleConsumer";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // The original intent cannot be broadcasted, it will fail due to security violations.
+ // Since the test case is only interested in the extras, we need to create a new custom
+ // intent with just them.
+ final Intent intent = getIntent();
+ final Intent customIntent = new Intent(CUSTOM_ACTION_SEND_MULTIPLE_INTENT);
+ customIntent.putExtras(intent.getExtras());
+
+ getApplicationContext().sendBroadcast(customIntent);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ /*
+ * TODO: if finish() is not called, app will crash with an exception such as:
+ * AndroidRuntime: java.lang.RuntimeException: Unable to resume activity
+ * {com.android.shell.tests/com.android.shell.SendMultipleActivity}:
+ * java.lang.IllegalStateException: Activity
+ * {com.android.shell.tests/com.android.shell.SendMultipleActivity} did not call finish()
+ * prior to onResume() completing. That seems to be a problem on M:
+ * https://code.google.com/p/android-developer-preview/issues/detail?id=2353
+ */
+ finish();
+ }
+
+ /**
+ * Gets the {@link CustomActionSendMultipleListener} singleton.
+ */
+ static CustomActionSendMultipleListener getListener(Context context) {
+ synchronized (ActionSendMultipleConsumerActivity.class) {
+ if (sListener == null) {
+ sListener = new CustomActionSendMultipleListener(context);
+ }
+ }
+ return sListener;
+ }
+
+ /**
+ * Listener of custom ACTION_SEND_MULTIPLE_INTENTS.
+ */
+ static class CustomActionSendMultipleListener {
+
+ private static final int TIMEOUT = 10;
+ private final BlockingQueue<Bundle> mQueue = new SynchronousQueue<>();
+
+ public CustomActionSendMultipleListener(Context context) {
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ mQueue.put(intent.getExtras());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ };
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(CUSTOM_ACTION_SEND_MULTIPLE_INTENT);
+ context.registerReceiver(receiver, filter);
+ }
+
+ /**
+ * Gets the extras from the custom intent, blocking until it's received.
+ */
+ Bundle getExtras() {
+ Bundle bundle = null;
+ try {
+ bundle = mQueue.poll(TIMEOUT, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ if (bundle == null) {
+ throw new IllegalStateException("Intent not received after " + TIMEOUT + "s");
+ }
+ return bundle;
+ }
+ }
+}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
new file mode 100644
index 000000000000..33c4ef1ffbd5
--- /dev/null
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.shell;
+
+import static 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;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import libcore.io.Streams;
+import android.app.Instrumentation;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.service.notification.StatusBarNotification;
+import android.support.test.uiautomator.UiDevice;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import com.android.shell.ActionSendMultipleConsumerActivity.CustomActionSendMultipleListener;
+
+/**
+ * Integration tests for {@link BugreportReceiver}.
+ * <p>
+ * These tests don't mock any component and rely on external UI components (like the notification
+ * bar and activity chooser), which can make them unreliable and slow.
+ * <p>
+ * The general workflow is:
+ * <ul>
+ * <li>creates the bug report files
+ * <li>generates the BUGREPORT_FINISHED intent
+ * <li>emulate user actions to share the intent with a custom activity
+ * <li>asserts the extras received by the custom activity
+ * </ul>
+ * <p>
+ * TODO: currently, these tests only work if the bug report sharing warning is disabled and the
+ * device screen is unlocked.
+ */
+public class BugreportReceiverTest extends InstrumentationTestCase {
+
+ private static final String TAG = "BugreportReceiverTest";
+
+ // Timeout for UI operations, in milliseconds.
+ private static final int TIMEOUT = 1000;
+
+ private static final String ROOT_DIR = "/data/data/com.android.shell/files/bugreports";
+ private static final String BUGREPORT_FILE = "test_bugreport.txt";
+ private static final String ZIP_FILE = "test_bugreport.zip";
+ private static final String PLAIN_TEXT_PATH = ROOT_DIR + "/" + BUGREPORT_FILE;
+ private static final String ZIP_PATH = ROOT_DIR + "/" + ZIP_FILE;
+ private static final String SCREENSHOT_PATH = ROOT_DIR + "/test_screenshot.png";
+
+ private static final String BUGREPORT_CONTENT = "Dump, might as well dump!\n";
+ private static final String SCREENSHOT_CONTENT = "A picture is worth a thousand words!\n";
+
+ private Context mContext;
+ private UiBot mUiBot;
+ private CustomActionSendMultipleListener mListener;
+
+ @Override
+ protected void setUp() throws Exception {
+ Instrumentation instrumentation = getInstrumentation();
+ mContext = instrumentation.getTargetContext();
+ mUiBot = new UiBot(UiDevice.getInstance(instrumentation), TIMEOUT);
+ mListener = ActionSendMultipleConsumerActivity.getListener(mContext);
+ 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);
+ Bundle extras = sendBugreportFinishedIntent(PLAIN_TEXT_PATH, SCREENSHOT_PATH);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT);
+ }
+
+ public void testBugreportFinished_zippedBugreportAndScreenshot() throws Exception {
+ createZipFile(ZIP_PATH, BUGREPORT_FILE, BUGREPORT_CONTENT);
+ createTextFile(SCREENSHOT_PATH, SCREENSHOT_CONTENT);
+ Bundle extras = sendBugreportFinishedIntent(ZIP_PATH, SCREENSHOT_PATH);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT);
+ }
+
+ public void testBugreportFinished_plainBugreportAndNoScreenshot() throws Exception {
+ createTextFile(PLAIN_TEXT_PATH, BUGREPORT_CONTENT);
+ Bundle extras = sendBugreportFinishedIntent(PLAIN_TEXT_PATH, null);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, null);
+ }
+
+ public void testBugreportFinished_zippedBugreportAndNoScreenshot() throws Exception {
+ createZipFile(ZIP_PATH, BUGREPORT_FILE, BUGREPORT_CONTENT);
+ Bundle extras = sendBugreportFinishedIntent(ZIP_PATH, null);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, null);
+ }
+
+ private void cancelExistingNotifications() {
+ NotificationManager nm = NotificationManager.from(mContext);
+ for (StatusBarNotification notification : nm.getActiveNotifications()) {
+ int id = notification.getId();
+ Log.i(TAG, "Canceling existing notification (id=" + id + ")");
+ nm.cancel(id);
+ }
+ }
+
+ 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) {
+ 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);
+ }
+ if (screenshotPath != null) {
+ intent.putExtra(EXTRA_SCREENSHOT, screenshotPath);
+ }
+
+ mContext.sendBroadcast(intent);
+
+ mUiBot.clickOnNotification(mContext.getString(R.string.bugreport_finished_title));
+ mUiBot.chooseActivity(UI_NAME);
+ return mListener.getExtras();
+ }
+
+ /**
+ * Asserts the proper ACTION_SEND_MULTIPLE intent was sent.
+ */
+ private void assertActionSendMultiple(Bundle extras, String bugreportContent,
+ String screenshotContent) throws IOException {
+ String body = extras.getString(Intent.EXTRA_TEXT);
+ assertContainsRegex("missing build info",
+ SystemProperties.get("ro.build.description"), body);
+ assertContainsRegex("missing serial number",
+ SystemProperties.get("ro.serialno"), body);
+
+ assertEquals("wrong subject", ZIP_FILE, extras.getString(Intent.EXTRA_SUBJECT));
+
+ List<Uri> attachments = extras.getParcelableArrayList(Intent.EXTRA_STREAM);
+ int expectedSize = screenshotContent != null ? 2 : 1;
+ assertEquals("wrong number of attachments", expectedSize, attachments.size());
+
+ // Need to interact through all attachments, since order is not guaranteed.
+ Uri zipUri = null, screenshotUri = null;
+ for (Uri attachment : attachments) {
+ if (attachment.getPath().endsWith(".zip")) {
+ zipUri = attachment;
+ }
+ if (attachment.getPath().endsWith(".png")) {
+ screenshotUri = attachment;
+ }
+ }
+ assertNotNull("did not get .zip attachment", zipUri);
+ assertZipContent(zipUri, BUGREPORT_FILE, BUGREPORT_CONTENT);
+
+ if (screenshotContent != null) {
+ assertNotNull("did not get .png attachment", screenshotUri);
+ assertContent(screenshotUri, SCREENSHOT_CONTENT);
+ } else {
+ assertNull("should not have .png attachment", screenshotUri);
+ }
+ }
+
+ private void assertContent(Uri uri, String expectedContent) throws IOException {
+ Log.v(TAG, "assertContents(uri=" + uri);
+ try (InputStream is = mContext.getContentResolver().openInputStream(uri)) {
+ String actualContent = new String(Streams.readFully(is));
+ assertEquals("wrong content for '" + uri + "'", expectedContent, actualContent);
+ }
+ }
+
+ private void assertZipContent(Uri uri, String entryName, String expectedContent)
+ throws IOException, IOException {
+ Log.v(TAG, "assertZipEntry(uri=" + uri + ", entryName=" + entryName);
+ try (ZipInputStream zis = new ZipInputStream(mContext.getContentResolver().openInputStream(
+ uri))) {
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ Log.v(TAG, "Zip entry: " + entry.getName());
+ if (entry.getName().equals(entryName)) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ Streams.copy(zis, bos);
+ String actualContent = new String(bos.toByteArray(), "UTF-8");
+ bos.close();
+ assertEquals("wrong content for zip entry'" + entryName + "' on '" + uri + "'",
+ expectedContent, actualContent);
+ return;
+ }
+ }
+ }
+ fail("Did not find entry '" + entryName + "' on file '" + uri + "'");
+ }
+
+ private static void createTextFile(String path, String content) throws IOException {
+ Log.v(TAG, "createFile(" + path + ")");
+ try (Writer writer = new BufferedWriter(new OutputStreamWriter(
+ new FileOutputStream(path)))) {
+ writer.write(content);
+ }
+ }
+
+ private void createZipFile(String path, String entryName, String content) throws IOException {
+ Log.v(TAG, "createZipFile(" + path + ", " + entryName + ")");
+ try (ZipOutputStream zos = new ZipOutputStream(
+ new BufferedOutputStream(new FileOutputStream(path)))) {
+ ZipEntry entry = new ZipEntry(entryName);
+ zos.putNextEntry(entry);
+ byte[] data = content.getBytes();
+ zos.write(data, 0, data.length);
+ zos.closeEntry();
+ }
+ }
+}
diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java
new file mode 100644
index 000000000000..fa1714efcd41
--- /dev/null
+++ b/packages/Shell/tests/src/com/android/shell/UiBot.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.shell;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+import static junit.framework.Assert.assertTrue;
+
+/**
+ * A helper class for UI-related testing tasks.
+ */
+final class UiBot {
+
+ private static final String TAG = "UiBot";
+ private static final String SYSTEMUI_PACKAGED = "com.android.systemui";
+
+ private final UiDevice mDevice;
+ private final int mTimeout;
+
+ public UiBot(UiDevice device, int timeout) {
+ mDevice = device;
+ mTimeout = timeout;
+ }
+
+ /**
+ * Opens the system notification and gets a given notification.
+ *
+ * @param text Notificaton's text as displayed by the UI.
+ * @return notification object.
+ */
+ 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);
+
+ 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 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());
+ return uiObject;
+ }
+
+ /**
+ * Clicks on a UI element.
+ *
+ * @param uiObject UI element to be clicked.
+ * @param description Elements's description used on logging statements.
+ */
+ public void click(UiObject uiObject, String description) {
+ try {
+ boolean clicked = uiObject.click();
+ // TODO: assertion below fails sometimes, even though the click succeeded,
+ // (specially when clicking the "Just Once" button), so it's currently just logged.
+ // assertTrue("could not click on object '" + description + "'", clicked);
+
+ Log.v(TAG, "onClick for " + description + ": " + clicked);
+ } catch (UiObjectNotFoundException e) {
+ throw new IllegalStateException("exception when clicking on object '" + description
+ + "'", e);
+ }
+ }
+
+ /**
+ * Chooses a given activity to handle an Intent, using the "Just Once" button.
+ *
+ * @param name name of the activity as displayed in the UI (typically the value set by
+ * {@code android:label} in the manifest).
+ */
+ // TODO: UI Automator should provide such logic.
+ public void chooseActivity(String name) {
+ // First select activity if it's not the default option.
+ boolean gotIt = mDevice.wait(Until.hasObject(By.text(name)), mTimeout);
+ // TODO: if the activity is indeed the default option, call above will timeout, which will
+ // make the tests run slower. It might be better to change the logic to assume the default
+ // first.
+ if (gotIt) {
+ Log.v(TAG, "Found activity " + name + ", it's not default action");
+ UiObject activityChooser = getVisibleObject(name);
+ click(activityChooser, "activity chooser");
+ } else {
+ String text = String.format("Share with %s", name);
+ Log.v(TAG, "Didn't find activity " + name
+ + ", assuming it's the default action and search for '" + text + "'");
+ gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
+ assertTrue("did not find text '" + text + "'", gotIt);
+ }
+
+ // Then clicks the "Just Once" button.
+ gotIt = mDevice
+ .wait(Until.hasObject(By.res("android", "button_once")), mTimeout);
+ assertTrue("'Just Once' button not visible yet", gotIt);
+
+ UiObject justOnce = mDevice
+ .findObject(new UiSelector().resourceId("android:id/button_once"));
+ assertTrue("'Just Once' button not found", justOnce.exists());
+
+ click(justOnce, "Just Once");
+ }
+}
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-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 1e18523c6cde..7006d439330f 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"skermvaspen"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"soek"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Kon nie <xliff:g id="APP">%s</xliff:g> begin nie."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Meer"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Geskiedenis"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Verdeel horisontaal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Verdeel vertikaal"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Verdeel gepasmaak"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Wys horlosiesekondes op die statusbalk. Sal batterylewe dalk beïnvloed."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Herrangskik Kitsinstellings"</string>
<string name="show_brightness" msgid="6613930842805942519">"Wys helderheid in Kitsinstellings"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Aktiveer oproep"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Aktiveer oproep deur die Oorsig-knoppie"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Aktiveer vinnige wissel"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Aktiveer beginuitteltyd terwyl daar opgeroep word"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Aktiveer volskerm-skermkiekies"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Aktiveer volskerm-skermkiekies in Oorsig"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimenteel"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Skakel Bluetooth aan?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Jy moet Bluetooth aanskakel om jou sleutelbord aan jou tablet te koppel."</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 8dae91197333..948c538fa009 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ማያ ገጽ መሰካት"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ፈልግ"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g>ን መጀመር አልተቻለም።"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"ተጨማሪ"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ታሪክ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"አግድም ክፈል"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ቁልቁል ክፈል"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"በብጁ ክፈል"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"የሰዓት ሰከንዶችን በሁኔታ አሞሌ ውስጥ አሳይ። በባትሪ ዕድሜ ላይ ተጽዕኖ ሊኖርው ይችል ይሆናል።"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ፈጣን ቅንብሮችን ዳግም ያደራጁ"</string>
<string name="show_brightness" msgid="6613930842805942519">"በፈጣን ቅንብሮች ውስጥ ብሩህነትን አሳይ"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"ገጽ መስራትን አንቃ"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"በአጠቃላይ እይታ አዝራር በኩል ገጽ መስራትን አንቃ"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"ፈጣን ቅይይርን አንቃ"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"ገጽ እየሰራ ሳለ የማስጀመሪያ ጊዜ ማብቂያውን አንቃ"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"የሙሉ ማያ ገጽ ቅጽበታዊ ገጽ እይታዎችን አንቃ"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"በአጠቃላይ እይታ ውስጥ የሙሉ ማያ ገጽ ቅጽበታዊ ገጽ እይታዎችን አንቃ"</string>
<string name="experimental" msgid="6198182315536726162">"የሙከራ"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"ብሉቱዝ ይብራ?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"የቁልፍ ሰሌዳዎን ከእርስዎ ጡባዊ ጋር ለማገናኘት በመጀመሪያ ብሉቱዝን ማብራት አለብዎት።"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index b7d85b74b75e..e2723642ebb2 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -298,6 +298,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"تثبيت الشاشة"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"بحث"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"تعذر بدء <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"المزيد"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"السجلّ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"تقسيم أفقي"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"تقسيم رأسي"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"تقسيم مخصص"</string>
@@ -441,12 +443,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"عرض ثواني الساعة في شريط الحالة. قد يؤثر ذلك في عمر البطارية."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"إعادة ترتيب الإعدادات السريعة"</string>
<string name="show_brightness" msgid="6613930842805942519">"عرض السطوع في الإعدادات السريعة"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"تمكين الترحيل"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"تمكين الترحيل عبر زر \"نظرة عامة\""</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"تمكين التبديل السريع"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"تمكين مهلة الإطلاق أثناء الترحيل"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"تمكين اللقطات بملء الشاشة"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"تمكين اللقطات بملء الشاشة في النظرة العامة"</string>
<string name="experimental" msgid="6198182315536726162">"إعدادات تجريبية"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"تشغيل البلوتوث؟"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"لتوصيل لوحة المفاتيح بالجهاز اللوحي، يلزمك تشغيل بلوتوث أولاً."</string>
diff --git a/packages/SystemUI/res/values-az-rAZ/strings.xml b/packages/SystemUI/res/values-az-rAZ/strings.xml
index b872db37309a..7f2a6b071d2b 100644
--- a/packages/SystemUI/res/values-az-rAZ/strings.xml
+++ b/packages/SystemUI/res/values-az-rAZ/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ekran sancağı"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"axtarış"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> başlana bilmir."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Daha çox"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Tarixçə"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Üfüqi Böl"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Şaquli Böl"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Fərdi Böl"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Saatın saniyəsini status panelində göstərin. Batareyaya təsir edə bilər."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Sürətli Ayarları yenidən tənzimləyin"</string>
<string name="show_brightness" msgid="6613930842805942519">"Sürətli ayarlarda parlaqlılığı göstərin"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Səhifə nömrələməni aktiv edin"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Baxış düyməsi vasitəsilə səhifələməni aktiv edin"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Sürətli keçidi aktiv edin"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Səhifə nömrələyərkən işə salma vaxtının bitməsini aktiv edin"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Tam ekran ani görüntülərini aktiv edin"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Tam ekran ani görüntülərini İcmalda aktiv edin"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth aktivləşsin?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Tabletinizlə klaviaturaya bağlanmaq üçün ilk olaraq Bluetooth\'u aktivləşdirməlisiniz."</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index fd5ddbf6902d..6ed2adbfb561 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"фиксиране на екрана"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"търсене"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> не можа да стартира."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Още"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"История"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Хоризонтално разделяне"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Вертикално разделяне"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Персонализирано разделяне"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Показване на секундите на часовника в лентата на състоянието. Може да се отрази на живота на батерията."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Пренареждане на бързите настройки"</string>
<string name="show_brightness" msgid="6613930842805942519">"Показване на яркостта от бързите настройки"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Активиране на разделянето на страници"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Активиране на разделянето на страници чрез бутона за общ преглед"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Активиране на бързото превключване"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Активиране на времето за изчакване при стартиране за разделянето на страници"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Активиране на екранните снимки на цял екран"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Активиране на екранните снимки на цял екран в режима на общ преглед"</string>
<string name="experimental" msgid="6198182315536726162">"Експериментални"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Да се включи ли Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"За да свържете клавиатурата с таблета си, първо трябва да включите Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-bn-rBD/strings.xml b/packages/SystemUI/res/values-bn-rBD/strings.xml
index 442b179a405c..20e2c354b8d7 100644
--- a/packages/SystemUI/res/values-bn-rBD/strings.xml
+++ b/packages/SystemUI/res/values-bn-rBD/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"স্ক্রীন পিন করা"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"অনুসন্ধান"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> শুরু করা যায়নি৷"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"আরো"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ইতিহাস"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"অনুভূমিক স্প্লিট"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"উল্লম্ব স্প্লিট"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"কাস্টম স্প্লিট করুন"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"স্থিতি দন্ডে ঘড়ির সেকেন্ড দেখায়৷ ব্যাটারি লাইফকে প্রভাবিত করতে পারে৷"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"দ্রুত সেটিংসে পুনরায় সাজান"</string>
<string name="show_brightness" msgid="6613930842805942519">"দ্রুত সেটিংসে উজ্জ্বলতা দেখান"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"পেজিং সক্ষম করুন"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"\'এক নজরে\' বোতামের মাধ্যমে পেজিং সক্ষম করুন"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"\'দ্রুত টগল করা\'র ব্যবস্থাটি সক্ষম করুন"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"পেজিং এর সময় \'লঞ্চ সময় সমাপ্ত\' সক্ষম করুন"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"পূর্ণস্ক্রীন স্ক্রীনশট সক্ষম করুন"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"\'এক নজরে\'র মধ্যে পূর্ণস্ক্রীন স্ক্রীনশট সক্ষম করুন"</string>
<string name="experimental" msgid="6198182315536726162">"পরীক্ষামূলক"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth চালু করবেন?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"আপনার ট্যাবলেটের সাথে আপনার কীবোর্ড সংযুক্ত করতে, আপনাকে প্রথমে Bluetooth চালু করতে হবে।"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index f67b425de5d2..0bb4826e2390 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fixació de pantalla"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"cerca"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"No s\'ha pogut iniciar <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Més"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historial"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisió horitzontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisió vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisió personalitzada"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Mostra els segons del rellotge a la barra d\'estat. Això pot afectar la durada de la bateria."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Reorganitza Configuració ràpida"</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostra la brillantor a Configuració ràpida"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Activa la paginació"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Activa la paginació mitjançant el botó Visió general"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Activa el canvi ràpid"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Activa el temps d\'espera de llançament durant la paginació"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Activa les captures de pantalla completa"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Activa les captures de pantalla completa a Visió general"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Vols activar el Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Per connectar el teclat amb la tauleta, primer has d\'activar el Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 08d833ea2d5d..63a16ad32026 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"připnutí obrazovky"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"vyhledat"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Aplikaci <xliff:g id="APP">%s</xliff:g> nelze spustit."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Další"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historie"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Vodorovné rozdělení"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikální rozdělení"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Vlastní rozdělení"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Na stavovém řádku se bude zobrazovat sekundová ručička. Může být ovlivněna výdrž baterie."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Změnit uspořádání Rychlého nastavení"</string>
<string name="show_brightness" msgid="6613930842805942519">"Zobrazit jas v Rychlém nastavení"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Povolit stránkování"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Povolit stránkování pomocí tlačítka Přehled"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Povolit rychlé přepínání"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Povolit časový limit pro spuštění při stránkování"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Povolit snímky celé obrazovky"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Povolit snímky celé obrazovky v Přehledu"</string>
<string name="experimental" msgid="6198182315536726162">"Experimentální"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Zapnout Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Chcete-li klávesnici připojit k tabletu, nejdříve musíte zapnout Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 1fb1a51609ae..eb8053d922a5 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"bliv i app"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"søg"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> kunne ikke startes."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mere"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historik"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Opdel vandret"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Opdel lodret"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Opdel brugerdefineret"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Vis sekunder i statuslinjen. Dette kan påvirke batteriets levetid."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Omarranger Hurtige indstillinger"</string>
<string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i Hurtige indstillinger"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Aktivér sideopdeling"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Aktivér sideopdeling via knappen Oversigt"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Aktivér hurtigt skift"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Aktivér åbningstimeout ved sideinddeling"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Aktivér skærmbilleder i fuld størrelse"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Aktivér skærmbilleder i fuld størrelse i Oversigt"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimentel"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Vil du slå Bluetooth til?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Bluetooth skal være slået til, før du kan knytte dit tastatur til din tablet."</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index a9196d65b5f4..e156c92da9c8 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"Bildschirmfixierung"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"Suche"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> konnte nicht gestartet werden."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mehr"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Verlauf"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Geteilte Schaltfläche – horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Geteilte Schaltfläche – vertikal"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Geteilte Schaltfläche – benutzerdefiniert"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Uhrsekunden in der Statusleiste anzeigen. Kann sich auf die Akkulaufzeit auswirken."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Schnelleinstellungen neu anordnen"</string>
<string name="show_brightness" msgid="6613930842805942519">"Helligkeit in den Schnelleinstellungen anzeigen"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Paging aktivieren"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Paging über die Schaltfläche \"Übersicht\" aktivieren"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Schnelles Wechseln aktivieren"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Beim Paging Zeitlimit für Start aktivieren"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Vollbild-Screenshots aktivieren"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Vollbild-Screenshots in der Übersicht aktivieren"</string>
<string name="experimental" msgid="6198182315536726162">"Experimentell"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth aktivieren?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Zum Verbinden von Tastatur und Tablet muss Bluetooth aktiviert sein."</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index e982df64ec89..b0dcc40f5a88 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"καρφίτσωμα οθόνης"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"αναζήτηση"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Δεν ήταν δυνατή η εκκίνηση της εφαρμογής <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Περισσότερα"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Ιστορικό"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Οριζόντιος διαχωρισμός"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Κάθετος διαχωρισμός"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Προσαρμοσμένος διαχωρισμός"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Εμφάνιση δευτερολέπτων ρολογιού στη γραμμή κατάστασης. Ενδέχεται να επηρεάσει τη διάρκεια ζωής της μπαταρίας."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Αναδιάταξη Γρήγορων ρυθμίσεων"</string>
<string name="show_brightness" msgid="6613930842805942519">"Εμφάνιση φωτεινότητας στις Γρήγορες ρυθμίσεις"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Ενεργοποίηση σελιδοποίησης"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Ενεργοποίηση σελιδοποίησης μέσω του κουμπιού \"Επισκόπηση\""</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Ενεργοποίηση γρήγορης εναλλαγής"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Ενεργοποίηση του χρονικού ορίου λήξης εκκίνησης κατά τη σελιδοποίηση"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Ενεργοποίηση στιγμιοτύπων πλήρους οθόνης"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Ενεργοποίηση των στιγμιοτύπων πλήρους οθόνης στην Προεπισκόπηση"</string>
<string name="experimental" msgid="6198182315536726162">"Σε πειραματικό στάδιο"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Ενεργοποίηση Bluetooth;"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Για να συνδέσετε το πληκτρολόγιο με το tablet σας, θα πρέπει πρώτα να ενεργοποιήσετε το Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 32c486feb4e3..84f178565e13 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"screen pinning"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"search"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Could not start <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"More"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"History"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Split Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Split Vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Split Customised"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Show clock seconds in the status bar. May impact battery life."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
<string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Enable paging"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Enable paging via the Overview button"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Enable fast toggle"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Enable launch timeout while paging"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Enable fullscreen screenshots"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Enable fullscreen screenshots in Overview"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Turn on Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 32c486feb4e3..84f178565e13 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"screen pinning"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"search"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Could not start <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"More"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"History"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Split Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Split Vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Split Customised"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Show clock seconds in the status bar. May impact battery life."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
<string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Enable paging"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Enable paging via the Overview button"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Enable fast toggle"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Enable launch timeout while paging"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Enable fullscreen screenshots"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Enable fullscreen screenshots in Overview"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Turn on Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 32c486feb4e3..84f178565e13 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"screen pinning"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"search"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Could not start <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"More"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"History"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Split Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Split Vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Split Customised"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Show clock seconds in the status bar. May impact battery life."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Rearrange Quick Settings"</string>
<string name="show_brightness" msgid="6613930842805942519">"Show brightness in Quick Settings"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Enable paging"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Enable paging via the Overview button"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Enable fast toggle"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Enable launch timeout while paging"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Enable fullscreen screenshots"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Enable fullscreen screenshots in Overview"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Turn on Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 27451ec607a5..c9af19312291 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"Fijar pantalla"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"buscar"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"No se pudo iniciar <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Más"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historial"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"División horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"División vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"División personalizada"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Muestra los segundos del reloj en la barra de estado. Puede afectar la duración de la batería."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar la Configuración rápida"</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostrar el brillo en la Configuración rápida"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Habilitar paginación"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Habilita la paginación a través del botón Recientes"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Habilitar la activación rápida"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Habilita el tiempo de espera de inicio durante la paginación"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Habilitar capturas de pantalla en pantalla completa"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Habilita las capturas de pantalla en pantalla completa en Recientes"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"¿Activar Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar el teclado con la tablet, primero debes activar Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 1a7fea56f29b..51984bf8a698 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fijación de pantalla"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"buscar"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"No se ha podido iniciar <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Más"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historial"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"División horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"División vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"División personalizada"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Muestra los segundos del reloj en la barra de estado. Puede afectar a la duración de la batería."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar Ajustes rápidos"</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostrar brillo en Ajustes rápidos"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Habilitar paginación"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Habilitar paginación con el botón Visión general"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Habilitar activación rápida"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Habilitar tiempo de espera de lanzamiento durante paginación"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Habilitar capturas de pantalla completa"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Habilitar capturas de pantalla completa en Visión general"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"¿Activar Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Para poder conectar tu teclado a tu tablet, debes activar el Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-et-rEE/strings.xml b/packages/SystemUI/res/values-et-rEE/strings.xml
index 1369252efee4..b527b9f88315 100644
--- a/packages/SystemUI/res/values-et-rEE/strings.xml
+++ b/packages/SystemUI/res/values-et-rEE/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ekraanikuva kinnitamine"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"otsing"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Rakendust <xliff:g id="APP">%s</xliff:g> ei saanud käivitada."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Rohkem"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Ajalugu"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Horisontaalne poolitamine"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikaalne poolitamine"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Kohandatud poolitamine"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Olekuribal kella sekundite kuvamine. See võib mõjutada aku kasutusaega."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Korralda kiirseaded ümber"</string>
<string name="show_brightness" msgid="6613930842805942519">"Kuva kiirseadetes heledus"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Luba sirvimine"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Saate lubada sirvimise nupuga Ülevaade"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Luba kiire vahetamine"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Luba sirvimisel käivitamise ajalõpp"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Luba täisekraanil ekraanipildid"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Saate lubada lehel Ülevaade täisekraanil ekraanipildid"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimentaalne"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Kas lülitada Bluetooth sisse?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Klaviatuuri ühendamiseks tahvelarvutiga peate esmalt Bluetoothi sisse lülitama."</string>
diff --git a/packages/SystemUI/res/values-eu-rES/strings.xml b/packages/SystemUI/res/values-eu-rES/strings.xml
index 6a5212e9a0ed..b7cfb2a2ad77 100644
--- a/packages/SystemUI/res/values-eu-rES/strings.xml
+++ b/packages/SystemUI/res/values-eu-rES/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"pantaila-ainguratzea"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"bilatu"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Ezin izan da hasi <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Gehiago"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historia"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Banaketa horizontala"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Banaketa bertikala"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Banaketa pertsonalizatua"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Erakutsi erlojuko segundoak egoera-barran. Baliteke bateria gehiago erabiltzea."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Berrantolatu ezarpen bizkorrak"</string>
<string name="show_brightness" msgid="6613930842805942519">"Erakutsi distira Ezarpen bizkorretan"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Gaitu orriak pasatzeko aukera"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Gaitu ikuspegi orokorraren botoiaren bidez orriak pasatzeko aukera"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Gaitu bizkor aldatzeko aukera"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Gaitu orriak pasatu bitarteko abiarazteen denbora-muga"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Gaitu pantaila osoko pantaila-argazkiak"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Gaitu pantaila osoko pantaila-argazkiak ikuspegi orokorrean"</string>
<string name="experimental" msgid="6198182315536726162">"Esperimentala"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth eginbidea aktibatu nahi duzu?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Teklatua tabletara konektatzeko, Bluetooth eginbidea aktibatu behar duzu."</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 6bfef668fddc..b3d821673e51 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"پین کردن صفحه"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"جستجو"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> شروع نشد."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"بیشتر"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"سابقه"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"تقسیم افقی"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"تقسیم عمودی"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"سفارشی کردن تقسیم"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"ثانیه‌های ساعت را در نوار وضعیت نشان می‌دهد. ممکن است بر ماندگاری باتری تأثیر بگذارد."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ترتیب مجدد در تنظیمات سریع"</string>
<string name="show_brightness" msgid="6613930842805942519">"نمایش روشنایی در تنظیمات سریع"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"فعال کردن صفحه‌بندی"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"فعال کردن صفحه‌بندی از طریق دکمه «نمای کلی»"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"فعال کردن جابه‌جایی سریع"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"فعال کردن مهلت زمانی راه‌اندازی هنگام صفحه‌بندی"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"فعال کردن عکس‌های صفحه‌نمایش تمام‌صفحه"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"فعال کردن عکس‌های صفحه‌نمایش تمام‌صفحه در مرور کلی"</string>
<string name="experimental" msgid="6198182315536726162">"آزمایشی"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"بلوتوث روشن شود؟"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"برای مرتبط کردن صفحه‌کلید با رایانه لوحی، ابتدا باید بلوتوث را روشن کنید."</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 4d0debe77160..235eef0c3987 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"näytön kiinnitys"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"haku"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Sovelluksen <xliff:g id="APP">%s</xliff:g> käynnistäminen epäonnistui."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Lisää"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historia"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Vaakasuuntainen jako"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Pystysuuntainen jako"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Muokattu jako"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Näytä sekunnit tilapalkin kellossa. Tämä voi vaikuttaa akun kestoon."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Järjestä pika-asetukset uudelleen"</string>
<string name="show_brightness" msgid="6613930842805942519">"Näytä kirkkaus pika-asetuksissa"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Ota sivutus käyttöön"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Ottaa sivutuksen käyttöön Yleiskatsaus-painikkeen avulla."</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Ota käyttöön nopea päälle/pois-toiminto."</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Ottaa käynnistyksen aikakatkaisun käyttöön sivutuksen aikana."</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Salli koko ruudun kuvakaappaukset"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Sallii koko ruudun kuvakaappaukset Viimeisimmät-näkymässä."</string>
<string name="experimental" msgid="6198182315536726162">"Kokeellinen"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Otetaanko Bluetooth käyttöön?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Jotta voit yhdistää näppäimistön tablettiisi, sinun on ensin otettava Bluetooth käyttöön."</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 24a033fcc043..3493067775d1 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"épinglage d\'écran"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"rechercher"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Impossible de lancer <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Plus"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historique"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Séparation horizontale"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Séparation verticale"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Séparation personnalisée"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Afficher les secondes sur l\'horloge dans la barre d\'état. Cela peut réduire l\'autonomie de la pile."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Réorganiser les paramètres rapides"</string>
<string name="show_brightness" msgid="6613930842805942519">"Afficher la luminosité dans les paramètres rapides"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Activer la mise en page"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Activer la mise en page à l\'aide du bouton Aperçu"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Activer le basculement rapide"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Activer le délai avant expiration du lancement pendant la mise en page"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Activer les saisies d\'écran plein écran"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Activer les saisies d\'écran plein écran dans Aperçu"</string>
<string name="experimental" msgid="6198182315536726162">"Fonctions expérimentales"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Activer Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Pour connecter votre clavier à votre tablette, vous devez d\'abord activer la connectivité Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index d6522511d7ee..ebed6e963a2d 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"épinglage d\'écran"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"rechercher"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Impossible de lancer <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Plus"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historique"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Séparation horizontale"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Séparation verticale"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Séparation personnalisée"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Afficher les secondes dans la barre d\'état. Cela risque de réduire l\'autonomie de la batterie."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Réorganiser la fenêtre de configuration rapide"</string>
<string name="show_brightness" msgid="6613930842805942519">"Afficher la luminosité dans fenêtre de configuration rapide"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Activer la mise en page"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Activer la mise en page via le bouton Aperçu"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Activer le basculement rapide"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Activer le délai avant expiration du lancement pendant la mise en page"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Activer les captures d\'écran en plein écran"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Activer les captures d\'écran en plein écran en mode Aperçu"</string>
<string name="experimental" msgid="6198182315536726162">"Paramètres expérimentaux"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Activer le Bluetooth ?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Pour connecter un clavier à votre tablette, vous devez avoir activé le Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-gl-rES/strings.xml b/packages/SystemUI/res/values-gl-rES/strings.xml
index 3a8b98694a1f..a9648b9a5c2c 100644
--- a/packages/SystemUI/res/values-gl-rES/strings.xml
+++ b/packages/SystemUI/res/values-gl-rES/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fixación de pantalla"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"buscar"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Non foi posible iniciar <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Máis"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historial"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Dividir en horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Dividir en vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Dividir de xeito personalizado"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Mostra os segundos do reloxo na barra de estado. Pode influír na duración da batería."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar Configuración rápida"</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostrar brillo en Configuración rápida"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Activar paxinación"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Activar paxinación a través do botón Visión xeral"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Activar alternancia rápida"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Activa o tempo de espera do lanzamento durante a paxinación"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Activa as capturas de pantalla en pantalla completa"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Activa as capturas de pantalla en pantalla completa en Visión xeral"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Queres activar o Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar o teu teclado co tablet, primeiro tes que activar o Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-gu-rIN/strings.xml b/packages/SystemUI/res/values-gu-rIN/strings.xml
index 86c1502d0f3d..804a9404c4fc 100644
--- a/packages/SystemUI/res/values-gu-rIN/strings.xml
+++ b/packages/SystemUI/res/values-gu-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"સ્ક્રીન પિનિંગ"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"શોધ"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> પ્રારંભ કરી શકાયું નથી."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"વધુ"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ઇતિહાસ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"આડું વિભક્ત કરો"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ઊભું વિભક્ત કરો"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"કસ્ટમ વિભક્ત કરો"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"ઘડિયાળ સેકન્ડ સ્થિતિ બારમાં બતાવો. બૅટરીની આવરદા પર અસર કરી શકે છે."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ઝડપી સેટિંગ્સને ફરીથી ગોઠવો"</string>
<string name="show_brightness" msgid="6613930842805942519">"ઝડપી સેટિંગ્સમાં તેજ બતાવો"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"પેજિંગ સક્ષમ કરો"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"વિહંગાવલોકન બટન મારફતે પેજિંગ સક્ષમ કરો"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"ઝડપી ટૉગલ સક્ષમ કરો"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"પેજિંગ વખતે લોંચ સમયસમાપ્તિ સક્ષમ કરો"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"પૂર્ણસ્ક્રીન સ્ક્રીનશોટ્સ સક્ષમ કરો"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"વિહંગાવલોકનમાં પૂર્ણસ્ક્રીન સ્ક્રીનશોટ્સ સક્ષમ કરો"</string>
<string name="experimental" msgid="6198182315536726162">"પ્રાયોગિક"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth ચાલુ કરવુ છે?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"તમારા ટેબ્લેટ સાથે કીબોર્ડ કનેક્ટ કરવા માટે, તમારે પહેલાં Bluetooth ચાલુ કરવાની જરૂર પડશે."</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 834d025c4c5d..354de044f913 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"स्क्रीन पिन करना"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"खोज"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> प्रारंभ नहीं किया जा सका."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"अधिक"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"इतिहास"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"क्षैतिज रूप से विभाजित करें"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"लम्बवत रूप से विभाजित करें"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"कस्‍टम रूप से विभाजित करें"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"स्थिति बार में घड़ी के सेकंड दिखाएं. इससे बैटरी के जीवनकाल पर प्रभाव पड़ सकता है."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"त्वरित सेटिंग को पुन: व्यवस्थित करें"</string>
<string name="show_brightness" msgid="6613930842805942519">"त्वरित सेटिंग में स्क्रीन की रोशनी दिखाएं"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"पेजिंग सक्षम करें"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"अवलोकन बटन के माध्‍यम से पेजिंग सक्षम करें"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"तेज़ टॉगल सक्षम करें"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"पेजिंग के दौरान लॉन्‍च समयबाह्य सक्षम करें"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"पूर्णस्‍क्रीन स्‍क्रीनशॉट सक्षम करें"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"अवलोकन में पूर्ण स्‍क्रीनशॉट सक्षम करें"</string>
<string name="experimental" msgid="6198182315536726162">"प्रयोगात्मक"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लूटूथ चालू करें?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"अपने कीबोर्ड को अपने टैबलेट से कनेक्ट करने के लिए, आपको पहले ब्लूटूथ चालू करना होगा."</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index c929d9c9eab4..b35360e755fd 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -295,6 +295,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"prikvačivanje zaslona"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"pretraži"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Aplikacija <xliff:g id="APP">%s</xliff:g> nije pokrenuta."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Više"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Povijest"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Podijeli vodoravno"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Podijeli okomito"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Podijeli prilagođeno"</string>
@@ -438,12 +440,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Prikazuju se sekunde na satu na traci statusa. Može utjecati na trajanje baterije."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Promijeni raspored Brzih postavki"</string>
<string name="show_brightness" msgid="6613930842805942519">"Prikaži svjetlinu u Brzim postavkama"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Omogući pregledavanje stranica"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Omogućivanje pregledavanja stranica gumbom Pregled"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Omogući brzo prebacivanje"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Omogućivanje vremenskog ograničenja pokretanja tijekom pregledavanja stranica"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Omogući snimanje cijelog zaslona"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Omogući snimanje cijelog zaslona u Pregledu"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimentalno"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Želite li uključiti Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Da biste povezali tipkovnicu s tabletom, morate uključiti Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 145d904d55af..7956a2104c66 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"képernyő rögzítése"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"keresés"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Nem lehet elindítani a következőt: <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Továbbiak"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Előzmények"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Osztott vízszintes"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Osztott függőleges"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Osztott egyéni"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Másodpercek megjelenítése az állapotsor óráján. Ez hatással lehet az akkumulátor üzemidejére."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Gyorsbeállítások átrendezése"</string>
<string name="show_brightness" msgid="6613930842805942519">"Fényerő megjelenítése a gyorsbeállításokban"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Lapozás engedélyezése"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Lapozás engedélyezése az Áttekintés gombbal"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Gyors váltás engedélyezése"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Indítási időtúllépés engedélyezése lapozás közben"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Teljes képernyős képernyőképek engedélyezése"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Teljes képernyős képernyőképek engedélyezése Áttekintés módban"</string>
<string name="experimental" msgid="6198182315536726162">"Kísérleti"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Engedélyezi a Bluetooth-kapcsolatot?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Ha a billentyűzetet csatlakoztatni szeretné táblagépéhez, először engedélyeznie kell a Bluetooth-kapcsolatot."</string>
diff --git a/packages/SystemUI/res/values-hy-rAM/strings.xml b/packages/SystemUI/res/values-hy-rAM/strings.xml
index 5a6ec62b2e4e..de70fec049ac 100644
--- a/packages/SystemUI/res/values-hy-rAM/strings.xml
+++ b/packages/SystemUI/res/values-hy-rAM/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"էկրանի ամրակցում"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"որոնել"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Հնարավոր չէ գործարկել <xliff:g id="APP">%s</xliff:g>-ը:"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Ավելին"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Պատմություն"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Հորիզոնական տրոհում"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Ուղղահայաց տրոհում"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Հատուկ տրոհում"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Ցույց տալ ժամացույցի վայրկյանները կարգավիճակի տողում: Կարող է ազդել մարտկոցի աշխատանքի ժամանակի վրա:"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Վերադասավորել Արագ կարգավորումները"</string>
<string name="show_brightness" msgid="6613930842805942519">"Ցույց տալ պայծառությունն Արագ կարգավորումներում"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Միացնել թերթումը"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Միացնել Համատեսք կոճակի միջոցով թերթումը"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Միացնել արագ փոխարկումը"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Միացնել գործարկման ժամանակի սպառումը թերթելու ժամանակ"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Միացնել էկրանի պատկերների լիաէկրան ցուցադրումը"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Միացնել էկրանի պատկերների լիաէկրան ցուցադրումը Համատեսքում"</string>
<string name="experimental" msgid="6198182315536726162">"Փորձնական"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Միացնե՞լ Bluetooth-ը:"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Ստեղնաշարը ձեր պլանշետին միացնելու համար նախ անհրաժեշտ է միացնել Bluetooth-ը:"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index cb2fe537590b..51ddcc015c01 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"pin ke layar"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"telusuri"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Tidak dapat memulai <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Lainnya"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Riwayat"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Pisahkan Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Pisahkan Vertikal"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Pisahkan Khusus"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Tampilkan detik jam di bilah status. Dapat memengaruhi masa pakai baterai."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Atur Ulang Setelan Cepat"</string>
<string name="show_brightness" msgid="6613930842805942519">"Tampilkan kecerahan di Setelan Cepat"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Aktifkan tampilan laman"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Mengaktifkan tampilan laman melalui tombol Ringkasan"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Mengaktifkan pengalih cepat"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Aktifkan waktu tunggu peluncuran saat menampilkan laman"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Aktifkan tangkapan layar untuk layar penuh"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Aktifkan tangkapan layar untuk layar penuh di Ringkasan"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Aktifkan Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Untuk menghubungkan keyboard dengan tablet, terlebih dahulu aktifkan Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-is-rIS/strings.xml b/packages/SystemUI/res/values-is-rIS/strings.xml
index 63a4fe831577..20d8693c3fbb 100644
--- a/packages/SystemUI/res/values-is-rIS/strings.xml
+++ b/packages/SystemUI/res/values-is-rIS/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"skjáfesting"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"leita"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Ekki var hægt að ræsa <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Meira"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Ferill"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Lárétt skipting"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Lóðrétt skipting"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Sérsniðin skipting"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Sýna sekúndur á klukku í stöðustikunni. Getur haft áhrif á endingu rafhlöðu."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Endurraða flýtistillingum"</string>
<string name="show_brightness" msgid="6613930842805942519">"Sýna birtustig í flýtistillingum"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Kveikja á síðuskoðun"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Virkja síðuskoðun með yfirlitshnappinum"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Kveikja á flýtiskiptingum"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Kveikja á tímamörkum ræsingar þegar flett er"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Kveikja á skjámyndum sem fylla upp í skjáinn"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Kveikja á skjámyndum sem fylla upp í skjáinn í yfirliti"</string>
<string name="experimental" msgid="6198182315536726162">"Tilraunastillingar"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Kveikja á Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Til að geta tengt lyklaborðið við spjaldtölvuna þarftu fyrst að kveikja á Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 24bb49f3dd94..872679951781 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"blocco su schermo"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"cerca"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Impossibile avviare <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Altro"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Cronologia"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisione in orizzontale"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisione in verticale"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisione personalizzata"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Mostra i secondi nell\'orologio nella barra di stato. Ciò potrebbe ridurre la durata della carica della batteria."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Riorganizza Impostazioni rapide"</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostra luminosità in Impostazioni rapide"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Attiva paging"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Attiva il paging tramite il pulsante Panoramica"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Abilita attivazione/disattivazione veloce"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Attiva timeout lancio durante il paging"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Attiva screenshot a schermo intero"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Attiva screenshot a schermo intero in Panoramica"</string>
<string name="experimental" msgid="6198182315536726162">"Sperimentali"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Attivare il Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Per connettere la tastiera al tablet, devi prima attivare il Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 54a9ffe3336e..a28dc2a9343f 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"הצמדת מסך"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"חפש"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"לא ניתן היה להפעיל את <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"עוד"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"היסטוריה"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"פיצול אופקי"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"פיצול אנכי"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"פיצול מותאם אישית"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"הצג שניות בשעון בשורת הסטטוס. פעולה זו עשויה להשפיע על אורך חיי הסוללה."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"סידור מחדש של הגדרות מהירות"</string>
<string name="show_brightness" msgid="6613930842805942519">"הצג בהירות בהגדרות מהירות"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"אפשר דפדוף"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"הפעל דפדוף באמצעות לחצן הסקירה"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"הפעל החלפת מצב מהירה"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"אפשר זמן קצוב להפעלה במהלך הדפדוף"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"אפשר צילומי מסך במסך מלא"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"אפשר צילומי מסך במסך מלא ב\'סקירה\'"</string>
<string name="experimental" msgid="6198182315536726162">"ניסיוניות"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"‏האם להפעיל את ה-Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"‏כדי לחבר את המקלדת לטאבלט, תחילה עליך להפעיל את ה-Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index b0a1974830ae..13217a0c4f98 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"画面固定"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"検索"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g>を開始できません。"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"もっと見る"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"履歴"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"横に分割"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"縦に分割"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"分割(カスタム)"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"ステータスバーに時計の秒を表示します。電池使用量に影響する可能性があります。"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"クイック設定を並べ替え"</string>
<string name="show_brightness" msgid="6613930842805942519">"クイック設定に明るさ調整バーを表示する"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"ページ切り替えを有効にする"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"[概要] ボタンによるページ切り替えを有効にします"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"高速切り替えを有効にする"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"ページ切り替え中の起動タイムアウトを有効にします"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"全画面スクリーンショットを有効にする"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"[概要] で全画面スクリーンショットを有効にします"</string>
<string name="experimental" msgid="6198182315536726162">"試験運用版"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"BluetoothをONにしますか?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"タブレットでキーボードに接続するには、最初にBluetoothをONにする必要があります。"</string>
diff --git a/packages/SystemUI/res/values-ka-rGE/strings.xml b/packages/SystemUI/res/values-ka-rGE/strings.xml
index ce8b417e01fa..c4e84a8d6a41 100644
--- a/packages/SystemUI/res/values-ka-rGE/strings.xml
+++ b/packages/SystemUI/res/values-ka-rGE/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ეკრანზე ჩამაგრება"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ძიება"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g>-ის გამოძახება ვერ მოხერხდა."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"მეტი"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ისტორია"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ჰორიზონტალური გაყოფა"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ვერტიკალური გაყოფა"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ინდივიდუალური გაყობა"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"საათის წამების ჩვენება სტატუსის ზოლში. შეიძლება გავლენა იქონიოს ბატარეაზე."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"სწრაფი პარამეტრების გადაწყობა"</string>
<string name="show_brightness" msgid="6613930842805942519">"სიკაშკაშის ჩვენება სწრაფ პარამეტრებში"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"გვერდების გადაფურცვლის ჩართვა"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"მიმოხილვის ღილაკის მეშვეობით გვერდების გადაფურცვლის ჩართვა"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"სწრაფი გადართვის ჩართვა"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"გვერდების გადაფურცვლისას გაშვების დროის ლიმიტის ჩართვა"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"სრულეკრანიანი ეკრანის ანაბეჭდების ჩართვა"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"მიმოხილვაში სრულეკრანიანი ეკრანის ანაბეჭდების ჩართვა"</string>
<string name="experimental" msgid="6198182315536726162">"ექსპერიმენტული"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"გსურთ Bluetooth-ის ჩართვა?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"კლავიატურის ტაბლეტთან დასაკავშირებლად, ჯერ უნდა ჩართოთ Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-kk-rKZ/strings.xml b/packages/SystemUI/res/values-kk-rKZ/strings.xml
index 586b1bf9e7c6..213317049df7 100644
--- a/packages/SystemUI/res/values-kk-rKZ/strings.xml
+++ b/packages/SystemUI/res/values-kk-rKZ/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"экранды бекіту"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"іздеу"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> іске қосу мүмкін болмады."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Қосымша"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Тарих"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Бөлінген көлденең"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Бөлінген тік"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Бөлінген теңшелетін"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Күйін көрсету жолағында сағат секундтарын көрсету. Батареяның қызмет көрсету мерзіміне әсер етуі мүмкін."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Жылдам параметрлерді қайта реттеу"</string>
<string name="show_brightness" msgid="6613930842805942519">"Жылдам параметрлерде жарықтықты көрсету"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Беттерді аудару"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"\"Шолу\" түймесі арқылы беттерді аударуды қосу"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Тез ауыстырып қосуды қосу"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Беттерді аудару кезінде іске қосуды күту уақытын қосу"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Толық экрандық скриншоттарды қосу"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"\"Шолу\" ішінде толық экрандық скриншоттарды қосу"</string>
<string name="experimental" msgid="6198182315536726162">"Эксперименттік"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth функциясын қосу керек пе?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Пернетақтаны планшетке қосу үшін алдымен Bluetooth функциясын қосу керек."</string>
diff --git a/packages/SystemUI/res/values-km-rKH/strings.xml b/packages/SystemUI/res/values-km-rKH/strings.xml
index f0f0d5fb5ef8..6fbb325662af 100644
--- a/packages/SystemUI/res/values-km-rKH/strings.xml
+++ b/packages/SystemUI/res/values-km-rKH/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ការ​ភ្ជាប់​អេក្រង់"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ស្វែងរក"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"មិន​អាច​ចាប់ផ្ដើម <xliff:g id="APP">%s</xliff:g> ទេ។"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"ច្រើនទៀត"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ប្រវត្តិ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"បំបែកផ្តេក"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"បំបែកបញ្ឈរ"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"បំបែកផ្ទាល់ខ្លួន"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"បង្ហាញវិនាទីនៅលើរបារស្ថានភាពអាចនឹងប៉ះពាល់ដល់ថាមពលថ្ម។"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"រៀបចំការកំណត់រហ័សឡើងវិញ"</string>
<string name="show_brightness" msgid="6613930842805942519">"បង្ហាញកម្រិតពន្លឺនៅក្នុងការកំណត់រហ័ស"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"បើកដំណើរការចុះទំព័រ"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"បើកដំណើការចុះទំព័រតាមរយៈប៊ូតុងទិដ្ឋភាព"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"បើកដំណើរការបិទ/បើករហ័ស"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"បើកដំណើរការអស់ពេលចាប់ផ្តើមខណៈពេលចុះទំព័រ"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"បើកដំណើរការរូបថតអេក្រង់ពេញអេក្រង់"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"បើកដំណើរការរូបថតអេក្រង់ពេញអេក្រង់នៅក្នុងទិដ្ឋភាព"</string>
<string name="experimental" msgid="6198182315536726162">"ពិសោធន៍"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"បើកប៊្លូធូសឬ?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"ដើម្បីភ្ជាប់ក្តារចុចរបស់អ្នកជាមួយនឹងថេប្លេតរបស់អ្នក អ្នកត្រូវតែបើកប៊្លូធូសជាមុនសិន។"</string>
diff --git a/packages/SystemUI/res/values-kn-rIN/strings.xml b/packages/SystemUI/res/values-kn-rIN/strings.xml
index 9dec425b4617..c8fc91d149fe 100644
--- a/packages/SystemUI/res/values-kn-rIN/strings.xml
+++ b/packages/SystemUI/res/values-kn-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ಸ್ಕ್ರೀನ್ ಪಿನ್ನಿಂಗ್"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ಹುಡುಕಾಟ"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> ಪ್ರಾರಂಭಿಸಲು ಸಾದ್ಯವಿಲ್ಲ."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"ಇನ್ನಷ್ಟು"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ಇತಿಹಾಸ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ಅಡ್ಡಲಾಗಿ ವಿಭಜಿಸಿದ"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ಲಂಬವಾಗಿ ವಿಭಜಿಸಿದ"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ಕಸ್ಟಮ್ ವಿಭಜಿಸಿದ"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"ಸ್ಥಿತಿ ಪಟ್ಟಿಯಲ್ಲಿ ಗಡಿಯಾರ ಸೆಕೆಂಡುಗಳನ್ನು ತೋರಿಸು. ಇದಕ್ಕೆ ಬ್ಯಾಟರಿ ಬಾಳಿಕೆಯು ಪರಿಣಾಮಬೀರಬಹುದು."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‌‌ಗಳನ್ನು ಮರುಹೊಂದಿಸಿ"</string>
<string name="show_brightness" msgid="6613930842805942519">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‌‌ಗಳಲ್ಲಿ ಪ್ರಖರತೆಯನ್ನು ತೋರಿಸಿ"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"ಪೇಜಿಂಗ್ ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"ಸಮಗ್ರ ನೋಟದ ಬಟನ್‌ ಮೂಲಕ ಪೇಜಿಂಗ್‌ ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"ವೇಗವಾಗಿ ಟಾಗಲ್‌ ಮಾಡುವಿಕೆ ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"ಪೇಜಿಂಗ್ ಸಂದರ್ಭದಲ್ಲಿ ಪ್ರಾರಂಭಿಸುವಿಕೆ ಕಾಲಾವಧಿಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"ಪೂರ್ಣ ಪರದೆ ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"ಸಮಗ್ರ ನೋಟದಲ್ಲಿ ಪೂರ್ಣ ಪರದೆ ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
<string name="experimental" msgid="6198182315536726162">"ಪ್ರಾಯೋಗಿಕ"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"ಬ್ಲೂಟೂತ್ ಆನ್ ಮಾಡುವುದೇ?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"ನಿಮ್ಮ ಕೀಬೋರ್ಡ್ ಅನ್ನು ಟ್ಯಾಬ್ಲೆಟ್‌ಗೆ ಸಂಪರ್ಕಿಸಲು, ನೀವು ಮೊದಲು ಬ್ಲೂಟೂತ್ ಆನ್ ಮಾಡಬೇಕಾಗುತ್ತದೆ."</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 126d84a83724..1296d21f1689 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"화면 고정"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"검색"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g>을(를) 시작할 수 없습니다."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"더보기"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"기록"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"수평 분할"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"수직 분할"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"맞춤 분할"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"상태 표시줄에 시계 초 단위를 표시합니다. 배터리 수명에 영향을 줄 수도 있습니다."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"빠른 설정 재정렬"</string>
<string name="show_brightness" msgid="6613930842805942519">"빠른 설정에서 밝기 표시"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"페이징을 사용 설정합니다."</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"최근 사용 버튼을 통해 페이징 사용"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"빠른 전환 사용"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"페이징하는 동안 실행 제한시간을 사용 설정합니다."</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"전체 화면 스크린샷을 사용 설정합니다."</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"최근 사용에서 전체 화면 스크린샷을 사용 설정합니다."</string>
<string name="experimental" msgid="6198182315536726162">"베타"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"블루투스를 켜시겠습니까?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"키보드를 태블릿에 연결하려면 먼저 블루투스를 켜야 합니다."</string>
diff --git a/packages/SystemUI/res/values-ky-rKG/strings.xml b/packages/SystemUI/res/values-ky-rKG/strings.xml
index 1be6f0514a24..ead55c77cd2c 100644
--- a/packages/SystemUI/res/values-ky-rKG/strings.xml
+++ b/packages/SystemUI/res/values-ky-rKG/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"экран кадоо"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"издөө"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> баштай алган жок."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Дагы"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Таржымал"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Туурасынан бөлүү"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Тигинен бөлүү"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Ыңгайлаштырылган бөлүү"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Абал тилкесинен сааттын секунддары көрсөтүлсүн. Батареянын кубаты көбүрөөк сарпталышы мүмкүн."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Ыкчам жөндөөлөрдү кайра коюу"</string>
<string name="show_brightness" msgid="6613930842805942519">"Ыкчам жөндөөлөрдөн жарык деңгээлин көрсөтүү"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Барактоону иштетүү"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Сереп баскычы менен барактоону иштетүү"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Тез которгучту иштетүү"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Барактап жатканда таймаутту иштетүү"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Толук экран скриншотторун иштетүү"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Толук экран скриншотторун Серепте иштетүү"</string>
<string name="experimental" msgid="6198182315536726162">"Сынамык"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth күйгүзүлсүнбү?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Баскычтобуңузду планшетиңизге туташтыруу үчүн, адегенде Bluetooth\'ту күйгүзүшүңүз керек."</string>
diff --git a/packages/SystemUI/res/values-lo-rLA/strings.xml b/packages/SystemUI/res/values-lo-rLA/strings.xml
index 2fac9b90cfcc..c0689c3f13ca 100644
--- a/packages/SystemUI/res/values-lo-rLA/strings.xml
+++ b/packages/SystemUI/res/values-lo-rLA/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ການ​ປັກ​ໝຸດ​ໜ້າ​ຈໍ​"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ຊອກຫາ"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"ບໍ່​ສາ​ມາດ​ເລີ່ມ <xliff:g id="APP">%s</xliff:g> ໄດ້."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"ເພີ່ມເຕີມ"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ປະຫວັດ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ການ​ແຍກ​ລວງ​ຂວາງ"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ການ​ແຍກ​ລວງ​ຕັ້ງ"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ການ​ແຍກ​ກຳ​ນົດ​ເອງ"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"ສະ​ແດງວິ​ນາ​ທີ​ໂມງ​ຢູ່​ໃນ​ແຖບ​ສະ​ຖາ​ນະ. ອາດ​ຈະ​ມີ​ຜົນ​ກະ​ທົບ​ຕໍ່​ອາ​ຍຸ​ແບັດ​ເຕີ​ຣີ."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ຈັດ​ວາງ​ການ​ຕັ້ງ​ຄ່າ​ດ່ວນ​ຄືນ​ໃໝ່"</string>
<string name="show_brightness" msgid="6613930842805942519">"ສະ​ແດງ​ຄວາມ​ແຈ້ງ​ຢູ່​ໃນ​ການ​ຕັ້ງ​ຄ່າ​ດ່ວນ"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"​ເປີດ​ໃຊ້​ງານ​ການ​ແບ່ງ​ໜ້າ"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"ເປີດນຳໃຊ້ການແບ່ງໜ້າຜ່ານປຸ່ມພາບລວມ"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"ເປີດນຳໃຊ້ການສັບປ່ຽນໄວ"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"​ເປີດ​ໃຊ້​ງານ​ການ​ໝົດ​ເວລາ​ການ​ເປີດ​ໃຊ້ ​ໃນ​ຂະນະ​ທີ່​ແບ່ງ​ໜ້າ"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"​ເປີດ​ໃຊ້ງານພາບ​ໜ້າ​ຈໍ​ແບບ​ເຕັມ​ຈໍ"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"​ເປີດ​ໃຊ້​ງານ​ພາບ​ໜ້າ​ຈໍ​ແບບ​ເຕັມ​ຈໍ​ໃນ​ໂໝດພາບ​ລວມ"</string>
<string name="experimental" msgid="6198182315536726162">"ຍັງຢູ່ໃນການທົດລອງ"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"ເປີດ​ໃຊ້ Bluetooth ບໍ່?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"ເພື່ອ​ເຊື່ອມ​ຕໍ່​ແປ້ນ​ພິມ​ຂອງ​ທ່ານ​ກັບ​ແທັບ​ເລັດ​ຂອງ​ທ່ານ, ກ່ອນ​ອື່ນ​ໝົດ​ທ່ານ​ຕ້ອງ​ເປີດ Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 1533ae8db935..4425d31c828e 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ekrano prisegimas"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"paieška"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Nepavyko paleisti <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Daugiau"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Istorija"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Horizontalus skaidymas"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikalus skaidymas"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Tinkintas skaidymas"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Rodyti laikrodžio sekundes būsenos juostoje. Tai gali paveikti akumuliatoriaus naudojimo laiką."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Pertvarkyti sparčiuosius nustatymus"</string>
<string name="show_brightness" msgid="6613930842805942519">"Rodyti skaistį sparčiuosiuose nustatymuose"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Įgalinti puslapių kaitą"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Įgalinti puslapių kaitą apžvalgos mygtuku"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Įgalinti greitą perjungimą"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Įgalinti paleidimo skirtąjį laiką keičiant puslapius"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Įgalinti viso ekrano kopijas"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Įgalinti viso ekrano kopijas apžvalgoje"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimentinė versija"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Įjungti „Bluetooth“?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Kad galėtumėte prijungti klaviatūrą prie planšetinio kompiuterio, pirmiausia turite įjungti „Bluetooth“."</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 347932067a1b..eca57a60f815 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -295,6 +295,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"Piespraust ekrānu"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"Meklēt"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Nevarēja palaist lietotni <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Vairāk"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Vēsture"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Horizontāls dalījums"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikāls dalījums"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Pielāgots dalījums"</string>
@@ -438,12 +440,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Statusa joslā rādīt pulksteņa sekundes. Var ietekmēt akumulatora darbības laiku."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Pārkārtot ātros iestatījumus"</string>
<string name="show_brightness" msgid="6613930842805942519">"Rādīt spilgtumu ātrajos iestatījumos"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Iespējot lapošanu"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Iespējot lapošanu, izmantojot pogu Pārskats"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Iespējot ātro pārslēgšanu"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Iespējot palaišanas noildzi lapošanas laikā"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Iespējot pilnekrāna ekrānuzņēmumus"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Iespējot pilnekrāna ekrānuzņēmumus sadaļā Pārskats"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimentāli"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Vai ieslēgt Bluetooth savienojumu?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Lai pievienotu tastatūru planšetdatoram, vispirms ir jāieslēdz Bluetooth savienojums."</string>
diff --git a/packages/SystemUI/res/values-mk-rMK/strings.xml b/packages/SystemUI/res/values-mk-rMK/strings.xml
index 277a51956036..038c669c2d40 100644
--- a/packages/SystemUI/res/values-mk-rMK/strings.xml
+++ b/packages/SystemUI/res/values-mk-rMK/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"прикачување екран"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"пребарај"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> не може да се вклучи."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Повеќе"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Историја"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Раздели хоризонтално"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Раздели вертикално"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Раздели прилагодено"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Прикажи ги секундите на часовникот на статусната лента. Може да влијае на траењето на батеријата."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Преуредете ги Брзи поставки"</string>
<string name="show_brightness" msgid="6613930842805942519">"Прикажете ја осветленоста во Брзи поставки"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Овозможете прелистување страници"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Овозможете прелистување страници со копчето Краток преглед"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Овозможете брзо префрлање"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Овозможете истек на време при стартување додека прелистувате страници"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Овозможете слики од цел екран"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Овозможете слики од цел екран во Преглед"</string>
<string name="experimental" msgid="6198182315536726162">"Експериментално"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Да се вклучи Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"За да ја поврзете тастатурата со таблетот, најпрво треба да вклучите Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-ml-rIN/strings.xml b/packages/SystemUI/res/values-ml-rIN/strings.xml
index a448d9b00343..f4964d0f63fd 100644
--- a/packages/SystemUI/res/values-ml-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ml-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"സ്ക്രീൻ പിൻ ചെയ്യൽ"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"തിരയുക"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> ആരംഭിക്കാനായില്ല."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"കൂടുതൽ"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ചരിത്രം"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"തിരശ്ചീനമായി വേർതിരിക്കുക"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ലംബമായി വേർതിരിക്കുക"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ഇഷ്‌ടാനുസൃതമായി വേർതിരിക്കുക"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"സ്റ്റാറ്റസ് ബാറിൽ ക്ലോക്ക് സെക്കൻഡ് കാണിക്കുന്നത് ബാറ്ററിയുടെ ലൈഫിനെ ബാധിക്കാം."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ദ്രുത ക്രമീകരണം പുനഃസജ്ജീകരിക്കുക"</string>
<string name="show_brightness" msgid="6613930842805942519">"ദ്രുത ക്രമീകരണത്തിൽ തെളിച്ചം കാണിക്കുക"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"പേജിംഗ് പ്രവർത്തനക്ഷമമാക്കുക"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"ചുരുക്കവിവരണ ബട്ടൺ വഴി പേജിംഗ് പ്രവർത്തനക്ഷമമാക്കുക"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"അതിവേഗ ടോഗിൾ പ്രവർത്തനക്ഷമമാക്കുക"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"പേജിംഗിനിടെ ലോഞ്ച് ടൈമൗട്ട് പ്രവർത്തനക്ഷമമാക്കുക"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"പൂർണ്ണസ്ക്രീൻ സ്ക്രീൻഷോട്ടുകൾ പ്രവർത്തനക്ഷമമാക്കുക"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"ചുരുക്കവിവരണത്തിൽ പൂർണ്ണസ്ക്രീൻ സ്ക്രീൻഷോട്ടുകൾ പ്രവർത്തനക്ഷമമാക്കുക"</string>
<string name="experimental" msgid="6198182315536726162">"പരീക്ഷണാത്മകം!"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth ഓണാക്കണോ?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"നിങ്ങളുടെ ടാബ്‌ലെറ്റുമായി കീബോർഡ് കണക്റ്റുചെയ്യുന്നതിന്, ആദ്യം Bluetooth ഓണാക്കേണ്ടതുണ്ട്."</string>
diff --git a/packages/SystemUI/res/values-mn-rMN/strings.xml b/packages/SystemUI/res/values-mn-rMN/strings.xml
index 8dcb6d9882d6..122068c304cf 100644
--- a/packages/SystemUI/res/values-mn-rMN/strings.xml
+++ b/packages/SystemUI/res/values-mn-rMN/strings.xml
@@ -292,6 +292,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"дэлгэц тогтоох"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"хайх"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g>-г эхлүүлж чадсангүй."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Илүү"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Түүх"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Хэвтээ чиглэлд хуваах"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Босоо чиглэлд хуваах"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Хүссэн хэлбэрээр хуваах"</string>
@@ -435,12 +437,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Статус талбарт цагийн секундыг харуулах. Энэ нь тэжээлийн цэнэгт нөлөөлж болно."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Түргэн тохиргоог дахин засварлах"</string>
<string name="show_brightness" msgid="6613930842805942519">"Түргэн тохиргоонд гэрэлтүүлэг харах"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Өгөгдөл хадгалах/сэргээхийг идэвхжүүлэх"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Тойм товчлуурыг ашиглан өгөгдөл хадгалах/сэргээхийг идэвхжүүлэх"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Түргэн унтраах/асаахыг идэвхжүүлэх"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Өгөгдөл хадгалах/сэргээх явцад завсарлагыг эхлүүлэх"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Бүтэн дэлгэцийн дэлгэцийн агшинг идэвхжүүлэх"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Тойм хэсэгт бүтэн дэлгэцийн \"Дэлгэцийн агшинг\" идэвхжүүлэх"</string>
<string name="experimental" msgid="6198182315536726162">"Туршилтын"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth-г асаах уу?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Компьютерийн гараа таблетад холбохын тулд эхлээд Bluetooth-г асаана уу."</string>
diff --git a/packages/SystemUI/res/values-mr-rIN/strings.xml b/packages/SystemUI/res/values-mr-rIN/strings.xml
index 00c49654f323..6415adb00573 100644
--- a/packages/SystemUI/res/values-mr-rIN/strings.xml
+++ b/packages/SystemUI/res/values-mr-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"स्‍क्रीन पिन करणे"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"शोधा"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> प्रारंभ करणे शक्य झाले नाही."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"अधिक"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"इतिहास"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"क्षैतिज विभाजित करा"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"अनुलंब विभाजित करा"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"सानुकूल विभाजित करा"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"स्टेटस बारमध्‍ये घड्‍याळ सेकंद दर्शवा. कदाचित बॅटरी आयुष्‍य प्रभावित होऊ शकते."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"द्रुत सेटिंग्जची पुनर्रचना करा"</string>
<string name="show_brightness" msgid="6613930842805942519">"द्रुत सेटिंग्जमध्‍ये चमक दर्शवा"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"लिखाण सक्षम करा"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"विहंगावलोकन बटणाद्वारे लिखाण सक्षम करा"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"जलद टॉगल करा सक्षम करा"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"लिखाण करताना लाँच कालबाह्य सक्षम करा"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"पूर्ण स्क्रीन स्क्रीनशॉट सक्षम करा"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"विहंगावलोकनामध्ये पूर्ण स्क्रीन स्क्रीनशॉट सक्षम करा"</string>
<string name="experimental" msgid="6198182315536726162">"प्रायोगिक"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लूटुथ सुरू करायचे?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"आपला कीबोर्ड आपल्या टॅब्लेटसह कनेक्ट करण्यासाठी, आपल्याला प्रथम ब्लूटुथ चालू करणे आवश्यक आहे."</string>
diff --git a/packages/SystemUI/res/values-ms-rMY/strings.xml b/packages/SystemUI/res/values-ms-rMY/strings.xml
index 7b53a6cc7655..ceefc29b2c4d 100644
--- a/packages/SystemUI/res/values-ms-rMY/strings.xml
+++ b/packages/SystemUI/res/values-ms-rMY/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"penyematan skrin"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"cari"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Tidak dapat memulakan <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Lagi"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Sejarah"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Mendatar Terpisah"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Menegak Terpisah"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Tersuai Terpisah"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Tunjukkan saat jam dalam bar status. Mungkin menjejaskan hayat bateri."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Susun Semula Tetapan Pantas"</string>
<string name="show_brightness" msgid="6613930842805942519">"Tunjukkan kecerahan dalam Tetapan Pantas"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Dayakan penghalamanan"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Mendayakan penghalamanan melalui butang Ikhtisar"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Dayakan togol pantas"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Membolehkan pelancaran tamat masa semasa penghalamanan"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Dayakan tangkapan skrin skrin penuh"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Mendayakan tangkapan skrin skrin penuh dalam Ikhtisar"</string>
<string name="experimental" msgid="6198182315536726162">"Percubaan"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Hidupkan Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Untuk menyambungkan papan kekunci anda dengan tablet, anda perlu menghidupkan Bluetooth terlebih dahulu."</string>
diff --git a/packages/SystemUI/res/values-my-rMM/strings.xml b/packages/SystemUI/res/values-my-rMM/strings.xml
index 2f52a021cc0c..81c0932ce43e 100644
--- a/packages/SystemUI/res/values-my-rMM/strings.xml
+++ b/packages/SystemUI/res/values-my-rMM/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"မျက်နှာပြင် ပင်ထိုးမှု"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ရှာဖွေရန်"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> ကို မစနိုင်ပါ။"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"နောက်ထပ်"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"မှတ်တမ်း"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ရေပြင်ညီ ပိုင်းမည်"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ဒေါင်လိုက်ပိုင်းမည်"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"စိတ်ကြိုက် ပိုင်းမည်"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"အခြေအနေပြနေရာမှာ နာရီ စက္ကန့်များကို ပြပါ။ ဘက်ထရီ သက်တမ်းကို အကျိုးသက်ရောက်နိုင်တယ်။"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"အမြန် ဆက်တင်များကို ပြန်စီစဉ်ရန်"</string>
<string name="show_brightness" msgid="6613930842805942519">"အမြန် ဆက်တင်များထဲက တောက်ပမှုကို ပြရန်"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"စာမျက်နှာပြောင်းမှု ဖွင့်ပါ"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"အနှစ်ချုပ်ကြည့်မှု မှတစ်ဆင့် စာမျက်နှာပြောင်းမှုကို ဖွင့်ပါ"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"အမြန်ဖွင့်/ပိတ်ခြင်း ဖွင့်ရန်"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"စာမျက်နှာပြောင်းမှု ဖွင့်တင်ရန်အတွက် ကန့်သတ်ချိန်ကို ဖွင့်ပါ"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"မျက်နှာပြင်အပြည့် လျှပ်တစ်ပြက်ပုံများကို ဖွင့်ပါ"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"အနှစ်ချုပ်ကြည့်မှုထဲတွင် မျက်နှာပြင်အပြည့် လျှပ်တစ်ပြက်ပုံများကို ဖွင့်ပါ"</string>
<string name="experimental" msgid="6198182315536726162">"စမ်းသပ်ရေး"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"ဘလူးတုသ် ဖွင့်ရမလား။"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"ကီးဘုတ်ကို တပ်ဘလက်နှင့် ချိတ်ဆက်ရန်၊ ပမထဦးစွာ ဘလူးတုသ်ကို ဖွင့်ပါ။"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 014a6b19df2b..7f6895e87abf 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"én-appsmodus"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"Søk"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Kunne ikke starte <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mer"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Logg"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Del horisontalt"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Del vertikalt"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Del tilpasset"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Vis sekunder i statusfeltet på klokken. Det kan påvirke batteritiden."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Omorganiser hurtiginnstillingene"</string>
<string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i hurtiginnstillingene"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Slå på paginering"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Slå på paginering via Oversikt-knappen"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Slå på hurtigveksling"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Slå på tidsavbrudd mens paginering pågår"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Slå på skjermdumper av fullskjerm"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Slå på skjermdumper av fullskjerm i Oversikt"</string>
<string name="experimental" msgid="6198182315536726162">"På forsøksstadiet"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Vil du slå på Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"For å koble tastaturet til nettbrettet ditt må du først slå på Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-ne-rNP/strings.xml b/packages/SystemUI/res/values-ne-rNP/strings.xml
index 46fce99340bb..51e8a1678700 100644
--- a/packages/SystemUI/res/values-ne-rNP/strings.xml
+++ b/packages/SystemUI/res/values-ne-rNP/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"स्क्रिन पिन गर्दै"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"खोजी गर्नुहोस्"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"सुरु गर्न सकिएन <xliff:g id="APP">%s</xliff:g>।"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"थप"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"इतिहास"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"तेर्सो रूपमा विभाजन गर्नुहोस्"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ठाडो रूपमा विभाजन गर्नुहोस्"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"अनुकूलन विभाजन गर्नुहोस्"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"वस्तुस्थिति पट्टीको घडीमा सेकेन्ड देखाउनुहोस्। ब्याट्री आयु प्रभावित हुन सक्छ।"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"द्रुत सेटिङहरू पुनः व्यवस्थित गर्नुहोस्"</string>
<string name="show_brightness" msgid="6613930842805942519">"द्रुत सेटिङहरूमा उज्यालो देखाउनुहोस्"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"पेजिंग सक्रिय गर्नुहोस्"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"परिदृश्य बटन मार्फत पेजिङ सक्रिय गर्नुहोस्"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"छिटो टगल सक्रिय गर्नुहोस्"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"जब पेजिंग हुन्छ टाइमआउट प्रक्षेपण सक्रिय गर्नुहोस्"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"पूर्ण स्क्रिन स्क्रीनशटहरू सक्रिय गर्नुहोस्"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"परिदृश्यमा पूर्ण स्क्रिन स्क्रीनशटहरू सक्रिय गर्नुहोस्"</string>
<string name="experimental" msgid="6198182315536726162">"प्रयोगात्मक"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"ब्लुटुथ सक्रिय पार्ने हो?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"आफ्नो ट्याब्लेटसँग किबोर्ड जोड्न, पहिले तपाईँले ब्लुटुथ सक्रिय गर्नुपर्छ।"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index dbcaab8006c9..88d0b4b4bf35 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -283,7 +283,7 @@
<string name="quick_settings_notifications_label" msgid="4818156442169154523">"Meldingen"</string>
<string name="quick_settings_flashlight_label" msgid="2133093497691661546">"Zaklamp"</string>
<string name="quick_settings_cellular_detail_title" msgid="8575062783675171695">"Mobiele gegevens"</string>
- <string name="quick_settings_cellular_detail_data_usage" msgid="1964260360259312002">"Gegevensgebruik"</string>
+ <string name="quick_settings_cellular_detail_data_usage" msgid="1964260360259312002">"Datagebruik"</string>
<string name="quick_settings_cellular_detail_remaining_data" msgid="722715415543541249">"Resterende gegevens"</string>
<string name="quick_settings_cellular_detail_over_limit" msgid="967669665390990427">"Limiet overschreden"</string>
<string name="quick_settings_cellular_detail_data_used" msgid="1476810587475761478">"<xliff:g id="DATA_USED">%s</xliff:g> gebruikt"</string>
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"scherm vastzetten"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"zoeken"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Kan <xliff:g id="APP">%s</xliff:g> niet starten."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Meer"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Geschiedenis"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Horizontaal splitsen"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Verticaal splitsen"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Aangepast splitsen"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Klokseconden op de statusbalk weergeven. Kan van invloed zijn op de accuduur."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Snelle instellingen opnieuw indelen"</string>
<string name="show_brightness" msgid="6613930842805942519">"Helderheid weergeven in Snelle instellingen"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Paginering inschakelen"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Paginering via de knop Overzicht inschakelen"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Snel schakelen inschakelen"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Starttime-out tijdens paginering inschakelen"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Screenshots op volledig scherm inschakelen"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Screenshots op volledig scherm in Overzicht inschakelen"</string>
<string name="experimental" msgid="6198182315536726162">"Experimenteel"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth inschakelen?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Als je je toetsenbord wilt verbinden met je tablet, moet je eerst Bluetooth inschakelen."</string>
diff --git a/packages/SystemUI/res/values-pa-rIN/strings.xml b/packages/SystemUI/res/values-pa-rIN/strings.xml
index 5f21e94b19c5..df6c473afcc1 100644
--- a/packages/SystemUI/res/values-pa-rIN/strings.xml
+++ b/packages/SystemUI/res/values-pa-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ਸਕ੍ਰੀਨ ਪਿਨਿੰਗ"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ਖੋਜੋ"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> ਨੂੰ ਚਾਲੂ ਨਹੀਂ ਕਰ ਸਕਿਆ।"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"ਹੋਰ"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ਇਤਿਹਾਸ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"ਹੌਰੀਜ਼ੌਂਟਲ ਸਪਲਿਟ"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"ਵਰਟੀਕਲ ਸਪਲਿਟ"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"ਕਸਟਮ ਸਪਲਿਟ"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"ਸਥਿਤੀ ਬਾਰ ਵਿੱਚ ਘੜੀ ਸਕਿੰਟ ਦਿਖਾਓ। ਬੈਟਰੀ ਸਮਰੱਥਾ ਤੇ ਅਸਰ ਪੈ ਸਕਦਾ ਹੈ।"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਨੂੰ ਦੁਬਾਰਾ ਕ੍ਰਮ ਦਿਓ"</string>
<string name="show_brightness" msgid="6613930842805942519">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਚਮਕ ਦਿਖਾਓ"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"ਪੇਜਿੰਗ ਨੂੰ ਯੋਗ ਬਣਾਓ"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"ਰੂਪ-ਰੇਖਾ ਬਟਨ ਦੁਆਰਾ ਪੇਜਿੰਗ ਨੂੰ ਯੋਗ ਬਣਾਓ"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"ਤੇਜ਼ ਬਦਲੋ ਨੂੰ ਯੋਗ ਬਣਾਓ"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"ਪੇਜਿੰਗ ਦੌਰਾਨ ਲਾਂਚ ਸਮਾਂ ਸਮਾਪਤੀ ਦੀ ਮਿਅਾਦ ਯੋਗ ਬਣਾਓ"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"ਪੂਰੀ-ਸਕਰੀਨ ਸਕਰੀਨਸ਼ਾਟ ਨੂੰ ਯੋਗ ਬਣਾਓ"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"ਰੂਪ-ਰੇਖਾ ਵਿੱਚ ਪੂਰੀ-ਸਕਰੀਨ ਸਕਰੀਨਸ਼ਾਟ ਨੂੰ ਯੋਗ ਬਣਾਓ"</string>
<string name="experimental" msgid="6198182315536726162">"ਪ੍ਰਯੋਗਾਤਮਿਕ"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth ਚਾਲੂ ਕਰੋ?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"ਆਪਣੇ ਟੈਬਲੇਟ ਨਾਲ ਆਪਣਾ ਕੀ-ਬੋਰਡ ਕਨੈਕਟ ਕਰਨ ਲਈ, ਤੁਹਾਨੂੰ ਪਹਿਲਾਂ Bluetooth ਚਾਲੂ ਕਰਨ ਦੀ ਜ਼ਰੂਰਤ ਹੈ।"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 68816ef8b8ab..694132b8e3b1 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"przypinanie ekranu"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"szukaj"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Nie udało się uruchomić aplikacji <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Więcej"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historia"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Podziel poziomo"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Podziel pionowo"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Podziel niestandardowo"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Pokaż sekundy na zegarku na pasku stanu. Może mieć wpływ na czas pracy baterii."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Uporządkuj Szybkie ustawienia"</string>
<string name="show_brightness" msgid="6613930842805942519">"Pokaż jasność w Szybkich ustawieniach"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Włącz stronicowanie"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Włącz stronicowanie za pomocą przycisku Przegląd"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Włącz szybkie przełączanie"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Włącz limit czasu uruchomienia podczas stronicowania"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Włącz pełnoekranowe zrzuty ekranu"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Włącz pełnoekranowe zrzuty ekranu w widoku Przegląd"</string>
<string name="experimental" msgid="6198182315536726162">"Eksperymentalne"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Włączyć Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Aby połączyć klawiaturę z tabletem, musisz najpierw włączyć Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index c8759222db92..e806bfc8123c 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fixação de tela"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"pesquisar"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Não foi possível iniciar <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mais"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Histórico"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisão horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisão vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisão personalizada"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Mostrar segundos do relógio na barra de status. Pode afetar a duração da bateria."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar \"Configurações rápidas\""</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostrar brilho nas \"Configurações rápidas\""</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Ativar paginação"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Ativar paginação pelo botão \"Visão geral\""</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Ativar alternância rápida"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Ativar tempo limite de inicialização ao fazer paginação"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Ativar capturas de tela em tela cheia"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Ativar capturas de tela em tela cheia em \"Visão geral\""</string>
<string name="experimental" msgid="6198182315536726162">"Experimentais"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Ativar o Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar o teclado ao tablet, é preciso primeiro ativar o Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 1594dca99b06..468c75de2acd 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fixação no ecrã"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"pesquisar"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Não foi possível iniciar o <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mais"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Histórico"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisão horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisão vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisão personalizada"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Mostrar segundos do relógio na barra de estado. Pode afetar a autonomia da bateria."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar as Definições rápidas"</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostrar luminosidade nas Definições rápidas"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Ativar a paginação"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Ativar a paginação através do botão Vista geral"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Ativar alternância rápida"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Ativar o limite de tempo de lançamento durante a paginação"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Ativar capturas de ecrã inteiro"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Ativar capturas de ecrã inteiro na Vista geral"</string>
<string name="experimental" msgid="6198182315536726162">"Experimental"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Pretende ativar o Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Para ligar o teclado ao tablet, tem de ativar primeiro o Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index c8759222db92..e806bfc8123c 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fixação de tela"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"pesquisar"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Não foi possível iniciar <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mais"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Histórico"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divisão horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divisão vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divisão personalizada"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Mostrar segundos do relógio na barra de status. Pode afetar a duração da bateria."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Reorganizar \"Configurações rápidas\""</string>
<string name="show_brightness" msgid="6613930842805942519">"Mostrar brilho nas \"Configurações rápidas\""</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Ativar paginação"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Ativar paginação pelo botão \"Visão geral\""</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Ativar alternância rápida"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Ativar tempo limite de inicialização ao fazer paginação"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Ativar capturas de tela em tela cheia"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Ativar capturas de tela em tela cheia em \"Visão geral\""</string>
<string name="experimental" msgid="6198182315536726162">"Experimentais"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Ativar o Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Para conectar o teclado ao tablet, é preciso primeiro ativar o Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index ab182a2a46d7..1b8e8de1ca2a 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -295,6 +295,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fixare pe ecran"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"căutare"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> nu a putut porni."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mai mult"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Istoric"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Divizare pe orizontală"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Divizare pe verticală"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Divizare personalizată"</string>
@@ -438,12 +440,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Afișează secundele pe ceas în bara de stare. Poate afecta autonomia bateriei."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Rearanjați Setările rapide"</string>
<string name="show_brightness" msgid="6613930842805942519">"Afișați luminozitatea în Setările rapide"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Activați paginarea"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Activați paginarea prin butonul Recente"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Activați comutarea rapidă"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Activați timpul limită pentru lansare în timpul paginării"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Activați capturile de ecran pe ecran complet"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Activați capturile de ecran pe ecran complet în modul Prezentare generală"</string>
<string name="experimental" msgid="6198182315536726162">"Experimentale"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Activați Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Pentru a conecta tastatura la tabletă, mai întâi trebuie să activați Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index ecacd4b9c99d..9068e0b85517 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"Заблокировать в приложении"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"поиск"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Не удалось запустить приложение \"<xliff:g id="APP">%s</xliff:g>\""</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Ещё"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Журнал"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Разделить по горизонтали"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Разделить по вертикали"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Разделить по-другому"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Показывать в строке состояния время с точностью до секунды (заряд батареи может расходоваться быстрее)."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Изменить порядок Быстрых настроек"</string>
<string name="show_brightness" msgid="6613930842805942519">"Добавить яркость в Быстрые настройки"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Включить подкачку страниц"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Разрешить подкачку страниц с помощью кнопки \"Обзор\""</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Быстрое переключение"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Включить тайм-аут запуска при подкачке страниц"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Разрешить скриншоты всего экрана"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Разрешить скриншоты всего экрана в режиме обзора"</string>
<string name="experimental" msgid="6198182315536726162">"Экспериментальная функция"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Подключение по Bluetooth"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Чтобы подключить клавиатуру к планшету, включите Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-si-rLK/strings.xml b/packages/SystemUI/res/values-si-rLK/strings.xml
index a8f9469c6fa1..3be371832a75 100644
--- a/packages/SystemUI/res/values-si-rLK/strings.xml
+++ b/packages/SystemUI/res/values-si-rLK/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"තිර ඇමිණීම"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"සෙවීම"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> ආරම්භ කළ නොහැක."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"තවත්"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ඉතිහාසය"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"තිරස්ව වෙන් කරන්න"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"සිරස්ව වෙන් කරන්න"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"අභිමත ලෙස වෙන් කරන්න"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"තත්ත්ව තීරුවෙහි ඔරලෝසු තත්පර පෙන්වන්න. බැටරි ආයු කාලයට බලපෑමට හැකිය."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"ඉක්මන් සැකසීම් යළි පිළිවෙළට සකසන්න"</string>
<string name="show_brightness" msgid="6613930842805942519">"ඉක්මන් සැකසීම්වල දීප්තිය පෙන්වන්න"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"පේජින් සබල කිරීම"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"දළ විශ්ලේෂණ බොත්තම හරහා පේජින් සබල කිරීම"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"වේගවත් ටොගල කිරීම සබල කරන්න"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"පේජින් අතරතුර දියත් කිරීමේ කාල නිමාව සබල කිරීම"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"පූර්ණ තිරයේ තිර රූ සබල කිරීම"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"දළ විශ්ලේෂණය තුළ පූර්ණ තිරයේ තිර රූ සබල කිරීම"</string>
<string name="experimental" msgid="6198182315536726162">"පරීක්ෂණාත්මක"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"බ්ලූටූත් ක්‍රියාත්මක කරන්නද?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"ඔබේ යතුරු පුවරුව ඔබේ ටැබ්ලට් පරිගණකයට සම්බන්ධ කිරීමට, ඔබ පළමුව බ්ලූටූත් ක්‍රියාත්මක කළ යුතුය."</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index bd7e210c3307..d96194c6ada4 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"pripnutie k obrazovke"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"hľadať"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Aplikáciu <xliff:g id="APP">%s</xliff:g> sa nepodarilo spustiť"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Viac"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"História"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Rozdeliť vodorovné"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Rozdeliť zvislé"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Rozdeliť vlastné"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Zobrazí sekundy v stavovom riadku. Môže to ovplyvňovať výdrž batérie."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Zmeniť usporiadanie Rýchlych nastavení"</string>
<string name="show_brightness" msgid="6613930842805942519">"Zobraziť jas v Rýchlych nastaveniach"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Povoliť prechádzanie po stranách"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Povoľte prechádzanie po stranách prostredníctvom tlačidla Prehľad"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Povoliť rýchle prepínanie"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Povolenie časového limitu pre spustenie počas prechádzania po stranách"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Povoliť snímky celej obrazovky"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Povolenie snímok celej obrazovky v Prehľade"</string>
<string name="experimental" msgid="6198182315536726162">"Experimentálne"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Zapnúť Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Ak chcete klávesnicu pripojiť k tabletu, najprv musíte zapnúť Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 6d7fbc0f9f9a..f3d3b3947d16 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"pripenjanje zaslona"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"iskanje"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Aplikacije <xliff:g id="APP">%s</xliff:g> ni bilo mogoče zagnati."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Več"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Zgodovina"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Razdeli vodoravno"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Razdeli navpično"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Razdeli po meri"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Prikaže sekunde pri uri v vrstici stanja. To lahko vpliva na čas delovanja pri akumulatorskem napajanju."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Preuredi hitre nastavitve"</string>
<string name="show_brightness" msgid="6613930842805942519">"Prikaz svetlosti v hitrih nastavitvah"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Omogočanje pregleda strani"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Omogoči pregled strani z gumbom za pregled"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Omogoči hiter preklop"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Omogoči potek časovne omejitve za odpiranje med pregledom strani"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Omogočanje celozaslonskih posnetkov zaslona"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Omogoči celozaslonske posnetke zaslona v Pregledu"</string>
<string name="experimental" msgid="6198182315536726162">"Poskusno"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Želite vklopiti Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Če želite povezati tipkovnico in tablični računalnik, vklopite Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-sq-rAL/strings.xml b/packages/SystemUI/res/values-sq-rAL/strings.xml
index 38066be685b6..afad77229161 100644
--- a/packages/SystemUI/res/values-sq-rAL/strings.xml
+++ b/packages/SystemUI/res/values-sq-rAL/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"gozhdimi i ekranit"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"kërko"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> nuk mundi të nisej."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Më shumë"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historiku"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Ndaje horizontalisht"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Ndaj vertikalisht"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Ndaj të personalizuarën"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Trego sekondat e orës në shiritin e statusit. Mund të ndikojë te jeta e baterisë."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Risistemo Cilësimet e shpejta"</string>
<string name="show_brightness" msgid="6613930842805942519">"Shfaq ndriçimin te Cilësimet e shpejta"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Aktivizo shfletimin"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Aktivizo shfletimin me butonin \"Përmbledhja\""</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Aktivizo ndërrimin e shpejtë"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Aktivizo kohën e pritjes të nisjes gjatë shfletimit"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Aktivizo pamjet e ekranit në ekranin e plotë"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Aktivizo pamjet e ekranit në ekranin e plotë te \"Përmbledhja\""</string>
<string name="experimental" msgid="6198182315536726162">"Eksperimentale"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Të aktivizohet \"bluetooth-i\"?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Për të lidhur tastierën me tabletin, në fillim duhet të aktivizosh \"bluetooth-in\"."</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 5fd78f06aeaa..ad14d2604773 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -295,6 +295,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"качење екрана"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"претражи"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Покретање апликације <xliff:g id="APP">%s</xliff:g> није успело."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Још"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Историја"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Подели хоризонтално"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Подели вертикално"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Прилагођено дељење"</string>
@@ -438,12 +440,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Секунде на сату се приказују на статусној траци. То може да утиче на трајање батерије."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Преуреди Брза подешавања"</string>
<string name="show_brightness" msgid="6613930842805942519">"Прикажи осветљеност у Брзим подешавањима"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Омогући листање"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Омогућава листање помоћу дугмета Преглед"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Омогући брзо листање"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Омогућава временско ограничење покретања при листању"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Омогући снимке екрана преко целог екрана"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Омогућава снимке екрана преко целог екрана у Прегледу"</string>
<string name="experimental" msgid="6198182315536726162">"Експериментално"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Желите ли да укључите Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Да бисте повезали тастатуру са таблетом, прво морате да укључите Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index bec7593f341b..15e8b121d163 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"fästa skärmen"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"sök"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Det gick inte att starta appen <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Mer"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historik"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Dela horisontellt"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Dela vertikalt"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Dela anpassad"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Visa klocksekunder i statusfältet. Detta kan påverka batteritiden."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Ordna snabbinställningarna"</string>
<string name="show_brightness" msgid="6613930842805942519">"Visa ljusstyrka i snabbinställningarna"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Aktivera sidindelning"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Aktivera sidindelning via knappen Översikt"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Aktivera snabb aktivering och inaktivering"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Aktivera timeout i lanseringen vid sidindelning"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Aktivera skärmdumpar på helskärm"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Aktivera skärmdumpar på helskärm i översikten"</string>
<string name="experimental" msgid="6198182315536726162">"Experimentella"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Vill du aktivera Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Om du vill ansluta tangentbordet till surfplattan måste du först aktivera Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 8cc6712fcb69..1d231fcf7612 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"kudumisha programu moja"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"tafuta"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Haikuweza kuanzisha <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Zaidi"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Historia"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Gawanya Mlalo"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Gawanya Wima"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Maalum Iliyogawanywa"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Onyesha sekunde za saa katika sehemu ya arifa. Inaweza kuathiri muda wa matumizi ya betri."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Panga Upya Mipangilio ya Haraka"</string>
<string name="show_brightness" msgid="6613930842805942519">"Onyesha unga\'avu katika Mipangilio ya Haraka"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Washa kipengee cha kupata kumbukumbu"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Washa kipengee cha kupata kumbukumbu kupitia kitufe cha Muhtasari"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Washa kugeuza haraka"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Washa kipengee cha kuisha kwa muda wa uzinduzi unapopata kumbukumbu"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Washa picha za skrini nzima"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Washa picha za skrini nzima katika Muhtasari"</string>
<string name="experimental" msgid="6198182315536726162">"Ya majaribio"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Je, ungependa kuwasha Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Ili uunganishe Kibodi yako kwenye kompyuta yako kibao, lazima kwanza uwashe Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-ta-rIN/strings.xml b/packages/SystemUI/res/values-ta-rIN/strings.xml
index 5896c30010b7..c41494b0c76a 100644
--- a/packages/SystemUI/res/values-ta-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ta-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"திரையை பின் செய்தல்"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"தேடு"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g>ஐத் தொடங்க முடியவில்லை."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"மேலும்"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"வரலாறு"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"கிடைமட்டமாகப் பிரி"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"செங்குத்தாகப் பிரி"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"தனிவிருப்பத்தில் பிரி"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"நிலைப் பட்டியில் கடிகார வினாடிகளைக் காட்டும். பேட்டரியின் ஆயுளைக் குறைக்கலாம்."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"விரைவு அமைப்புகளை மறுவரிசைப்படுத்து"</string>
<string name="show_brightness" msgid="6613930842805942519">"விரைவு அமைப்புகளில் ஒளிர்வுப் பட்டியைக் காட்டு"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"பேஜிங்கை இயக்கு"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"மேலோட்டப் பார்வை பொத்தான் வழியாக பேஜிங்கை இயக்கு"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"வேகமாக மாறுவதை இயக்கு"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"பேஜிங்கின் போது தொடக்க நேர முடிவை இயக்கு"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"முழுத்திரை ஸ்கிரீன் ஷாட்களை இயக்கு"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"மேலோட்டப் பார்வையில் முழுத்திரை ஸ்கிரீன் ஷாட்களை இயக்கு"</string>
<string name="experimental" msgid="6198182315536726162">"சோதனை முயற்சி"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"புளூடூத்தை இயக்கவா?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"உங்கள் டேப்லெட்டுடன் விசைப்பலகையை இணைக்க, முதலில் புளூடூத்தை இயக்க வேண்டும்."</string>
diff --git a/packages/SystemUI/res/values-te-rIN/strings.xml b/packages/SystemUI/res/values-te-rIN/strings.xml
index dccac8ba7bf3..a628ef74d55c 100644
--- a/packages/SystemUI/res/values-te-rIN/strings.xml
+++ b/packages/SystemUI/res/values-te-rIN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"స్క్రీన్ పిన్నింగ్"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"శోధించు"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g>ని ప్రారంభించడం సాధ్యపడలేదు."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"మరింత"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"చరిత్ర"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"సమతలంగా విభజించు"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"లంబంగా విభజించు"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"అనుకూలంగా విభజించు"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"స్థితి పట్టీలో గడియారం సెకన్లు చూపుతుంది. బ్యాటరీ శక్తి ప్రభావితం చేయవచ్చు."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"శీఘ్ర సెట్టింగ్‌ల ఏర్పాటు క్రమం మార్చు"</string>
<string name="show_brightness" msgid="6613930842805942519">"శీఘ్ర సెట్టింగ్‌ల్లో ప్రకాశం చూపు"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"పేజింగ్‌ను ప్రారంభించు"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"స్థూలదృష్టి బటన్ ద్వారా పేజింగ్‌ను ప్రారంభిస్తుంది"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"వేగవంతమైన టోగుల్‌ను ప్రారంభించు"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"పేజింగ్ చేస్తున్నప్పుడు గడువు ముగింపు లాంచ్ చేయిని ప్రారంభిస్తుంది"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"పూర్తి స్క్రీన్ స్క్రీన్‌షాట్‌లను ప్రారంభించండి"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"స్థూలదృష్టిలో పూర్తి స్క్రీన్ స్క్రీన్‌షాట్‌లను ప్రారంభిస్తుంది"</string>
<string name="experimental" msgid="6198182315536726162">"ప్రయోగాత్మకం"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"బ్లూటూత్ ఆన్ చేయాలా?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"మీ కీబోర్డ్‌ను మీ టాబ్లెట్‌తో కనెక్ట్ చేయడానికి, మీరు ముందుగా బ్లూటూత్ ఆన్ చేయాలి."</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 625b676b7829..44ad1fa0e615 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"การตรึงหน้าจอ"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ค้นหา"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"ไม่สามารถเริ่มใช้ <xliff:g id="APP">%s</xliff:g>"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"เพิ่มเติม"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"ประวัติ"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"แยกในแนวนอน"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"แยกในแนวตั้ง"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"แยกแบบกำหนดเอง"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"แสดงวินาทีของนาฬิกาในแถบสถานะ อาจส่งผลต่ออายุแบตเตอรี"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"จัดเรียงการตั้งค่าด่วนใหม่"</string>
<string name="show_brightness" msgid="6613930842805942519">"แสดงความสว่างในการตั้งค่าด่วน"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"เปิดใช้การสลับหน้า"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"เปิดใช้การสลับหน้าผ่านทางปุ่มภาพรวม"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"เปิดใช้การสลับแบบด่วน"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"เปิดใช้ระยะหมดเวลาการเปิดขณะสลับหน้า"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"เปิดใช้ภาพหน้าจอแบบเต็มจอ"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"เปิดใช้ภาพหน้าจอแบบเต็มจอใน \"ภาพรวม\""</string>
<string name="experimental" msgid="6198182315536726162">"ทดสอบ"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"เปิดบลูทูธไหม"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"หากต้องการเชื่อมต่อแป้นพิมพ์กับแท็บเล็ต คุณต้องเปิดบลูทูธก่อน"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index e3b8e24ba54c..c1bb85c62477 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"pagpi-pin sa screen"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"maghanap"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Hindi masimulan <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Higit pa"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"History"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Split Horizontal"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Split Vertical"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Split Custom"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Ipakita ang mga segundo ng orasan sa status bar. Maaaring makaapekto sa tagal ng baterya."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Ayusing Muli ang Mga Mabilisang Setting"</string>
<string name="show_brightness" msgid="6613930842805942519">"Ipakita ang liwanag sa Mga Mabilisang Setting"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"I-enable ang paging"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"I-enable ang paging sa pamamagitan ng button na Pangkalahatang-ideya"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"I-enable ang mabilis na pag-toggle"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"I-enable ang pag-timeout ng paglunsad habang nagsasagawa ng paging"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"I-enable ang mga fullscreen na screenshot"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"I-enable ang mga fullscreen na screenshot sa Pangkalahatang-ideya"</string>
<string name="experimental" msgid="6198182315536726162">"Pang-eksperimento"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"I-on ang Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Upang ikonekta ang iyong keyboard sa iyong tablet, kailangan mo munang i-on ang Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 2fe49e5f0f32..854f850931cd 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ekran sabitleme"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ara"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> başlatılamadı."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Diğer"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Geçmiş"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Yatay Ayırma"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Dikey Ayırma"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Özel Ayırma"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Durum çubuğunda saatin saniyelerini gösterir. Pil ömrünü etkileyebilir."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Hızlı Ayarlar\'ı Yeniden Düzenle"</string>
<string name="show_brightness" msgid="6613930842805942519">"Hızlı Ayarlar\'da parlaklığı göster"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Sayfalara ayırmayı etkinleştir"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Genel bakış düğmesiyle sayfalara ayırmayı etkinleştirin"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Hızlı açma/kapatmayı etkinleştir"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Sayfalara ayırma sırasında başlatma zaman aşımını etkinleştirin"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Tam ekran boyutunda ekran görüntülerini etkinleştir"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Genel bakışta tam ekran boyutundaki ekran görüntülerini etkinleştirin"</string>
<string name="experimental" msgid="6198182315536726162">"Deneysel"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth açılsın mı?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Klavyenizi tabletinize bağlamak için önce Bluetooth\'u açmanız gerekir."</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 5a749c451f69..5347d93016db 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -296,6 +296,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"закріпити екран"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"пошук"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Не вдалося запустити <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Більше"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Історія"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Розділити горизонтально"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Розділити вертикально"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Розділити (власний варіант)"</string>
@@ -439,12 +441,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Показувати секунди на годиннику в рядку стану. Акумулятор може розряджатися швидше."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Упорядкувати швидкі налаштування"</string>
<string name="show_brightness" msgid="6613930842805942519">"Показувати панель яскравості у швидких налаштуваннях"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Увімкнути перехід між сторінками"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Увімкнути перехід між сторінками за допомогою кнопки \"Огляд\""</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Увімкнути швидкий перехід"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Увімкнути очікування на запуск під час переходу на сторінку"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Увімкнути знімки повного екрана"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Увімкнути знімки повного екрана в режимі огляду"</string>
<string name="experimental" msgid="6198182315536726162">"Експериментальні налаштування"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Увімкнути Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Щоб під’єднати клавіатуру до планшета, спершу потрібно ввімкнути Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-ur-rPK/strings.xml b/packages/SystemUI/res/values-ur-rPK/strings.xml
index 7dcb076bfb96..b9da6e3395c6 100644
--- a/packages/SystemUI/res/values-ur-rPK/strings.xml
+++ b/packages/SystemUI/res/values-ur-rPK/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"اسکرین کو پن کرنا"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"تلاش کریں"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"<xliff:g id="APP">%s</xliff:g> کو شروع نہیں کیا جا سکا۔"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"مزید"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"سرگزشت"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"بلحاظ افقی الگ کریں"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"بلحاظ عمودی الگ کریں"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"بلحاظ حسب ضرورت الگ کریں"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"گھڑی کے سیکنڈز اسٹیٹس بار میں دکھائیں۔ اس کا بیٹری کی زندگی پر اثر پڑ سکتا ہے۔"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"فوری ترتیبات کو دوبارہ ترتیب دیں"</string>
<string name="show_brightness" msgid="6613930842805942519">"فوری ترتیبات میں چمکیلا پن دکھائیں"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"پیجنگ فعال کریں"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"مجموعی جائزہ بٹن کے ذریعے پیجنگ فعال کریں"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"تیز ٹوگل فعال کریں"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"پیجنگ کے دوران لانچ ٹائم آؤٹ فعال کریں"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"فل اسکرین کے اسکرین شاٹس فعال کریں"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"مجموعی جائزے میں فل اسکرین کے اسکرین شاٹس فعال کریں"</string>
<string name="experimental" msgid="6198182315536726162">"تجرباتی"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"بلوٹوتھ آن کریں؟"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"اپنے کی بورڈ کو اپنے ٹیبلٹ کے ساتھ منسلک کرنے کیلئے پہلے آپ کو اپنا بلو ٹوتھ آن کرنا ہو گا۔"</string>
diff --git a/packages/SystemUI/res/values-uz-rUZ/strings.xml b/packages/SystemUI/res/values-uz-rUZ/strings.xml
index 1f26da660e73..67cd123d700c 100644
--- a/packages/SystemUI/res/values-uz-rUZ/strings.xml
+++ b/packages/SystemUI/res/values-uz-rUZ/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"o‘zgarmas ekran"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"qidirish"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"“<xliff:g id="APP">%s</xliff:g>” ilovasini ishga tushirib bo‘lmadi."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Ko‘proq"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Jurnal"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Gorizontal yo‘nalishda bo‘lish"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikal yo‘nalishda bo‘lish"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Boshqa usulda bo‘lish"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Holat panelida soat soniyalari ko‘rsatilsin. Bu batareya resursiga ta’sir qilishi mumkin."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Tezkor sozlamalarni qayta tartiblash"</string>
<string name="show_brightness" msgid="6613930842805942519">"Tezkor sozlamalarda yorqinlikni ko‘rsatish"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Sahifalashni yoqish"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Umumiy ma’lumot tugmasi orqali sahifalashni yoqish"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Tezkor almashtirishni yoqish"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Sahifalashda tanaffusni yoqish"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"To‘liq ekranni skrinshotga olishni yoqish"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Umumiy rejimda to‘liq ekranni skrinshotga olishni yoqish"</string>
<string name="experimental" msgid="6198182315536726162">"Tajribaviy"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bluetooth yoqilsinmi?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Klaviaturani planshetingizga ulash uchun Bluetooth xizmatini yoqishingiz kerak."</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 41ecb4567085..aa8106896027 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"khóa màn hình"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"tìm kiếm"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Không thể khởi động <xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Thêm"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Lịch sử"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Phân tách ngang"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Phân tách dọc"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Tùy chỉnh phân tách"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Hiển thị giây đồng hồ trong thanh trạng thái. Có thể ảnh hưởng đến thời lượng pin."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Sắp xếp lại Cài đặt nhanh"</string>
<string name="show_brightness" msgid="6613930842805942519">"Hiển thị độ sáng trong Cài đặt nhanh"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Bật đánh số trang"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Bật đánh số trang qua nút Tổng quan"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Bật chuyển đổi nhanh"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Bật thời gian chờ khởi chạy trong khi đánh số trang"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Bật ảnh chụp toàn màn hình"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Bật ảnh chụp toàn màn hình trong Tổng quan"</string>
<string name="experimental" msgid="6198182315536726162">"Thử nghiệm"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Bật Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Để kết nối bàn phím với máy tính bảng, trước tiên, bạn phải bật Bluetooth."</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index cc78ef7286fd..fa69975ad942 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"固定屏幕"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"搜索"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"无法启动<xliff:g id="APP">%s</xliff:g>。"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"更多"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"历史记录"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"水平分割"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"垂直分割"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"自定义分割"</string>
@@ -437,17 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"在状态栏中显示时钟的秒数。这可能会影响电池的续航时间。"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速设置"</string>
<string name="show_brightness" msgid="6613930842805942519">"在快速设置中显示亮度栏"</string>
- <!-- no translation found for overview_page_on_toggle (7332906295136546986) -->
- <skip />
- <!-- no translation found for overview_page_on_toggle_desc (3350421878356386241) -->
- <skip />
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"启用快速切换"</string>
- <!-- no translation found for overview_fast_toggle_via_button_desc (8983671478896649561) -->
- <skip />
- <!-- no translation found for overview_fullscreen_thumbnails (3052584848522856237) -->
- <skip />
- <!-- no translation found for overview_fullscreen_thumbnails_desc (3588874352141119692) -->
- <skip />
<string name="experimental" msgid="6198182315536726162">"实验性"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"要开启蓝牙吗?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"要将您的键盘连接到平板电脑,您必须先开启蓝牙。"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 1a0291418d7f..7375a92bedd9 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"螢幕固定"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"搜尋"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"無法啟動「<xliff:g id="APP">%s</xliff:g>」。"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"更多"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"記錄"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"水平分割"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"垂直分割"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"自訂分割"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"在狀態列中顯示時鐘秒數,但可能會影響電池壽命。"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速設定"</string>
<string name="show_brightness" msgid="6613930842805942519">"在快速設定顯示亮度"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"啟用分頁"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"透過「概覽」按鈕啟用分頁"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"啟用快速切換"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"進行分頁時啟用啟動逾時"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"啟用全螢幕擷取畫面"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"在「概覽」啟用全螢幕擷取畫面"</string>
<string name="experimental" msgid="6198182315536726162">"實驗版"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"要開啟藍牙嗎?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"如要將鍵盤連接至平板電腦,請先開啟藍牙。"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 659334d821d8..5edda8461e57 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"螢幕固定"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"搜尋"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"無法啟動「<xliff:g id="APP">%s</xliff:g>」。"</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"更多"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"紀錄"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"水平分割"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"垂直分割"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"自訂分割"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"在狀態列中顯示時鐘秒數。這可能會影響電池續航力。"</string>
<string name="qs_rearrange" msgid="8060918697551068765">"重新排列快速設定"</string>
<string name="show_brightness" msgid="6613930842805942519">"在快速設定中顯示亮度"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"啟用分頁功能"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"啟用透過 [總覽] 按鈕切換分頁的功能"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"啟用快速切換"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"切換分頁時啟用啟動逾時功能"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"啟用全螢幕的螢幕擷取畫面"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"在「總覽」畫面中啟用全螢幕的螢幕擷取畫面"</string>
<string name="experimental" msgid="6198182315536726162">"實驗性"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"要開啟藍牙功能嗎?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"如要將鍵盤連線到平板電腦,您必須先開啟藍牙。"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 8ba9363c77f6..51c4389d746b 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -294,6 +294,8 @@
<string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"ukuphina isikrini"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"sesha"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"Ayikwazanga ukuqala i-<xliff:g id="APP">%s</xliff:g>."</string>
+ <string name="recents_show_history_button_label" msgid="7062088196449747245">"Okuningi"</string>
+ <string name="recents_history_label" msgid="3076213823382198287">"Umlando"</string>
<string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Hlukanisa okuvundlile"</string>
<string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Hlukanisa okumile"</string>
<string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Hlukanisa kwezifiso"</string>
@@ -437,12 +439,6 @@
<string name="clock_seconds_desc" msgid="6282693067130470675">"Bonisa amasekhondi wewashi kubha yesimo. Ingathinta impilo yebhethri."</string>
<string name="qs_rearrange" msgid="8060918697551068765">"Hlela kabusha izilungiselelo ezisheshayo"</string>
<string name="show_brightness" msgid="6613930842805942519">"Bonisa ukukhanya kuzilungiselelo ezisheshayo"</string>
- <string name="overview_page_on_toggle" msgid="7332906295136546986">"Nika amandla ukuphenya"</string>
- <string name="overview_page_on_toggle_desc" msgid="3350421878356386241">"Nika amandla wenkinobho yokubuka konke"</string>
- <string name="overview_fast_toggle_via_button" msgid="8316769524084143500">"Nika amandla ukuguqula ngokushesha"</string>
- <string name="overview_fast_toggle_via_button_desc" msgid="8983671478896649561">"Nika amandla ukuvula kokuphela kwesikhathi ngesikhathi uphenya"</string>
- <string name="overview_fullscreen_thumbnails" msgid="3052584848522856237">"Nika amandla izithombe-skrini ezigcwele"</string>
- <string name="overview_fullscreen_thumbnails_desc" msgid="3588874352141119692">"Nika amandla izithombe-skrini ezigcwele ngokubuka konke"</string>
<string name="experimental" msgid="6198182315536726162">"Okokulinga"</string>
<string name="enable_bluetooth_title" msgid="5027037706500635269">"Vula i-Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="9106595990708985385">"Ukuze uxhume ikhibhodi yakho nethebhulethi yakho, kufanele uqale ngokuvula i-Bluetooth."</string>
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/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 96ee39756513..d931856a822b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -180,7 +180,7 @@ public class KeyboardUI extends SystemUI implements InputManager.OnTabletModeCha
mProfileManager = bluetoothManager.getProfileManager();
bluetoothManager.getEventManager().registerCallback(new BluetoothCallbackHandler());
- InputManager im = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
+ InputManager im = context.getSystemService(InputManager.class);
im.registerOnTabletModeChangedListener(this, mHandler);
mInTabletMode = im.isInTabletMode();
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/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index f32cfdc7b417..4cc2b8d191ee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -222,7 +222,6 @@ public class QSTileView extends QSTileBaseView {
final ImageView icon = new ImageView(mContext);
icon.setId(android.R.id.icon);
icon.setScaleType(ScaleType.CENTER_INSIDE);
- icon.setImageTintList(ColorStateList.valueOf(getContext().getColor(android.R.color.white)));
return icon;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
index b0d885a3a49e..d26e8d64dc82 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
@@ -23,6 +23,7 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Drawable;
import android.os.IBinder;
import android.os.UserHandle;
import android.service.quicksettings.IQSTileService;
@@ -132,7 +133,9 @@ public class CustomTile extends QSTile<QSTile.State> {
@Override
protected void handleUpdateState(State state, Object arg) {
state.visible = true;
- state.icon = new DrawableIcon(mTile.getIcon().loadDrawable(mContext));
+ Drawable drawable = mTile.getIcon().loadDrawable(mContext);
+ drawable.setTint(mContext.getColor(android.R.color.white));
+ state.icon = new DrawableIcon(drawable);
state.label = mTile.getLabel();
if (mTile.getContentDescription() != null) {
state.contentDescription = mTile.getContentDescription();
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/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 6f8cd8c10c8b..da3cd54a91e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -130,6 +130,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private final int mLowPriorityColor;
private boolean mIsBelowSpeedBump;
private FalsingManager mFalsingManager;
+ private boolean mTrackTouch;
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -175,12 +176,27 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
};
@Override
- public boolean onTouchEvent(MotionEvent event) {
- if (mDimmed) {
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (mDimmed && !mActivated) {
return handleTouchEventDimmed(event);
} else {
- return super.onTouchEvent(event);
+ return super.dispatchTouchEvent(event);
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean result;
+ if (mDimmed && mActivated) {
+ result = handleTouchEventDimmed(event);
+ } else {
+ result = super.onTouchEvent(event);
+ }
+ if (mActivated && result && event.getAction() == MotionEvent.ACTION_UP) {
+ mFalsingManager.onNotificationDoubleTap();
+ removeCallbacks(mTapTimeoutRunnable);
}
+ return result;
}
@Override
@@ -206,14 +222,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
case MotionEvent.ACTION_DOWN:
mDownX = event.getX();
mDownY = event.getY();
+ mTrackTouch = true;
if (mDownY > getActualHeight()) {
- return false;
+ mTrackTouch = false;
}
break;
case MotionEvent.ACTION_MOVE:
if (!isWithinTouchSlop(event)) {
makeInactive(true /* animate */);
- return false;
+ mTrackTouch = false;
}
break;
case MotionEvent.ACTION_UP:
@@ -222,23 +239,23 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
makeActive();
postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
} else {
- mFalsingManager.onNotificationDoubleTap();
- boolean performed = performClick();
- if (performed) {
- removeCallbacks(mTapTimeoutRunnable);
+ if (!performClick()) {
+ return false;
}
}
} else {
makeInactive(true /* animate */);
+ mTrackTouch = false;
}
break;
case MotionEvent.ACTION_CANCEL:
makeInactive(true /* animate */);
+ mTrackTouch = false;
break;
default:
break;
}
- return true;
+ return mTrackTouch;
}
private void makeActive() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 723989affae6..3c7ff7f13146 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;
@@ -117,7 +116,8 @@ import static com.android.keyguard.KeyguardHostView.OnDismissAction;
public abstract class BaseStatusBar extends SystemUI implements
CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
- ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment {
+ ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
+ ExpandableNotificationRow.OnExpandClickListener {
public static final String TAG = "StatusBar";
public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final boolean MULTIUSER_DEBUG = false;
@@ -193,6 +193,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 +888,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 +1291,22 @@ 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();
+ mStackScroller.onHeightChanged(null, false);
+ }
+ }
+
+ @Override
+ public void onExpandClicked(View clickedView, boolean nowExpanded) {
+ }
+
protected class H extends Handler {
public void handleMessage(Message m) {
switch (m.what) {
@@ -1337,7 +1345,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);
@@ -1380,6 +1387,7 @@ public abstract class BaseStatusBar extends SystemUI implements
parent, false);
row.setExpansionLogger(this, entry.notification.getKey());
row.setGroupManager(mGroupManager);
+ row.setOnExpandClickListener(this);
}
workAroundBadLayerDrawableOpacity(row);
@@ -1498,18 +1506,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 +1532,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 +1544,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 +1557,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 +1981,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 +1997,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 +2016,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 +2191,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..7a94a58297b3 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,37 @@ 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 OnExpandClickListener mOnExpandClickListener;
+ private OnClickListener mExpandClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mGroupManager.isSummaryOfGroup(mStatusBarNotification)) {
+ mGroupManager.toggleGroupExpansion(mStatusBarNotification);
+ mOnExpandClickListener.onExpandClicked(ExpandableNotificationRow.this,
+ mGroupManager.isGroupExpanded(mStatusBarNotification));
+ } else {
+ boolean nowExpanded = !isExpanded();
+ setUserExpanded(nowExpanded);
+ notifyHeightChanged(true);
+ mOnExpandClickListener.onExpandClicked(ExpandableNotificationRow.this,
+ nowExpanded);
+ }
+ }
+ };
public NotificationContentView getPrivateLayout() {
return mPrivateLayout;
@@ -117,6 +133,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 +187,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 +414,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);
@@ -399,6 +427,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mPrivateLayout.setSubTextVisible(visible);
}
+ public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) {
+ mOnExpandClickListener = onExpandClickListener;
+ }
+
public interface ExpansionLogger {
public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
@@ -452,6 +484,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 +496,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 +519,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
return;
}
mChildrenContainer.setVisibility(mIsSummaryWithChildren ? VISIBLE : INVISIBLE);
+ mNotificationHeader.setVisibility(mIsSummaryWithChildren ? VISIBLE : INVISIBLE);
mPrivateLayout.setVisibility(!mIsSummaryWithChildren ? VISIBLE : INVISIBLE);
}
@@ -523,6 +549,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 +562,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
public void setExpandable(boolean expandable) {
mExpandable = expandable;
+ mPrivateLayout.updateExpandButtons(isExpandable());
}
/**
@@ -654,8 +683,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 +709,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 +805,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
animateShowingPublic(delay, duration);
}
+ mPrivateLayout.updateExpandButtons(isExpandable());
updateVetoButton();
mShowingPublicInitialized = true;
}
@@ -811,23 +846,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 +899,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 +907,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 +934,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() {
@@ -958,4 +996,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ;
}
}
+
+ public interface OnExpandClickListener {
+ void onExpandClicked(View clickedView, boolean nowExpanded);
+ }
}
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/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 6d1394758cfb..71904fa707e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -48,8 +48,8 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
*/
public class KeyguardIndicationController {
- private static final String TAG = "KeyguardIndicationController";
- private static final boolean DEBUG_CHARGING_CURRENT = false;
+ private static final String TAG = "KeyguardIndication";
+ private static final boolean DEBUG_CHARGING_SPEED = false;
private static final int MSG_HIDE_TRANSIENT = 1;
private static final int MSG_CLEAR_FP_MSG = 2;
@@ -72,7 +72,7 @@ public class KeyguardIndicationController {
private boolean mPowerPluggedIn;
private boolean mPowerCharged;
private int mChargingSpeed;
- private int mChargingCurrent;
+ private int mChargingWattage;
private String mMessageToShowOnScreenOn;
public KeyguardIndicationController(Context context, KeyguardIndicationTextView textView,
@@ -173,8 +173,8 @@ public class KeyguardIndicationController {
}
if (mPowerPluggedIn) {
String indication = computePowerIndication();
- if (DEBUG_CHARGING_CURRENT) {
- indication += ", " + (mChargingCurrent / 1000) + " mA";
+ if (DEBUG_CHARGING_SPEED) {
+ indication += ", " + (mChargingWattage / 1000) + " mW";
}
return indication;
}
@@ -231,7 +231,7 @@ public class KeyguardIndicationController {
|| status.status == BatteryManager.BATTERY_STATUS_FULL;
mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull;
mPowerCharged = status.isCharged();
- mChargingCurrent = status.maxChargingCurrent;
+ mChargingWattage = status.maxChargingWattage;
mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold);
updateIndication();
}
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..fb8086c3828e 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);
@@ -426,6 +431,11 @@ public class NotificationContentView extends FrameLayout {
*/
private int calculateVisibleType() {
boolean noExpandedChild = mExpandedChild == null;
+
+ if (!noExpandedChild && mContentHeight == mExpandedChild.getHeight()) {
+ return VISIBLE_TYPE_EXPANDED;
+ }
+
if (mIsHeadsUp && mHeadsUpChild != null) {
if (mContentHeight <= mHeadsUpChild.getHeight() || noExpandedChild) {
return VISIBLE_TYPE_HEADSUP;
@@ -443,19 +453,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 +485,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 +524,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/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 83dbde5eb742..89edae316ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -59,6 +59,7 @@ public class NotificationData {
public RemoteViews cachedBigContentView;
public RemoteViews cachedHeadsUpContentView;
public RemoteViews cachedPublicContentView;
+ public CharSequence remoteInputText;
public Entry(StatusBarNotification n, StatusBarIconView ic) {
this.key = n.getKey();
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/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index e1aec6f5d868..cddb1fc3a6ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -143,7 +143,7 @@ public class NavigationBarView extends LinearLayout {
private final OnClickListener mImeSwitcherClickListener = new OnClickListener() {
@Override
public void onClick(View view) {
- ((InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE))
+ mContext.getSystemService(InputMethodManager.class)
.showInputMethodPicker(true /* showAuxiliarySubtypes */);
}
};
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..73ee363a61cf 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;
@@ -924,15 +955,7 @@ public class NotificationPanelView extends PanelView implements
mQsTracking = false;
mTrackingPointer = -1;
trackMovement(event);
- float fraction = getQsExpansionFraction();
- if ((fraction != 0f || y >= mInitialTouchY)
- && (fraction != 1f || y <= mInitialTouchY)) {
- flingQsWithCurrentVelocity(y,
- event.getActionMasked() == MotionEvent.ACTION_CANCEL);
- } else {
- logQsSwipeDown(y);
- mScrollYOverride = -1;
- }
+ flingQsWithCurrentVelocity(y, event.getActionMasked() == MotionEvent.ACTION_CANCEL);
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
@@ -1663,7 +1686,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..685c4e5b06ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -25,7 +25,6 @@ import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
-import android.app.RemoteInput;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
@@ -110,8 +109,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 +170,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 +336,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private long mKeyguardFadingAwayDelay;
private long mKeyguardFadingAwayDuration;
- int mKeyguardMaxNotificationCount;
+ int mMaxAllowedKeyguardNotifications;
boolean mExpandedVisible;
@@ -428,6 +426,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 +3128,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 +3913,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 +3954,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
@@ -3987,6 +3998,13 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
+ @Override
+ public void onExpandClicked(View clickedView, boolean nowExpanded) {
+ if (mState == StatusBarState.KEYGUARD && nowExpanded) {
+ goToLockedShade(clickedView);
+ }
+ }
+
/**
* Goes back to the keyguard after hanging around in {@link StatusBarState#SHADE_LOCKED}.
*/
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/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index acfe54d29e82..22c0cb9f270b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -148,6 +148,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
public void onDefocus() {
mController.removeRemoteInput(mEntry);
+ mEntry.remoteInputText = mEditText.getText();
setVisibility(INVISIBLE);
}
@@ -171,6 +172,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mEditText.setInnerFocusable(true);
mController.addRemoteInput(mEntry);
mEditText.mShowImeOnInputConnection = true;
+ mEditText.setText(mEntry.remoteInputText);
+ mEditText.setSelection(mEditText.getText().length());
mEditText.requestFocus();
}
@@ -216,8 +219,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
defocusIfNeeded();
+ final InputMethodManager imm = InputMethodManager.getInstance();
+ imm.hideSoftInputFromWindow(getWindowToken(), 0);
+ return true;
}
return super.onKeyPreIme(keyCode, event);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index e00b8907a01a..b010761aecc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -342,7 +342,7 @@ public class UserSwitcherController {
private void stopUserId(int id) {
try {
- ActivityManagerNative.getDefault().stopUser(id, null);
+ ActivityManagerNative.getDefault().stopUser(id, /* force= */ false, null);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't stop user.", e);
}
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..ae6b72929d4b 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;
}
@@ -1860,7 +1867,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
mNeedsAnimation = true;
}
- if (isHeadsUp(child)) {
+ if (isHeadsUp(child) && !mChangePositionInProgress) {
mAddedHeadsUpChildren.add(child);
mChildrenToAddAnimated.remove(child);
}
@@ -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/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
index 76b234694790..2fe6648d82da 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
@@ -19,6 +19,7 @@ package com.android.vpndialogs;
import android.content.Context;
import android.content.DialogInterface;
import android.net.IConnectivityManager;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.ServiceManager;
@@ -50,8 +51,8 @@ public class ManageDialog extends AlertActivity implements
private Handler mHandler;
@Override
- protected void onResume() {
- super.onResume();
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
if (getCallingPackage() != null) {
Log.e(TAG, getCallingPackage() + " cannot start this activity");
@@ -108,11 +109,11 @@ public class ManageDialog extends AlertActivity implements
}
@Override
- protected void onPause() {
- super.onPause();
+ protected void onDestroy() {
if (!isFinishing()) {
finish();
}
+ super.onDestroy();
}
@Override
diff --git a/preloaded-classes b/preloaded-classes
index 79c0957e48ae..2301c4119d38 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
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/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
index fc3a322670ee..fcd56eb70080 100644
--- a/services/core/java/com/android/server/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -446,8 +446,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
String candidateLocale = null;
if (hashCode == 0) {
// Spell checker language settings == "auto"
- final InputMethodManager imm =
- (InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+ final InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
if (imm != null) {
final InputMethodSubtype currentInputMethodSubtype =
imm.getCurrentInputMethodSubtype();
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 9eb66dd281e1..2924cef09d17 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -223,7 +223,7 @@ public class VibratorService extends IVibratorService.Stub
}
public void systemReady() {
- mIm = (InputManager)mContext.getSystemService(Context.INPUT_SERVICE);
+ mIm = mContext.getSystemService(InputManager.class);
mSettingObserver = new SettingsObserver(mH);
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 17b3d2a8290e..2742c657d853 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -30,10 +30,14 @@ import java.util.Set;
import android.app.ActivityThread;
import android.app.AppOpsManager;
+import android.content.IIntentSender;
+import android.content.IntentSender;
import android.os.Build;
+import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.Looper;
+import android.os.RemoteCallback;
import android.os.SystemProperties;
import android.os.TransactionTooLargeException;
import android.util.ArrayMap;
@@ -300,7 +304,7 @@ public final class ActiveServices {
}
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
- int callingPid, int callingUid, String callingPackage, int userId)
+ int callingPid, int callingUid, String callingPackage, final int userId)
throws TransactionTooLargeException {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
+ " type=" + resolvedType + " args=" + service.getExtras());
@@ -340,6 +344,18 @@ public final class ActiveServices {
NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked(
callingUid, r.packageName, service, service.getFlags(), null, r.userId);
+
+ // If permissions need a review before any of the app components can run,
+ // we do not start the service and launch a review activity if the calling app
+ // is in the foreground passing it a pending intent to start the service when
+ // review is completed.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+ if (!requestStartTargetPermissionsReviewIfNeededLocked(r, callingPackage,
+ callingUid, service, callerFg, userId)) {
+ return null;
+ }
+ }
+
if (unscheduleServiceRestartLocked(r, callingUid, false)) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "START SERVICE WHILE RESTART PENDING: " + r);
}
@@ -417,6 +433,50 @@ public final class ActiveServices {
return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
}
+ private boolean requestStartTargetPermissionsReviewIfNeededLocked(ServiceRecord r,
+ String callingPackage, int callingUid, Intent service, boolean callerFg,
+ final int userId) {
+ if (mAm.getPackageManagerInternalLocked().isPermissionsReviewRequired(
+ r.packageName, r.userId)) {
+
+ // Show a permission review UI only for starting from a foreground app
+ if (!callerFg) {
+ Slog.w(TAG, "u" + r.userId + " Starting a service in package"
+ + r.packageName + " requires a permissions review");
+ return false;
+ }
+
+ IIntentSender target = mAm.getIntentSenderLocked(
+ ActivityManager.INTENT_SENDER_SERVICE, callingPackage,
+ callingUid, userId, null, null, 0, new Intent[]{service},
+ new String[]{service.resolveType(mAm.mContext.getContentResolver())},
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_IMMUTABLE, null);
+
+ final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, r.packageName);
+ intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
+
+ if (DEBUG_PERMISSIONS_REVIEW) {
+ Slog.i(TAG, "u" + r.userId + " Launching permission review for package "
+ + r.packageName);
+ }
+
+ mAm.mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mAm.mContext.startActivityAsUser(intent, new UserHandle(userId));
+ }
+ });
+
+ return false;
+ }
+
+ return true;
+ }
+
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
ProcessStats.ServiceState stracker = r.getTracker();
@@ -427,7 +487,7 @@ public final class ActiveServices {
synchronized (r.stats.getBatteryStats()) {
r.stats.startRunningLocked();
}
- String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false);
+ String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false);
if (error != null) {
return new ComponentName("!!", error);
}
@@ -721,8 +781,8 @@ public final class ActiveServices {
}
int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
- String resolvedType, IServiceConnection connection, int flags,
- String callingPackage, int userId) throws TransactionTooLargeException {
+ String resolvedType, final IServiceConnection connection, int flags,
+ String callingPackage, final int userId) throws TransactionTooLargeException {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
+ " type=" + resolvedType + " conn=" + connection.asBinder()
+ " flags=0x" + Integer.toHexString(flags));
@@ -783,6 +843,83 @@ public final class ActiveServices {
}
ServiceRecord s = res.record;
+ boolean permissionsReviewRequired = false;
+
+ // If permissions need a review before any of the app components can run,
+ // we schedule binding to the service but do not start its process, then
+ // we launch a review activity to which is passed a callback to invoke
+ // when done to start the bound service's process to completing the binding.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+ if (mAm.getPackageManagerInternalLocked().isPermissionsReviewRequired(
+ s.packageName, s.userId)) {
+
+ permissionsReviewRequired = true;
+
+ // Show a permission review UI only for binding from a foreground app
+ if (!callerFg) {
+ Slog.w(TAG, "u" + s.userId + " Binding to a service in package"
+ + s.packageName + " requires a permissions review");
+ return 0;
+ }
+
+ final ServiceRecord serviceRecord = s;
+ final Intent serviceIntent = service;
+
+ RemoteCallback callback = new RemoteCallback(
+ new RemoteCallback.OnResultListener() {
+ @Override
+ public void onResult(Bundle result) {
+ synchronized(mAm) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (!mPendingServices.contains(serviceRecord)) {
+ return;
+ }
+ // If there is still a pending record, then the service
+ // binding request is still valid, so hook them up. We
+ // proceed only if the caller cleared the review requirement
+ // otherwise we unbind because the user didn't approve.
+ if (!mAm.getPackageManagerInternalLocked()
+ .isPermissionsReviewRequired(
+ serviceRecord.packageName,
+ serviceRecord.userId)) {
+ try {
+ bringUpServiceLocked(serviceRecord,
+ serviceIntent.getFlags(),
+ callerFg, false, false);
+ } catch (RemoteException e) {
+ /* ignore - local call */
+ }
+ } else {
+ unbindServiceLocked(connection);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+ });
+
+ final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, s.packageName);
+ intent.putExtra(Intent.EXTRA_REMOTE_CALLBACK, callback);
+
+ if (DEBUG_PERMISSIONS_REVIEW) {
+ Slog.i(TAG, "u" + s.userId + " Launching permission review for package "
+ + s.packageName);
+ }
+
+ mAm.mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mAm.mContext.startActivityAsUser(intent, new UserHandle(userId));
+ }
+ });
+ }
+ }
+
final long origId = Binder.clearCallingIdentity();
try {
@@ -840,7 +977,8 @@ public final class ActiveServices {
if ((flags&Context.BIND_AUTO_CREATE) != 0) {
s.lastActivity = SystemClock.uptimeMillis();
- if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) {
+ if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
+ permissionsReviewRequired) != null) {
return 0;
}
}
@@ -890,6 +1028,10 @@ public final class ActiveServices {
return 1;
}
+ private void foo() {
+
+ }
+
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
final long origId = Binder.clearCallingIdentity();
try {
@@ -1366,7 +1508,7 @@ public final class ActiveServices {
return;
}
try {
- bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true);
+ bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true, false);
} catch (TransactionTooLargeException e) {
// Ignore, it's been logged and nothing upstack cares.
}
@@ -1410,8 +1552,9 @@ public final class ActiveServices {
}
}
- private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
- boolean whileRestarting) throws TransactionTooLargeException {
+ private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
+ boolean whileRestarting, boolean permissionsReviewRequired)
+ throws TransactionTooLargeException {
//Slog.i(TAG, "Bring up service:");
//r.dump(" ");
@@ -1497,7 +1640,7 @@ public final class ActiveServices {
// Not running -- get it started, and enqueue this service record
// to be executed when the app comes up.
- if (app == null) {
+ if (app == null && !permissionsReviewRequired) {
if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
"service", r.name, false, isolated, false)) == null) {
String msg = "Unable to launch app "
@@ -1920,6 +2063,9 @@ public final class ActiveServices {
}
}
+ // If unbound while waiting to start, remove the pending service
+ mPendingServices.remove(s);
+
if ((c.flags&Context.BIND_AUTO_CREATE) != 0) {
boolean hasAutoCreate = s.hasAutoCreateConnections();
if (!hasAutoCreate) {
@@ -2962,5 +3108,4 @@ public final class ActiveServices {
}
}
}
-
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 16c959fabddf..4f0d4d951e1c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -88,6 +88,7 @@ class ActivityManagerDebugConfig {
static final boolean DEBUG_VISIBILITY = DEBUG_ALL || false;
static final boolean DEBUG_VISIBLE_BEHIND = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_USAGE_STATS = DEBUG_ALL || false;
+ static final boolean DEBUG_PERMISSIONS_REVIEW = DEBUG_ALL || false;
static final String POSTFIX_ADD_REMOVE = (APPEND_CATEGORY_NAME) ? "_AddRemove" : "";
static final String POSTFIX_APP = (APPEND_CATEGORY_NAME) ? "_App" : "";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 557b3869a886..ef623ebfb57d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16,9 +16,65 @@
package com.android.server.am;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
+import static com.android.server.am.ActivityManagerDebugConfig.*;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.app.ApplicationThreadNative;
+import android.app.BroadcastOptions;
+import android.app.IActivityContainer;
+import android.app.IActivityContainerCallback;
+import android.app.IAppTask;
+import android.app.ITaskStackListener;
+import android.app.ProfilerInfo;
+import android.app.assist.AssistContent;
+import android.app.assist.AssistStructure;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal;
+import android.appwidget.AppWidgetManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.BatteryStats;
+import android.os.PersistableBundle;
+import android.os.PowerManager;
+import android.os.Trace;
+import android.os.TransactionTooLargeException;
+import android.os.WorkSource;
+import android.os.storage.IMountService;
+import android.os.storage.MountServiceInternal;
+import android.os.storage.StorageManager;
+import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.VoiceInteractionSession;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.DebugUtils;
+import android.util.SparseIntArray;
+import android.view.Display;
+
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.AssistUtils;
@@ -1457,6 +1513,8 @@ public final class ActivityManagerService extends ActivityManagerNative
final MainHandler mHandler;
final UiHandler mUiHandler;
+ PackageManagerInternal mPackageManagerInt;
+
final class UiHandler extends Handler {
public UiHandler() {
super(com.android.server.UiThread.get().getLooper(), null, true);
@@ -2713,57 +2771,60 @@ public final class ActivityManagerService extends ActivityManagerNative
}
final void setFocusedActivityLocked(ActivityRecord r, String reason) {
- if (r != null && mFocusedActivity != r) {
- if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedActivityLocked: r=" + r);
- ActivityRecord last = mFocusedActivity;
- mFocusedActivity = r;
- if (r.task.taskType != ActivityRecord.HOME_ACTIVITY_TYPE
- && r.task.taskType != ActivityRecord.RECENTS_ACTIVITY_TYPE) {
- if (mCurAppTimeTracker != r.appTimeTracker) {
- // We are switching app tracking. Complete the current one.
- if (mCurAppTimeTracker != null) {
- mCurAppTimeTracker.stop();
- mHandler.obtainMessage(REPORT_TIME_TRACKER_MSG,
- mCurAppTimeTracker).sendToTarget();
- mStackSupervisor.clearOtherAppTimeTrackers(r.appTimeTracker);
- mCurAppTimeTracker = null;
- }
- if (r.appTimeTracker != null) {
- mCurAppTimeTracker = r.appTimeTracker;
- startTimeTrackingFocusedActivityLocked();
- }
- } else {
+ if (r == null || mFocusedActivity == r) {
+ return;
+ }
+
+ if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedActivityLocked: r=" + r);
+ final ActivityRecord last = mFocusedActivity;
+ mFocusedActivity = r;
+ if (r.task.isApplicationTask()) {
+ if (mCurAppTimeTracker != r.appTimeTracker) {
+ // We are switching app tracking. Complete the current one.
+ if (mCurAppTimeTracker != null) {
+ mCurAppTimeTracker.stop();
+ mHandler.obtainMessage(
+ REPORT_TIME_TRACKER_MSG, mCurAppTimeTracker).sendToTarget();
+ mStackSupervisor.clearOtherAppTimeTrackers(r.appTimeTracker);
+ mCurAppTimeTracker = null;
+ }
+ if (r.appTimeTracker != null) {
+ mCurAppTimeTracker = r.appTimeTracker;
startTimeTrackingFocusedActivityLocked();
}
} else {
- r.appTimeTracker = null;
- }
- if (r.task != null && r.task.voiceInteractor != null) {
- startRunningVoiceLocked(r.task.voiceSession, r.info.applicationInfo.uid);
- } else {
- finishRunningVoiceLocked();
- if (last != null && last.task.voiceSession != null) {
- // We had been in a voice interaction session, but now focused has
- // move to something different. Just finish the session, we can't
- // return to it and retain the proper state and synchronization with
- // the voice interaction service.
- finishVoiceTask(last.task.voiceSession);
- }
- }
- if (mStackSupervisor.setFocusedStack(r, reason + " setFocusedActivity")) {
- mWindowManager.setFocusedApp(r.appToken, true);
+ startTimeTrackingFocusedActivityLocked();
}
- applyUpdateLockStateLocked(r);
- if (mFocusedActivity.userId != mLastFocusedUserId) {
- mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(FOREGROUND_PROFILE_CHANGED_MSG,
- mFocusedActivity.userId, 0));
- mLastFocusedUserId = mFocusedActivity.userId;
+ } else {
+ r.appTimeTracker = null;
+ }
+ if (r.task.voiceInteractor != null) {
+ startRunningVoiceLocked(r.task.voiceSession, r.info.applicationInfo.uid);
+ } else {
+ finishRunningVoiceLocked();
+ if (last != null && last.task.voiceSession != null) {
+ // We had been in a voice interaction session, but now focused has
+ // move to something different. Just finish the session, we can't
+ // return to it and retain the proper state and synchronization with
+ // the voice interaction service.
+ finishVoiceTask(last.task.voiceSession);
}
}
- EventLog.writeEvent(EventLogTags.AM_FOCUSED_ACTIVITY,
+ if (mStackSupervisor.setFocusedStack(r, reason + " setFocusedActivity")) {
+ mWindowManager.setFocusedApp(r.appToken, true);
+ }
+ applyUpdateLockStateLocked(r);
+ if (mFocusedActivity.userId != mLastFocusedUserId) {
+ mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
+ mHandler.obtainMessage(
+ FOREGROUND_PROFILE_CHANGED_MSG, mFocusedActivity.userId, 0).sendToTarget();
+ mLastFocusedUserId = mFocusedActivity.userId;
+ }
+
+ EventLogTags.writeAmFocusedActivity(
mFocusedActivity == null ? -1 : mFocusedActivity.userId,
- mFocusedActivity == null ? "NULL" : mFocusedActivity.shortComponentName);
+ mFocusedActivity == null ? "NULL" : mFocusedActivity.shortComponentName,
+ reason);
}
final void clearFocusedActivity(ActivityRecord r) {
@@ -2779,7 +2840,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
mFocusedActivity = null;
- EventLog.writeEvent(EventLogTags.AM_FOCUSED_ACTIVITY, -1, "NULL");
+ EventLogTags.writeAmFocusedActivity(-1, "NULL", "clearFocusedActivity");
}
}
@@ -3356,10 +3417,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();
}
@@ -4399,8 +4458,10 @@ public final class ActivityManagerService extends ActivityManagerNative
if (r == null) {
return;
}
- if (r.task != null && r.task.mResizeable) {
- // Fixed screen orientation isn't supported with resizeable activities.
+ TaskRecord task = r.task;
+ if (task != null && (!task.mFullscreen || !task.stack.mFullscreen)) {
+ // Fixed screen orientation isn't supported when activities aren't in full screen
+ // mode.
return;
}
final long origId = Binder.clearCallingIdentity();
@@ -5328,6 +5389,11 @@ public final class ActivityManagerService extends ActivityManagerNative
removeUriPermissionsForPackageLocked(packageName, userId, true);
}
+ // Remove all zen rules created by this package; revoke it's zen access.
+ INotificationManager inm = NotificationManager.getService();
+ inm.removeAutomaticZenRules(packageName);
+ inm.setNotificationPolicyAccessGranted(packageName, false);
+
Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED,
Uri.fromParts("package", packageName, null));
intent.putExtra(Intent.EXTRA_UID, pkgUid);
@@ -6011,6 +6077,8 @@ public final class ActivityManagerService extends ActivityManagerNative
"No more processes in " + old.uidRecord);
enqueueUidChangeLocked(old.uidRecord, -1, UidRecord.CHANGE_GONE);
mActiveUids.remove(uid);
+ mBatteryStatsService.noteUidProcessState(uid,
+ ActivityManager.PROCESS_STATE_NONEXISTENT);
}
old.uidRecord = null;
}
@@ -6035,6 +6103,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
"Creating new process uid: " + uidRec);
mActiveUids.put(proc.uid, uidRec);
+ mBatteryStatsService.noteUidProcessState(uidRec.uid, uidRec.curProcState);
enqueueUidChangeLocked(uidRec, -1, UidRecord.CHANGE_ACTIVE);
}
proc.uidRecord = uidRec;
@@ -7148,22 +7217,40 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public void getProcessStatesFromPids(/*in*/ int[] pids, /*out*/ int[] states) {
- mActivityManagerService.getProcessStatesForPIDs(/*in*/ pids, /*out*/ states);
+ mActivityManagerService.getProcessStatesAndOomScoresForPIDs(
+ /*in*/ pids, /*out*/ states, null);
+ }
+
+ @Override
+ public void getProcessStatesAndOomScoresFromPids(
+ /*in*/ int[] pids, /*out*/ int[] states, /*out*/ int[] scores) {
+ mActivityManagerService.getProcessStatesAndOomScoresForPIDs(
+ /*in*/ pids, /*out*/ states, /*out*/ scores);
}
}
/**
* For each PID in the given input array, write the current process state
- * for that process into the output array, or -1 to indicate that no
- * process with the given PID exists.
+ * for that process into the states array, or -1 to indicate that no
+ * process with the given PID exists. If scores array is provided, write
+ * the oom score for the process into the scores array, with INVALID_ADJ
+ * indicating the PID doesn't exist.
*/
- public void getProcessStatesForPIDs(/*in*/ int[] pids, /*out*/ int[] states) {
+ public void getProcessStatesAndOomScoresForPIDs(
+ /*in*/ int[] pids, /*out*/ int[] states, /*out*/ int[] scores) {
+ if (scores != null) {
+ enforceCallingPermission(android.Manifest.permission.GET_PROCESS_STATE_AND_OOM_SCORE,
+ "getProcessStatesAndOomScoresForPIDs()");
+ }
+
if (pids == null) {
throw new NullPointerException("pids");
} else if (states == null) {
throw new NullPointerException("states");
} else if (pids.length != states.length) {
- throw new IllegalArgumentException("input and output arrays have different lengths!");
+ throw new IllegalArgumentException("pids and states arrays have different lengths!");
+ } else if (scores != null && pids.length != scores.length) {
+ throw new IllegalArgumentException("pids and scores arrays have different lengths!");
}
synchronized (mPidsSelfLocked) {
@@ -7171,6 +7258,9 @@ public final class ActivityManagerService extends ActivityManagerNative
ProcessRecord pr = mPidsSelfLocked.get(pids[i]);
states[i] = (pr == null) ? ActivityManager.PROCESS_STATE_NONEXISTENT :
pr.curProcState;
+ if (scores != null) {
+ scores[i] = (pr == null) ? ProcessList.INVALID_ADJ : pr.curAdj;
+ }
}
}
}
@@ -9944,14 +10034,6 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (cpr.proc != null) {
- if (false) {
- if (cpr.name.flattenToShortString().equals(
- "com.android.providers.calendar/.CalendarProvider2")) {
- Slog.v(TAG, "****************** KILLING "
- + cpr.name.flattenToShortString());
- Process.killProcess(cpr.proc.pid);
- }
- }
checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
boolean success = updateOomAdjLocked(cpr.proc);
maybeUpdateProviderUsageStatsLocked(r, cpr.info.packageName, name);
@@ -10044,6 +10126,16 @@ public final class ActivityManagerService extends ActivityManagerNative
final boolean firstClass = cpr == null;
if (firstClass) {
final long ident = Binder.clearCallingIdentity();
+
+ // If permissions need a review before any of the app components can run,
+ // we return no provider and launch a review activity if the calling app
+ // is in the foreground.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+ if (!requestTargetProviderPermissionsReviewIfNeededLocked(cpi, r, userId)) {
+ return null;
+ }
+ }
+
try {
checkTime(startTime, "getContentProviderImpl: before getApplicationInfo");
ApplicationInfo ai =
@@ -10196,6 +10288,52 @@ public final class ActivityManagerService extends ActivityManagerNative
return cpr != null ? cpr.newHolder(conn) : null;
}
+ private boolean requestTargetProviderPermissionsReviewIfNeededLocked(ProviderInfo cpi,
+ ProcessRecord r, final int userId) {
+ if (getPackageManagerInternalLocked().isPermissionsReviewRequired(
+ cpi.packageName, r.userId)) {
+
+ final boolean callerForeground = r != null ? r.setSchedGroup
+ != Process.THREAD_GROUP_BG_NONINTERACTIVE : true;
+
+ // Show a permission review UI only for starting from a foreground app
+ if (!callerForeground) {
+ Slog.w(TAG, "u" + r.userId + " Instantiating a provider in package"
+ + cpi.packageName + " requires a permissions review");
+ return false;
+ }
+
+ final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, cpi.packageName);
+
+ if (DEBUG_PERMISSIONS_REVIEW) {
+ Slog.i(TAG, "u" + r.userId + " Launching permission review "
+ + "for package " + cpi.packageName);
+ }
+
+ final UserHandle userHandle = new UserHandle(userId);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mContext.startActivityAsUser(intent, userHandle);
+ }
+ });
+
+ return false;
+ }
+
+ return true;
+ }
+
+ PackageManagerInternal getPackageManagerInternalLocked() {
+ if (mPackageManagerInt == null) {
+ mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
+ }
+ return mPackageManagerInt;
+ }
+
@Override
public final ContentProviderHolder getContentProvider(
IApplicationThread caller, String name, int userId, boolean stable) {
@@ -10899,7 +11037,7 @@ public final class ActivityManagerService extends ActivityManagerNative
public void stopAppSwitches() {
if (checkCallingPermission(android.Manifest.permission.STOP_APP_SWITCHES)
!= PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires permission "
+ throw new SecurityException("viewquires permission "
+ android.Manifest.permission.STOP_APP_SWITCHES);
}
@@ -17761,7 +17899,7 @@ public final class ActivityManagerService extends ActivityManagerNative
* @param userId is only used when persistent parameter is set to true to persist configuration
* for that particular user
*/
- boolean updateConfigurationLocked(Configuration values,
+ private boolean updateConfigurationLocked(Configuration values,
ActivityRecord starting, boolean initLocale, boolean persistent, int userId) {
int changes = 0;
@@ -19372,10 +19510,6 @@ public final class ActivityManagerService extends ActivityManagerNative
if (proc.baseProcessTracker != null) {
proc.baseProcessTracker.setState(proc.repProcState, memFactor, now, proc.pkgList);
}
- if (proc.repProcState >= 0) {
- mBatteryStatsService.noteProcessState(proc.processName, proc.info.uid,
- proc.repProcState);
- }
}
}
@@ -19875,6 +20009,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
uidRec.setProcState = uidRec.curProcState;
enqueueUidChangeLocked(uidRec, -1, uidChange);
+ mBatteryStatsService.noteUidProcessState(uidRec.uid, uidRec.curProcState);
}
}
@@ -20302,8 +20437,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
- public int stopUser(final int userId, final IStopUserCallback callback) {
- return mUserController.stopUser(userId, callback);
+ public int stopUser(final int userId, boolean force, final IStopUserCallback callback) {
+ return mUserController.stopUser(userId, force, callback);
}
void onUserRemovedLocked(int userId) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index da2c8c5cda6e..02a372a6a08a 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -54,7 +54,6 @@ import android.app.IActivityController;
import android.app.ResultInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
@@ -73,7 +72,6 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
-import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
import android.util.EventLog;
import android.util.Slog;
@@ -2827,41 +2825,43 @@ final class ActivityStack {
}
private void adjustFocusedActivityLocked(ActivityRecord r, String reason) {
- if (mStackSupervisor.isFocusedStack(this) && mService.mFocusedActivity == r) {
- ActivityRecord next = topRunningActivityLocked();
- final String myReason = reason + " adjustFocus";
- if (next != r) {
- if (next != null && StackId.keepFocusInStackIfPossible(mStackId)) {
- // For freeform, docked, and pinned stacks we always keep the focus within the
- // stack as long as there is a running activity in the stack that we can adjust
- // focus to.
- mService.setFocusedActivityLocked(next, myReason);
- return;
- } else {
- final TaskRecord task = r.task;
- if (r.frontOfTask && task == topTask() && task.isOverHomeStack()) {
- // For non-fullscreen stack, we want to move the focus to the next visible
- // stack to prevent the home screen from moving to the top and obscuring
- // other visible stacks.
- if (!mFullscreen
- && adjustFocusToNextVisibleStackLocked(null, myReason)) {
- return;
- }
- // Move the home stack to the top if this stack is fullscreen or there is no
- // other visible stack.
- if (mStackSupervisor.moveHomeStackTaskToTop(
- task.getTaskToReturnTo(), myReason)) {
- // Activity focus was already adjusted. Nothing else to do...
- return;
- }
+ if (!mStackSupervisor.isFocusedStack(this) || mService.mFocusedActivity != r) {
+ return;
+ }
+
+ final ActivityRecord next = topRunningActivityLocked();
+ final String myReason = reason + " adjustFocus";
+ if (next != r) {
+ if (next != null && StackId.keepFocusInStackIfPossible(mStackId)) {
+ // For freeform, docked, and pinned stacks we always keep the focus within the
+ // stack as long as there is a running activity in the stack that we can adjust
+ // focus to.
+ mService.setFocusedActivityLocked(next, myReason);
+ return;
+ } else {
+ final TaskRecord task = r.task;
+ if (r.frontOfTask && task == topTask() && task.isOverHomeStack()) {
+ // For non-fullscreen stack, we want to move the focus to the next visible
+ // stack to prevent the home screen from moving to the top and obscuring
+ // other visible stacks.
+ if (!mFullscreen
+ && adjustFocusToNextVisibleStackLocked(null, myReason)) {
+ return;
+ }
+ // Move the home stack to the top if this stack is fullscreen or there is no
+ // other visible stack.
+ if (mStackSupervisor.moveHomeStackTaskToTop(
+ task.getTaskToReturnTo(), myReason)) {
+ // Activity focus was already adjusted. Nothing else to do...
+ return;
}
}
}
+ }
- final ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
- if (top != null) {
- mService.setFocusedActivityLocked(top, myReason);
- }
+ final ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
+ if (top != null) {
+ mService.setFocusedActivityLocked(top, myReason);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index c3bd982ec8a2..e9e02c1af6f7 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -76,6 +76,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
@@ -88,6 +89,7 @@ import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
@@ -184,7 +186,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";
@@ -1708,6 +1709,46 @@ public final class ActivityStackSupervisor implements DisplayListener {
return ActivityManager.START_SUCCESS;
}
+ // If permissions need a review before any of the app components can run, we
+ // launch the review activity and pass a pending intent to start the activity
+ // we are to launching now after the review is completed.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED && aInfo != null) {
+ if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
+ aInfo.packageName, userId)) {
+ IIntentSender target = mService.getIntentSenderLocked(
+ ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
+ callingUid, userId, null, null, 0, new Intent[]{intent},
+ new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT
+ | PendingIntent.FLAG_ONE_SHOT, null);
+
+ Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
+ newIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName);
+ newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
+ if (resultRecord != null) {
+ newIntent.putExtra(Intent.EXTRA_RESULT_NEEDED, true);
+ }
+ newIntent.setFlags(intent.getFlags());
+ intent = newIntent;
+
+ resolvedType = null;
+ callingUid = realCallingUid;
+ callingPid = realCallingPid;
+
+ aInfo = resolveActivity(intent, null, PackageManager.MATCH_DEFAULT_ONLY
+ | ActivityManagerService.STOCK_PM_FLAGS, null, userId);
+
+ if (DEBUG_PERMISSIONS_REVIEW) {
+ Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true,
+ true, false) + "} from uid " + callingUid + " on display "
+ + (container == null ? (mFocusedStack == null ?
+ Display.DEFAULT_DISPLAY : mFocusedStack.mDisplayId) :
+ (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY :
+ container.mActivityDisplay.mDisplayId)));
+ }
+ }
+ }
+
ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
requestCode, componentSpecified, voiceSession != null, this, container, options);
@@ -2592,6 +2633,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 +3160,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 +3422,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 +3443,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
resumeTopActivitiesLocked();
if (!task.mResizeable && isStackDockedInEffect(stackId)) {
- showNonResizeableDockToast();
+ showNonResizeableDockToast(taskId);
}
}
@@ -4327,8 +4377,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 +4693,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/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c7228ce47683..f64b80323b91 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -272,15 +272,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
- void noteProcessState(String name, int uid, int state) {
+ void noteProcessFinish(String name, int uid) {
synchronized (mStats) {
- mStats.noteProcessStateLocked(name, uid, state);
+ mStats.noteProcessFinishLocked(name, uid);
}
}
- void noteProcessFinish(String name, int uid) {
+ void noteUidProcessState(int uid, int state) {
synchronized (mStats) {
- mStats.noteProcessFinishLocked(name, uid);
+ mStats.noteUidProcessStateLocked(uid, state);
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index fb37edafe44c..b1609819403a 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -27,12 +27,16 @@ import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.IIntentReceiver;
+import android.content.IIntentSender;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -46,6 +50,7 @@ import android.util.EventLog;
import android.util.Slog;
import android.util.TimeUtils;
import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
import static com.android.server.am.ActivityManagerDebugConfig.*;
@@ -181,7 +186,7 @@ public final class BroadcastQueue {
} break;
}
}
- };
+ }
private final class AppNotResponding implements Runnable {
private final ProcessRecord mApp;
@@ -580,6 +585,17 @@ public final class BroadcastQueue {
}
if (!skip) {
+ // If permissions need a review before any of the app components can run, we drop
+ // the broadcast and if the calling app is in the foreground and the broadcast is
+ // explicit we launch the review UI passing it a pending intent to send the skipped
+ // broadcast.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+ if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName,
+ filter.owningUserId)) {
+ return;
+ }
+ }
+
// If this is not being sent as an ordered broadcast, then we
// don't want to touch the fields that keep track of the current
// state of ordered broadcasts.
@@ -622,6 +638,54 @@ public final class BroadcastQueue {
}
}
+ private boolean requestStartTargetPermissionsReviewIfNeededLocked(
+ BroadcastRecord receiverRecord, String receivingPackageName,
+ final int receivingUserId) {
+ if (!mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
+ receivingPackageName, receivingUserId)) {
+ return true;
+ }
+
+ final boolean callerForeground = receiverRecord.callerApp != null
+ ? receiverRecord.callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE
+ : true;
+
+ // Show a permission review UI only for explicit broadcast from a foreground app
+ if (callerForeground && receiverRecord.intent.getComponent() != null) {
+ IIntentSender target = mService.getIntentSenderLocked(
+ ActivityManager.INTENT_SENDER_BROADCAST, receiverRecord.callerPackage,
+ receiverRecord.callingUid, receiverRecord.userId, null, null, 0,
+ new Intent[]{receiverRecord.intent},
+ new String[]{receiverRecord.intent.resolveType(mService.mContext
+ .getContentResolver())},
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_IMMUTABLE, null);
+
+ final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, receivingPackageName);
+ intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
+
+ if (DEBUG_PERMISSIONS_REVIEW) {
+ Slog.i(TAG, "u" + receivingUserId + " Launching permission review for package "
+ + receivingPackageName);
+ }
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mService.mContext.startActivityAsUser(intent, new UserHandle(receivingUserId));
+ }
+ });
+ } else {
+ Slog.w(TAG, "u" + receivingUserId + " Receiving a broadcast in package"
+ + receivingPackageName + " requires a permissions review");
+ }
+
+ return false;
+ }
+
final void scheduleTempWhitelistLocked(int uid, long duration, BroadcastRecord r) {
if (duration > Integer.MAX_VALUE) {
duration = Integer.MAX_VALUE;
@@ -1007,6 +1071,18 @@ public final class BroadcastQueue {
}
}
+ // If permissions need a review before any of the app components can run, we drop
+ // the broadcast and if the calling app is in the foreground and the broadcast is
+ // explicit we launch the review UI passing it a pending intent to send the skipped
+ // broadcast.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED && !skip) {
+ if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
+ info.activityInfo.packageName, UserHandle.getUserId(
+ info.activityInfo.applicationInfo.uid))) {
+ skip = true;
+ }
+ }
+
if (skip) {
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Skipping delivery of ordered [" + mQueueName + "] "
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 78b5f3333b2c..03975536816d 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -91,7 +91,7 @@ option java_package com.android.server.am
30042 am_activity_fully_drawn_time (User|1|5),(Token|1|5),(Component Name|3),(time|2|3)
# Activity focused
-30043 am_focused_activity (User|1|5),(Component Name|3)
+30043 am_focused_activity (User|1|5),(Component Name|3),(Reason|3)
# Stack focus
30044 am_focused_stack (User|1|5),(Focused Stack Id|1|5),(Last Focused Stack Id|1|5),(Reason|3)
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b30905e4d590..3e0ae1785faa 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -18,6 +18,8 @@ package com.android.server.am;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM;
+import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
import static android.app.ActivityManager.USER_OP_IS_CURRENT;
import static android.app.ActivityManager.USER_OP_SUCCESS;
import static android.os.Process.SYSTEM_UID;
@@ -34,6 +36,7 @@ import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_M
import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG;
import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.Dialog;
@@ -56,11 +59,11 @@ import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
+import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -78,6 +81,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import libcore.util.EmptyArray;
+
/**
* Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
*/
@@ -152,39 +157,44 @@ final class UserController {
finishUserBoot(uss);
startProfilesLocked();
+ stopRunningUsersLocked(MAX_RUNNING_USERS);
+ }
+ }
- int num = mUserLru.size();
- int i = 0;
- while (num > MAX_RUNNING_USERS && i < mUserLru.size()) {
- Integer oldUserId = mUserLru.get(i);
- UserState oldUss = mStartedUsers.get(oldUserId);
- if (oldUss == null) {
- // Shouldn't happen, but be sane if it does.
- mUserLru.remove(i);
- num--;
- continue;
- }
- if (oldUss.mState == UserState.STATE_STOPPING
- || oldUss.mState == UserState.STATE_SHUTDOWN) {
- // This user is already stopping, doesn't count.
+ void stopRunningUsersLocked(int maxRunningUsers) {
+ int num = mUserLru.size();
+ int i = 0;
+ while (num > maxRunningUsers && i < mUserLru.size()) {
+ Integer oldUserId = mUserLru.get(i);
+ UserState oldUss = mStartedUsers.get(oldUserId);
+ if (oldUss == null) {
+ // Shouldn't happen, but be sane if it does.
+ mUserLru.remove(i);
+ num--;
+ continue;
+ }
+ if (oldUss.mState == UserState.STATE_STOPPING
+ || oldUss.mState == UserState.STATE_SHUTDOWN) {
+ // This user is already stopping, doesn't count.
+ num--;
+ i++;
+ continue;
+ }
+ if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId) {
+ // Owner/System user and current user can't be stopped. We count it as running
+ // when it is not a pure system user.
+ if (UserInfo.isSystemOnly(oldUserId)) {
num--;
- i++;
- continue;
- }
- if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId) {
- // Owner/System user and current user can't be stopped. We count it as running
- // when it is not a pure system user.
- if (UserInfo.isSystemOnly(oldUserId)) {
- num--;
- }
- i++;
- continue;
}
- // This is a user to be stopped.
- stopUserLocked(oldUserId, null);
- num--;
i++;
+ continue;
+ }
+ // This is a user to be stopped.
+ if (stopUsersLocked(oldUserId, false, null) != USER_OP_SUCCESS) {
+ num--;
}
+ num--;
+ i++;
}
}
@@ -205,7 +215,7 @@ final class UserController {
}
}
- int stopUser(final int userId, final IStopUserCallback callback) {
+ int stopUser(final int userId, final boolean force, final IStopUserCallback callback) {
if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED) {
String msg = "Permission Denial: switchUser() from pid="
@@ -221,16 +231,44 @@ final class UserController {
mService.enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES,
userId);
synchronized (mService) {
- return stopUserLocked(userId, callback);
+ return stopUsersLocked(userId, force, callback);
}
}
- private int stopUserLocked(final int userId, final IStopUserCallback callback) {
- if (DEBUG_MU) Slog.i(TAG, "stopUserLocked userId=" + userId);
- if (mCurrentUserId == userId && mTargetUserId == UserHandle.USER_NULL) {
+ /**
+ * Stops the user along with its related users. The method calls
+ * {@link #getUsersToStopLocked(int)} to determine the list of users that should be stopped.
+ */
+ private int stopUsersLocked(final int userId, boolean force, final IStopUserCallback callback) {
+ if (userId == UserHandle.USER_SYSTEM) {
+ return USER_OP_ERROR_IS_SYSTEM;
+ }
+ if (isCurrentUserLocked(userId)) {
return USER_OP_IS_CURRENT;
}
+ int[] usersToStop = getUsersToStopLocked(userId);
+ // If one of related users is system or current, no related users should be stopped
+ for (int i = 0; i < usersToStop.length; i++) {
+ int relatedUserId = usersToStop[i];
+ if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLocked(relatedUserId)) {
+ if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked cannot stop related user "
+ + relatedUserId);
+ // We still need to stop the requested user if it's a force stop.
+ if (force) {
+ stopSingleUserLocked(userId, callback);
+ }
+ return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
+ }
+ }
+ if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
+ for (int userIdToStop : usersToStop) {
+ stopSingleUserLocked(userIdToStop, userIdToStop == userId ? callback : null);
+ }
+ return USER_OP_SUCCESS;
+ }
+ private void stopSingleUserLocked(final int userId, final IStopUserCallback callback) {
+ if (DEBUG_MU) Slog.i(TAG, "stopSingleUserLocked userId=" + userId);
final UserState uss = mStartedUsers.get(userId);
if (uss == null) {
// User is not started, nothing to do... but we do need to
@@ -246,7 +284,7 @@ final class UserController {
}
});
}
- return USER_OP_SUCCESS;
+ return;
}
if (callback != null) {
@@ -307,8 +345,6 @@ final class UserController {
Binder.restoreCallingIdentity(ident);
}
}
-
- return USER_OP_SUCCESS;
}
void finishUserStop(UserState uss) {
@@ -350,6 +386,36 @@ final class UserController {
}
}
+ /**
+ * Determines the list of users that should be stopped together with the specified
+ * {@code userId}. The returned list includes {@code userId}.
+ */
+ private @NonNull int[] getUsersToStopLocked(int userId) {
+ int startedUsersSize = mStartedUsers.size();
+ IntArray userIds = new IntArray();
+ userIds.add(userId);
+ synchronized (mUserProfileGroupIdsSelfLocked) {
+ int userGroupId = mUserProfileGroupIdsSelfLocked.get(userId,
+ UserInfo.NO_PROFILE_GROUP_ID);
+ for (int i = 0; i < startedUsersSize; i++) {
+ UserState uss = mStartedUsers.valueAt(i);
+ int startedUserId = uss.mHandle.getIdentifier();
+ // Skip unrelated users (profileGroupId mismatch)
+ int startedUserGroupId = mUserProfileGroupIdsSelfLocked.get(startedUserId,
+ UserInfo.NO_PROFILE_GROUP_ID);
+ boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
+ && (userGroupId == startedUserGroupId);
+ // userId has already been added
+ boolean sameUserId = startedUserId == userId;
+ if (!sameGroup || sameUserId) {
+ continue;
+ }
+ userIds.add(startedUserId);
+ }
+ }
+ return userIds.toArray();
+ }
+
private void forceStopUserLocked(int userId, String reason) {
mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
userId, reason);
@@ -362,7 +428,6 @@ final class UserController {
null, false, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
}
-
/**
* Stops the guest user if it has gone to the background.
*/
@@ -380,7 +445,7 @@ final class UserController {
UserInfo userInfo = getUserInfo(oldUserId);
if (userInfo.isGuest()) {
// This is a user to be stopped.
- stopUserLocked(oldUserId, null);
+ stopUsersLocked(oldUserId, true, null);
break;
}
}
@@ -476,7 +541,7 @@ final class UserController {
// If the user we are switching to is not currently started, then
// we need to start it now.
if (mStartedUsers.get(userId) == null) {
- mStartedUsers.put(userId, new UserState(new UserHandle(userId)));
+ mStartedUsers.put(userId, new UserState(UserHandle.of(userId)));
updateStartedUserArrayLocked();
needStart = true;
}
@@ -624,6 +689,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 +713,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 +721,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;
}
@@ -673,6 +760,24 @@ final class UserController {
mUserSwitchObservers.finishBroadcast();
}
+ private void stopBackgroundUsersIfEnforced(int oldUserId) {
+ // Never stop system user
+ if (oldUserId == UserHandle.USER_SYSTEM) {
+ return;
+ }
+ // For now, only check for user restriction. Additional checks can be added here
+ boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND,
+ oldUserId);
+ if (!disallowRunInBg) {
+ return;
+ }
+ synchronized (mService) {
+ if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId
+ + " and related users");
+ stopUsersLocked(oldUserId, false, null);
+ }
+ }
+
void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
synchronized (mService) {
Slog.wtf(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
@@ -725,7 +830,7 @@ final class UserController {
}
void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
- completeSwitchAndInitialize(uss, newUserId, false, true);
+ completeSwitchAndInitialize(uss, oldUserId, newUserId, false, true);
}
void onUserInitialized(UserState uss, boolean foreground, int oldUserId, int newUserId) {
@@ -734,10 +839,10 @@ final class UserController {
moveUserToForegroundLocked(uss, oldUserId, newUserId);
}
}
- completeSwitchAndInitialize(uss, newUserId, true, false);
+ completeSwitchAndInitialize(uss, oldUserId, newUserId, true, false);
}
- void completeSwitchAndInitialize(UserState uss, int newUserId,
+ void completeSwitchAndInitialize(UserState uss, int oldUserId, int newUserId,
boolean clearInitializing, boolean clearSwitching) {
boolean unfrozen = false;
synchronized (mService) {
@@ -759,6 +864,7 @@ final class UserController {
newUserId, 0));
}
stopGuestUserIfBackground();
+ stopBackgroundUsersIfEnforced(oldUserId);
}
void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) {
@@ -1011,12 +1117,11 @@ final class UserController {
if ((flags & ActivityManager.FLAG_OR_STOPPED) != 0) {
return true;
}
- if ((flags & ActivityManager.FLAG_WITH_AMNESIA) != 0) {
- // If user is currently locked, we fall through to default "running"
- // behavior below
- if (state.unlocked) {
- return false;
- }
+ if ((flags & ActivityManager.FLAG_AND_LOCKED) != 0 && state.unlocked) {
+ return false;
+ }
+ if ((flags & ActivityManager.FLAG_AND_UNLOCKED) != 0 && !state.unlocked) {
+ return false;
}
return state.mState != UserState.STATE_STOPPING
&& state.mState != UserState.STATE_SHUTDOWN;
@@ -1052,6 +1157,10 @@ final class UserController {
return mCurrentUserId;
}
+ private boolean isCurrentUserLocked(int userId) {
+ return mCurrentUserId == userId || mTargetUserId == userId;
+ }
+
int setTargetUserIdLocked(int targetUserId) {
return mTargetUserId = targetUserId;
}
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index d44d89d7b076..278d70b2226d 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -387,8 +387,11 @@ public class MediaFocusControl {
/** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */
protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) {
- Log.i(TAG, " AudioFocus requestAudioFocus() from " + clientId + " req=" + focusChangeHint +
- "flags=0x" + Integer.toHexString(flags));
+ Log.i(TAG, " AudioFocus requestAudioFocus() from uid/pid " + Binder.getCallingUid()
+ + "/" + Binder.getCallingPid()
+ + " clientId=" + clientId
+ + " req=" + focusChangeHint
+ + " flags=0x" + Integer.toHexString(flags));
// we need a valid binder callback for clients
if (!cb.pingBinder()) {
Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
@@ -481,7 +484,9 @@ public class MediaFocusControl {
* */
protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) {
// AudioAttributes are currently ignored, to be used for zones
- Log.i(TAG, " AudioFocus abandonAudioFocus() from " + clientId);
+ Log.i(TAG, " AudioFocus abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
+ + "/" + Binder.getCallingPid()
+ + " clientId=" + clientId);
try {
// this will take care of notifying the new focus owner if needed
synchronized(mAudioFocusLock) {
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 6687412a2a13..3b43633cfc37 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -33,7 +33,9 @@ import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
+import android.net.NetworkRequest;
import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.os.Binder;
@@ -213,6 +215,7 @@ public class Tethering extends BaseNetworkObserver {
checkDunRequired();
}
+ @Override
public void interfaceStatusChanged(String iface, boolean up) {
if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
boolean found = false;
@@ -248,6 +251,7 @@ public class Tethering extends BaseNetworkObserver {
}
}
+ @Override
public void interfaceLinkStateChanged(String iface, boolean up) {
if (VDBG) Log.d(TAG, "interfaceLinkStateChanged " + iface + ", " + up);
interfaceStatusChanged(iface, up);
@@ -280,6 +284,7 @@ public class Tethering extends BaseNetworkObserver {
}
}
+ @Override
public void interfaceAdded(String iface) {
if (VDBG) Log.d(TAG, "interfaceAdded " + iface);
boolean found = false;
@@ -311,6 +316,7 @@ public class Tethering extends BaseNetworkObserver {
}
}
+ @Override
public void interfaceRemoved(String iface) {
if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
synchronized (mPublicSync) {
@@ -638,7 +644,7 @@ public class Tethering extends BaseNetworkObserver {
return values;
}
- public void checkDunRequired() {
+ private void checkDunRequired() {
int secureSetting = 2;
TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
if (tm != null) {
@@ -1135,10 +1141,8 @@ public class Tethering extends BaseNetworkObserver {
static final int CMD_TETHER_MODE_UNREQUESTED = 2;
// upstream connection change - do the right thing
static final int CMD_UPSTREAM_CHANGED = 3;
- // we received notice that the cellular DUN connection is up
- static final int CMD_CELL_CONNECTION_RENEW = 4;
// we don't have a valid upstream conn, check again after a delay
- static final int CMD_RETRY_UPSTREAM = 5;
+ static final int CMD_RETRY_UPSTREAM = 4;
// This indicates what a timeout event relates to. A state that
// sends itself a delayed timeout event and handles incoming timeout events
@@ -1157,13 +1161,12 @@ public class Tethering extends BaseNetworkObserver {
private ArrayList<TetherInterfaceSM> mNotifyList;
- private int mCurrentConnectionSequence;
private int mMobileApnReserved = ConnectivityManager.TYPE_NONE;
+ private ConnectivityManager.NetworkCallback mMobileUpstreamCallback;
private String mUpstreamIfaceName = null;
private static final int UPSTREAM_SETTLE_TIME_MS = 10000;
- private static final int CELL_CONNECTION_RENEW_MS = 40000;
TetherMasterSM(String name, Looper looper) {
super(name, looper);
@@ -1190,58 +1193,69 @@ public class Tethering extends BaseNetworkObserver {
}
class TetherMasterUtilState extends State {
- protected final static boolean TRY_TO_SETUP_MOBILE_CONNECTION = true;
protected final static boolean WAIT_FOR_NETWORK_TO_SETTLE = false;
@Override
public boolean processMessage(Message m) {
return false;
}
- protected String enableString(int apnType) {
+
+ protected boolean turnOnUpstreamMobileConnection(int apnType) {
+ if (apnType == ConnectivityManager.TYPE_NONE) { return false; }
+
+ if (apnType != mMobileApnReserved) {
+ // Unregister any previous mobile upstream callback because
+ // this request, if any, will be different.
+ turnOffUpstreamMobileConnection();
+ }
+
+ if (mMobileUpstreamCallback != null) {
+ // Looks like we already filed a request for this apnType.
+ return true;
+ }
+
switch (apnType) {
- case ConnectivityManager.TYPE_MOBILE_DUN:
- return Phone.FEATURE_ENABLE_DUN_ALWAYS;
- case ConnectivityManager.TYPE_MOBILE:
- case ConnectivityManager.TYPE_MOBILE_HIPRI:
- return Phone.FEATURE_ENABLE_HIPRI;
+ case ConnectivityManager.TYPE_MOBILE_DUN:
+ case ConnectivityManager.TYPE_MOBILE:
+ case ConnectivityManager.TYPE_MOBILE_HIPRI:
+ mMobileApnReserved = apnType;
+ break;
+ default:
+ return false;
}
- return null;
- }
- protected boolean turnOnUpstreamMobileConnection(int apnType) {
- boolean retValue = true;
- if (apnType == ConnectivityManager.TYPE_NONE) return false;
- if (apnType != mMobileApnReserved) turnOffUpstreamMobileConnection();
- int result = PhoneConstants.APN_REQUEST_FAILED;
- String enableString = enableString(apnType);
- if (enableString == null) return false;
- result = getConnectivityManager().startUsingNetworkFeature(
- ConnectivityManager.TYPE_MOBILE, enableString);
- switch (result) {
- case PhoneConstants.APN_ALREADY_ACTIVE:
- case PhoneConstants.APN_REQUEST_STARTED:
- mMobileApnReserved = apnType;
- Message m = obtainMessage(CMD_CELL_CONNECTION_RENEW);
- m.arg1 = ++mCurrentConnectionSequence;
- sendMessageDelayed(m, CELL_CONNECTION_RENEW_MS);
- break;
- case PhoneConstants.APN_REQUEST_FAILED:
- default:
- retValue = false;
- break;
+
+ NetworkRequest.Builder builder = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+ if (apnType == ConnectivityManager.TYPE_MOBILE_DUN) {
+ builder.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ } else {
+ builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
}
+ NetworkRequest mobileUpstreamRequest = builder.build();
+ // Other mechanisms notice network and interface changes and act upon them.
+ // TODO, imminently: replace with a proper NetworkCallback-based scheme.
+ //
+ // TODO: Change the timeout from 0 (no onUnavailable callback) to use some
+ // moderate callback time (once timeout callbacks are implemented). This might
+ // be useful for updating some UI. Additionally, we should definitely log a
+ // message to aid in any subsequent debugging.
+ mMobileUpstreamCallback = new ConnectivityManager.NetworkCallback();
+ if (DBG) Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
+ getConnectivityManager().requestNetwork(
+ mobileUpstreamRequest, mMobileUpstreamCallback, 0, apnType);
- return retValue;
+ return true;
}
- protected boolean turnOffUpstreamMobileConnection() {
- // ignore pending renewal requests
- ++mCurrentConnectionSequence;
- if (mMobileApnReserved != ConnectivityManager.TYPE_NONE) {
- getConnectivityManager().stopUsingNetworkFeature(
- ConnectivityManager.TYPE_MOBILE, enableString(mMobileApnReserved));
- mMobileApnReserved = ConnectivityManager.TYPE_NONE;
+
+ protected void turnOffUpstreamMobileConnection() {
+ if (mMobileUpstreamCallback != null) {
+ getConnectivityManager().unregisterNetworkCallback(mMobileUpstreamCallback);
+ mMobileUpstreamCallback = null;
}
- return true;
+ mMobileApnReserved = ConnectivityManager.TYPE_NONE;
}
+
protected boolean turnOnMasterTetherSettings() {
try {
mNMService.setIpForwardingEnabled(true);
@@ -1304,35 +1318,39 @@ public class Tethering extends BaseNetworkObserver {
}
if (DBG) {
- Log.d(TAG, "chooseUpstreamType(" + tryCell + "), preferredApn ="
- + mPreferredUpstreamMobileApn + ", got type=" + upType);
+ Log.d(TAG, "chooseUpstreamType(" + tryCell + "),"
+ + " preferredApn="
+ + ConnectivityManager.getNetworkTypeName(mPreferredUpstreamMobileApn)
+ + ", got type="
+ + ConnectivityManager.getNetworkTypeName(upType));
}
- // if we're on DUN, put our own grab on it
- if (upType == ConnectivityManager.TYPE_MOBILE_DUN ||
- upType == ConnectivityManager.TYPE_MOBILE_HIPRI) {
- turnOnUpstreamMobileConnection(upType);
- } else if (upType != ConnectivityManager.TYPE_NONE) {
- /* If we've found an active upstream connection that's not DUN/HIPRI
- * we should stop any outstanding DUN/HIPRI start requests.
- *
- * If we found NONE we don't want to do this as we want any previous
- * requests to keep trying to bring up something we can use.
- */
- turnOffUpstreamMobileConnection();
+ switch (upType) {
+ case ConnectivityManager.TYPE_MOBILE_DUN:
+ case ConnectivityManager.TYPE_MOBILE_HIPRI:
+ // If we're on DUN, put our own grab on it.
+ turnOnUpstreamMobileConnection(upType);
+ break;
+ case ConnectivityManager.TYPE_NONE:
+ if (tryCell &&
+ turnOnUpstreamMobileConnection(mPreferredUpstreamMobileApn)) {
+ // We think mobile should be coming up; don't set a retry.
+ } else {
+ sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
+ }
+ break;
+ default:
+ /* If we've found an active upstream connection that's not DUN/HIPRI
+ * we should stop any outstanding DUN/HIPRI start requests.
+ *
+ * If we found NONE we don't want to do this as we want any previous
+ * requests to keep trying to bring up something we can use.
+ */
+ turnOffUpstreamMobileConnection();
+ break;
}
- if (upType == ConnectivityManager.TYPE_NONE) {
- boolean tryAgainLater = true;
- if ((tryCell == TRY_TO_SETUP_MOBILE_CONNECTION) &&
- (turnOnUpstreamMobileConnection(mPreferredUpstreamMobileApn) == true)) {
- // we think mobile should be coming up - don't set a retry
- tryAgainLater = false;
- }
- if (tryAgainLater) {
- sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
- }
- } else {
+ if (upType != ConnectivityManager.TYPE_NONE) {
LinkProperties linkProperties =
getConnectivityManager().getLinkProperties(upType);
if (linkProperties != null) {
@@ -1579,17 +1597,6 @@ public class Tethering extends BaseNetworkObserver {
chooseUpstreamType(mTryCell);
mTryCell = !mTryCell;
break;
- case CMD_CELL_CONNECTION_RENEW:
- // make sure we're still using a requested connection - may have found
- // wifi or something since then.
- if (mCurrentConnectionSequence == message.arg1) {
- if (VDBG) {
- Log.d(TAG, "renewing mobile connection - requeuing for another " +
- CELL_CONNECTION_RENEW_MS + "ms");
- }
- turnOnUpstreamMobileConnection(mMobileApnReserved);
- }
- break;
case CMD_RETRY_UPSTREAM:
chooseUpstreamType(mTryCell);
mTryCell = !mTryCell;
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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 946fbb14624a..e0b73701ef07 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1286,10 +1286,11 @@ public class NotificationManagerService extends SystemService {
Binder.getCallingUid(), incomingUserId, true, false,
"getAppActiveNotifications", pkg);
- final int N = mNotificationList.size();
- final ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>(N);
+ final ArrayList<StatusBarNotification> list
+ = new ArrayList<StatusBarNotification>(mNotificationList.size());
synchronized (mNotificationList) {
+ final int N = mNotificationList.size();
for (int i = 0; i < N; i++) {
final StatusBarNotification sbn = mNotificationList.get(i).sbn;
if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId) {
@@ -1633,6 +1634,14 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public boolean removeAutomaticZenRules(String packageName) throws RemoteException {
+ Preconditions.checkNotNull(packageName, "Package name is null");
+ enforceSystemOrSystemUI("removeAutomaticZenRules");
+
+ return mZenModeHelper.removeAutomaticZenRules(packageName, "removeAutomaticZenRules");
+ }
+
+ @Override
public void setInterruptionFilter(String pkg, int filter) throws RemoteException {
enforcePolicyAccess(pkg, "setInterruptionFilter");
final int zen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index dbdc3f4ca40b..3c891dff5b1d 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -41,6 +41,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings.Global;
@@ -274,7 +275,7 @@ public class ZenModeHelper {
newConfig = mConfig.copy();
}
final String ruleId = automaticZenRule.getId();
- ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ ZenModeConfig.ZenRule rule;
if (ruleId == null) {
throw new IllegalArgumentException("Rule doesn't exist");
} else {
@@ -307,13 +308,32 @@ public class ZenModeHelper {
return setConfig(newConfig, reason, true);
}
+ public boolean removeAutomaticZenRules(String packageName, String reason) {
+ ZenModeConfig newConfig;
+ synchronized (mConfig) {
+ if (mConfig == null) return false;
+ newConfig = mConfig.copy();
+ }
+ for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
+ ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
+ if (rule.component.getPackageName().equals(packageName)
+ && canManageAutomaticZenRule(rule)) {
+ newConfig.automaticRules.removeAt(i);
+ }
+ }
+ return setConfig(newConfig, reason, true);
+ }
+
public boolean canManageAutomaticZenRule(ZenRule rule) {
- if (mContext.checkCallingPermission(android.Manifest.permission.MANAGE_NOTIFICATIONS)
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == 0 || callingUid == Process.SYSTEM_UID) {
+ return true;
+ } else if (mContext.checkCallingPermission(android.Manifest.permission.MANAGE_NOTIFICATIONS)
== PackageManager.PERMISSION_GRANTED) {
return true;
} else {
- String[] packages = mContext.getPackageManager().getPackagesForUid(
- Binder.getCallingUid());
+ String[] packages =
+ mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid());
if (packages != null) {
final int packageCount = packages.length;
for (int i = 0; i < packageCount; i++) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4bc79cb13fce..0fde27f4ebef 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -26,6 +26,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
@@ -400,14 +401,11 @@ public class PackageManagerService extends IPackageManager.Stub {
/** Permission grant: grant the permission as an install permission. */
private static final int GRANT_INSTALL = 2;
- /** Permission grant: grant the permission as an install permission for a legacy app. */
- private static final int GRANT_INSTALL_LEGACY = 3;
-
/** Permission grant: grant the permission as a runtime one. */
- private static final int GRANT_RUNTIME = 4;
+ private static final int GRANT_RUNTIME = 3;
/** Permission grant: grant as runtime a permission that was granted as an install time one. */
- private static final int GRANT_UPGRADE = 5;
+ private static final int GRANT_UPGRADE = 4;
/** Canonical intent used to identify what counts as a "web browser" app */
private static final Intent sBrowserIntent;
@@ -2800,15 +2798,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 +3150,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;
}
@@ -3672,6 +3686,16 @@ public class PackageManagerService extends IPackageManager.Stub {
enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
+ // If a permission review is required for legacy apps we represent
+ // their permissions as always granted runtime ones since we need
+ // to keep the review required permission flag per user while an
+ // install permission's state is shared across all users.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED
+ && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+ && bp.isRuntime()) {
+ return;
+ }
+
uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
sb = (SettingBase) pkg.mExtras;
if (sb == null) {
@@ -3772,6 +3796,16 @@ public class PackageManagerService extends IPackageManager.Stub {
enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
+ // If a permission review is required for legacy apps we represent
+ // their permissions as always granted runtime ones since we need
+ // to keep the review required permission flag per user while an
+ // install permission's state is shared across all users.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED
+ && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+ && bp.isRuntime()) {
+ return;
+ }
+
SettingBase sb = (SettingBase) pkg.mExtras;
if (sb == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
@@ -3890,6 +3924,7 @@ public class PackageManagerService extends IPackageManager.Stub {
flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+ flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
}
synchronized (mPackages) {
@@ -8625,6 +8660,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
+ final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
+ >= Build.VERSION_CODES.M;
switch (level) {
case PermissionInfo.PROTECTION_NORMAL: {
// For all apps normal permissions are install time ones.
@@ -8632,9 +8669,13 @@ public class PackageManagerService extends IPackageManager.Stub {
} break;
case PermissionInfo.PROTECTION_DANGEROUS: {
- if (pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
+ // If a permission review is required for legacy apps we represent
+ // their permissions as always granted runtime ones since we need
+ // to keep the review required permission flag per user while an
+ // install permission's state is shared across all users.
+ if (!appSupportsRuntimePermissions && !Build.PERMISSIONS_REVIEW_REQUIRED) {
// For legacy apps dangerous permissions are install time ones.
- grant = GRANT_INSTALL_LEGACY;
+ grant = GRANT_INSTALL;
} else if (origPermissions.hasInstallPermission(bp.name)) {
// For legacy apps that became modern, install becomes runtime.
grant = GRANT_UPGRADE;
@@ -8681,7 +8722,7 @@ public class PackageManagerService extends IPackageManager.Stub {
switch (grant) {
case GRANT_INSTALL: {
// Revoke this as runtime permission to handle the case of
- // a runtime permission being downgraded to an install one.
+ // a runtime permission being downgraded to an install one. Also in permission review mode we keep dangerous permissions for legacy apps
for (int userId : UserManagerService.getInstance().getUserIds()) {
if (origPermissions.getRuntimePermissionState(
bp.name, userId) != null) {
@@ -8701,20 +8742,12 @@ public class PackageManagerService extends IPackageManager.Stub {
}
} break;
- case GRANT_INSTALL_LEGACY: {
- // Grant an install permission.
- if (permissionsState.grantInstallPermission(bp) !=
- PermissionsState.PERMISSION_OPERATION_FAILURE) {
- changedInstallPermission = true;
- }
- } break;
-
case GRANT_RUNTIME: {
// Grant previously granted runtime permissions.
for (int userId : UserManagerService.getInstance().getUserIds()) {
PermissionState permissionState = origPermissions
.getRuntimePermissionState(bp.name, userId);
- final int flags = permissionState != null
+ int flags = permissionState != null
? permissionState.getFlags() : 0;
if (origPermissions.hasRuntimePermission(bp.name, userId)) {
if (permissionsState.grantRuntimePermission(bp, userId) ==
@@ -8723,6 +8756,27 @@ public class PackageManagerService extends IPackageManager.Stub {
changedRuntimePermissionUserIds = ArrayUtils.appendInt(
changedRuntimePermissionUserIds, userId);
}
+ // If the app supports runtime permissions no need for a review.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED
+ && appSupportsRuntimePermissions
+ && (flags & PackageManager
+ .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+ flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+ // Since we changed the flags, we have to write.
+ changedRuntimePermissionUserIds = ArrayUtils.appendInt(
+ changedRuntimePermissionUserIds, userId);
+ }
+ } else if (Build.PERMISSIONS_REVIEW_REQUIRED
+ && !appSupportsRuntimePermissions) {
+ // For legacy apps that need a permission review, every new
+ // runtime permission is granted but it is pending a review.
+ if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
+ permissionsState.grantRuntimePermission(bp, userId);
+ flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
+ // We changed the permission and flags, hence have to write.
+ changedRuntimePermissionUserIds = ArrayUtils.appendInt(
+ changedRuntimePermissionUserIds, userId);
+ }
}
// Propagate the permission flags.
permissionsState.updatePermissionFlags(bp, userId, flags, flags);
@@ -11574,7 +11628,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
- if (origin.staged) {
+ if (origin.staged && origin.cid != null) {
if (DEBUG_INSTALL) Slog.d(TAG, origin.cid + " already staged; skipping copy");
cid = origin.cid;
setMountPath(PackageHelper.getSdDir(cid));
@@ -13784,9 +13838,11 @@ public class PackageManagerService extends IPackageManager.Stub {
return;
}
- final int userSettableFlags = FLAG_PERMISSION_USER_SET
+ // These are flags that can change base on user actions.
+ final int userSettableMask = FLAG_PERMISSION_USER_SET
| FLAG_PERMISSION_USER_FIXED
- | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+ | FLAG_PERMISSION_REVOKE_ON_UPGRADE
+ | FLAG_PERMISSION_REVIEW_REQUIRED;
final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED
| FLAG_PERMISSION_POLICY_FIXED;
@@ -13827,7 +13883,14 @@ public class PackageManagerService extends IPackageManager.Stub {
// Always clear the user settable flags.
final boolean hasInstallState = permissionsState.getInstallPermissionState(
bp.name) != null;
- if (permissionsState.updatePermissionFlags(bp, userId, userSettableFlags, 0)) {
+ // If permission review is enabled and this is a legacy app, mark the
+ // permission as requiring a review as this is the initial state.
+ int flags = 0;
+ if (Build.PERMISSIONS_REVIEW_REQUIRED
+ && ps.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+ flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
+ }
+ if (permissionsState.updatePermissionFlags(bp, userId, userSettableMask, flags)) {
if (hasInstallState) {
writeInstallPermissions = true;
} else {
@@ -13851,7 +13914,9 @@ public class PackageManagerService extends IPackageManager.Stub {
!= PERMISSION_OPERATION_FAILURE) {
writeRuntimePermissions = true;
}
- } else {
+ // If permission review is enabled the permissions for a legacy apps
+ // are represented as constantly granted runtime ones, so don't revoke.
+ } else if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
// Otherwise, reset the permission.
final int revokeResult = permissionsState.revokeRuntimePermission(bp, userId);
switch (revokeResult) {
@@ -16744,6 +16809,15 @@ public class PackageManagerService extends IPackageManager.Stub {
void newUserCreated(final int userHandle) {
mDefaultPermissionPolicy.grantDefaultPermissions(userHandle);
+ // If permission review for legacy apps is required, we represent
+ // dagerous permissions for such apps as always granted runtime
+ // permissions to keep per user flag state whether review is needed.
+ // Hence, if a new user is added we have to propagate dangerous
+ // permission grants for these legacy apps.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+ updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
+ | UPDATE_PERMISSIONS_REPLACE_ALL);
+ }
}
@Override
@@ -17159,6 +17233,7 @@ public class PackageManagerService extends IPackageManager.Stub {
packageName, userId);
}
}
+
@Override
public void grantDefaultPermissionsToDefaultSimCallManager(String packageName, int userId) {
synchronized (mPackages) {
@@ -17194,6 +17269,30 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
}
+
+ @Override
+ public boolean isPermissionsReviewRequired(String packageName, int userId) {
+ synchronized (mPackages) {
+ // If we do not support permission review, done.
+ if (!Build.PERMISSIONS_REVIEW_REQUIRED) {
+ return false;
+ }
+
+ PackageSetting packageSetting = mSettings.mPackages.get(packageName);
+ if (packageSetting == null) {
+ return false;
+ }
+
+ // Permission review applies only to apps not supporting the new permission model.
+ if (packageSetting.pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
+ return false;
+ }
+
+ // Legacy apps have the permission and get user consent on launch.
+ PermissionsState permissionsState = packageSetting.getPermissionsState();
+ return permissionsState.isPermissionReviewRequired(userId);
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/pm/PermissionsState.java b/services/core/java/com/android/server/pm/PermissionsState.java
index 57ef284f2758..007b73814256 100644
--- a/services/core/java/com/android/server/pm/PermissionsState.java
+++ b/services/core/java/com/android/server/pm/PermissionsState.java
@@ -16,11 +16,13 @@
package com.android.server.pm;
+import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
@@ -64,6 +66,8 @@ public final class PermissionsState {
private int[] mGlobalGids = NO_GIDS;
+ private SparseBooleanArray mPermissionReviewRequired;
+
public PermissionsState() {
/* do nothing */
}
@@ -116,6 +120,28 @@ public final class PermissionsState {
mGlobalGids = Arrays.copyOf(other.mGlobalGids,
other.mGlobalGids.length);
}
+
+ if (mPermissionReviewRequired != null) {
+ if (other.mPermissionReviewRequired == null) {
+ mPermissionReviewRequired = null;
+ } else {
+ mPermissionReviewRequired.clear();
+ }
+ }
+ if (other.mPermissionReviewRequired != null) {
+ if (mPermissionReviewRequired == null) {
+ mPermissionReviewRequired = new SparseBooleanArray();
+ }
+ final int userCount = other.mPermissionReviewRequired.size();
+ for (int i = 0; i < userCount; i++) {
+ final boolean reviewRequired = other.mPermissionReviewRequired.valueAt(i);
+ mPermissionReviewRequired.put(i, reviewRequired);
+ }
+ }
+ }
+
+ public boolean isPermissionReviewRequired(int userId) {
+ return mPermissionReviewRequired != null && mPermissionReviewRequired.get(userId);
}
/**
@@ -357,7 +383,28 @@ public final class PermissionsState {
permissionData = ensurePermissionData(permission);
}
- return permissionData.updateFlags(userId, flagMask, flagValues);
+ final int oldFlags = permissionData.getFlags(userId);
+
+ final boolean updated = permissionData.updateFlags(userId, flagMask, flagValues);
+ if (updated) {
+ final int newFlags = permissionData.getFlags(userId);
+ if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0
+ && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+ if (mPermissionReviewRequired == null) {
+ mPermissionReviewRequired = new SparseBooleanArray();
+ }
+ mPermissionReviewRequired.put(userId, true);
+ } else if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0
+ && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
+ if (mPermissionReviewRequired != null) {
+ mPermissionReviewRequired.delete(userId);
+ if (mPermissionReviewRequired.size() <= 0) {
+ mPermissionReviewRequired = null;
+ }
+ }
+ }
+ }
+ return updated;
}
public boolean updatePermissionFlagsForAllPermissions(
@@ -430,6 +477,7 @@ public final class PermissionsState {
public void reset() {
mGlobalGids = NO_GIDS;
mPermissions = null;
+ mPermissionReviewRequired = null;
}
private PermissionState getPermissionState(String name, int userId) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 424b90234ca2..b31d731e46b9 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);
}
@@ -868,10 +869,8 @@ public class UserManagerService extends IUserManager.Stub {
mHandler.post(new Runnable() {
@Override
public void run() {
- synchronized (mRestrictionsLock) {
- UserRestrictionsUtils.applyUserRestrictionsLR(
- mContext, userId, newRestrictionsFinal, prevRestrictionsFinal);
- }
+ UserRestrictionsUtils.applyUserRestrictions(
+ mContext, userId, newRestrictionsFinal, prevRestrictionsFinal);
final UserRestrictionsListener[] listeners;
synchronized (mUserRestrictionsListeners) {
@@ -1652,6 +1651,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);
}
@@ -1805,8 +1809,8 @@ public class UserManagerService extends IUserManager.Stub {
if (DBG) Slog.i(LOG_TAG, "Stopping user " + userHandle);
int res;
try {
- res = ActivityManagerNative.getDefault().stopUser(userHandle,
- new IStopUserCallback.Stub() {
+ res = ActivityManagerNative.getDefault().stopUser(userHandle, /* force= */ true,
+ new IStopUserCallback.Stub() {
@Override
public void userStopped(int userId) {
finishRemoveUser(userId);
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 77abd3ee0790..816903ec4901 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -22,11 +22,14 @@ import com.android.internal.util.Preconditions;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
+import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -84,7 +87,8 @@ public class UserRestrictionsUtils {
UserManager.DISALLOW_SAFE_BOOT,
UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
UserManager.DISALLOW_RECORD_AUDIO,
- UserManager.DISALLOW_CAMERA
+ UserManager.DISALLOW_CAMERA,
+ UserManager.DISALLOW_RUN_IN_BACKGROUND
);
/**
@@ -126,6 +130,7 @@ public class UserRestrictionsUtils {
*/
private static final Set<String> GLOBAL_RESTRICTIONS = Sets.newArraySet(
UserManager.DISALLOW_ADJUST_VOLUME,
+ UserManager.DISALLOW_RUN_IN_BACKGROUND,
UserManager.DISALLOW_UNMUTE_MICROPHONE
);
@@ -263,34 +268,28 @@ public class UserRestrictionsUtils {
* Takes a new use restriction set and the previous set, and apply the restrictions that have
* changed.
*
- * <p>Note this method is called by {@link UserManagerService} while holding
- * {@code mRestrictionLock}. Be aware when calling into other services, which could cause
- * a deadlock.
+ * <p>Note this method is called by {@link UserManagerService} without holding any locks.
*/
- public static void applyUserRestrictionsLR(Context context, int userId,
+ public static void applyUserRestrictions(Context context, int userId,
Bundle newRestrictions, Bundle prevRestrictions) {
for (String key : USER_RESTRICTIONS) {
final boolean newValue = newRestrictions.getBoolean(key);
final boolean prevValue = prevRestrictions.getBoolean(key);
if (newValue != prevValue) {
- applyUserRestrictionLR(context, userId, key, newValue);
+ applyUserRestriction(context, userId, key, newValue);
}
}
}
-
+
/**
* Apply each user restriction.
*
- * <p>Note this method is called by {@link UserManagerService} while holding
- * {@code mRestrictionLock}. Be aware when calling into other services, which could cause
- * a deadlock.
- *
* <p>See also {@link
* com.android.providers.settings.SettingsProvider#isGlobalOrSecureSettingRestrictedForUser},
* which should be in sync with this method.
*/
- private static void applyUserRestrictionLR(Context context, int userId, String key,
+ private static void applyUserRestriction(Context context, int userId, String key,
boolean newValue) {
if (UserManagerService.DBG) {
Log.d(TAG, "Applying user restriction: userId=" + userId
@@ -365,6 +364,17 @@ public class UserRestrictionsUtils {
userId);
}
break;
+ case UserManager.DISALLOW_RUN_IN_BACKGROUND:
+ if (newValue) {
+ int currentUser = ActivityManager.getCurrentUser();
+ if (currentUser != userId && userId != UserHandle.USER_SYSTEM) {
+ try {
+ ActivityManagerNative.getDefault().stopUser(userId, false, null);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+ }
}
} finally {
Binder.restoreCallingIdentity(id);
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/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index b32ec2d1b0a6..4861acc14121 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -109,7 +109,7 @@ public class AppWindowAnimator {
public AppWindowAnimator(final AppWindowToken atoken) {
mAppToken = atoken;
mService = atoken.service;
- mAnimator = atoken.mAnimator;
+ mAnimator = mService.mAnimator;
}
public void setAnimation(Animation anim, int width, int height, boolean skipFirstFrame) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 425ff9b93b1c..3d00e027a009 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -24,6 +24,7 @@ import static com.android.server.wm.WindowManagerService.TAG;
import com.android.server.input.InputApplicationHandle;
import com.android.server.wm.WindowManagerService.H;
+import android.annotation.NonNull;
import android.content.pm.ActivityInfo;
import android.os.Message;
import android.os.RemoteException;
@@ -49,9 +50,7 @@ class AppWindowToken extends WindowToken {
// All of the windows and child windows that are included in this
// application token. Note this list is NOT sorted!
final WindowList allAppWindows = new WindowList();
- final AppWindowAnimator mAppAnimator;
-
- final WindowAnimator mAnimator;
+ @NonNull final AppWindowAnimator mAppAnimator;
final boolean voiceInteraction;
@@ -145,7 +144,6 @@ class AppWindowToken extends WindowToken {
appToken = _token;
voiceInteraction = _voiceInteraction;
mInputApplicationHandle = new InputApplicationHandle(this);
- mAnimator = service.mAnimator;
mAppAnimator = new AppWindowAnimator(this);
}
@@ -259,7 +257,7 @@ class AppWindowToken extends WindowToken {
if (win.mAttrs.type == WindowManager.LayoutParams.TYPE_BASE_APPLICATION
|| win.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
// In cases where there are multiple windows, we prefer the non-exiting window. This
- // happens for example when when replacing windows during an activity relaunch. When
+ // happens for example when replacing windows during an activity relaunch. When
// constructing the animation, we want the new window, not the exiting one.
if (win.mExiting) {
candidate = win;
@@ -271,6 +269,10 @@ class AppWindowToken extends WindowToken {
return candidate;
}
+ boolean stackCanReceiveKeys() {
+ return (windows.size() > 0) ? windows.get(windows.size() - 1).stackCanReceiveKeys() : false;
+ }
+
boolean isVisible() {
final int N = allAppWindows.size();
for (int i=0; i<N; i++) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e9e09ec8c8a9..4bbf58638896 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -94,8 +94,9 @@ class DisplayContent {
Region mNonResizeableRegion = new Region();
/** Save allocating when calculating rects */
- private Rect mTmpRect = new Rect();
- private Rect mTmpRect2 = new Rect();
+ private final Rect mTmpRect = new Rect();
+ private final Rect mTmpRect2 = new Rect();
+ private final Region mTmpRegion = new Region();
/** For gathering Task objects in order. */
final ArrayList<Task> mTmpTaskHistory = new ArrayList<Task>();
@@ -403,6 +404,14 @@ class DisplayContent {
if (addBackFocusedTask) {
mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION);
}
+ final WindowState inputMethod = mService.mInputMethodWindow;
+ if (inputMethod != null && inputMethod.isVisibleLw()) {
+ // If the input method is visible and the user is typing, we don't want these touch
+ // events to be intercepted and used to change focus. This would likely cause a
+ // disappearance of the input method.
+ inputMethod.getTouchableRegion(mTmpRegion);
+ mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
+ }
if (mTapDetector != null) {
mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion, mNonResizeableRegion);
}
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..22f1d634e955 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) {
@@ -517,8 +562,10 @@ class Task implements DimLayer.DimLayerUser {
}
public void printTo(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("taskId="); pw.print(mTaskId);
- pw.print(prefix); pw.print("appTokens="); pw.print(mAppTokens);
- pw.print(prefix); pw.print("mdr="); pw.println(mDeferRemoval);
+ pw.print(prefix); pw.print("taskId="); pw.println(mTaskId);
+ pw.print(prefix + prefix); pw.print("mFullscreen="); pw.println(mFullscreen);
+ pw.print(prefix + prefix); pw.print("mBounds="); pw.println(mBounds.toShortString());
+ pw.print(prefix + prefix); pw.print("mdr="); pw.println(mDeferRemoval);
+ pw.print(prefix + prefix); pw.print("appTokens="); pw.println(mAppTokens);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 87deaa4d930c..611ad407dffe 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -602,6 +602,8 @@ public class TaskStack implements DimLayer.DimLayerUser {
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("mStackId="); pw.println(mStackId);
pw.print(prefix); pw.print("mDeferDetach="); pw.println(mDeferDetach);
+ pw.print(prefix); pw.print("mFullscreen="); pw.println(mFullscreen);
+ pw.print(prefix); pw.print("mBounds="); pw.println(mBounds.toShortString());
for (int taskNdx = 0; taskNdx < mTasks.size(); ++taskNdx) {
pw.print(prefix);
mTasks.get(taskNdx).printTo(prefix + " ", pw);
@@ -658,10 +660,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) {
@@ -699,26 +702,4 @@ public class TaskStack implements DimLayer.DimLayerUser {
}
return false;
}
-
- /**
- * Returns true if this stack has a window that is fully visible, doesn't perform an entry
- * animation and is just positioned where it's supposed to be.
- */
- boolean hasWindowWithFinalVisibility() {
- for (int i = mTasks.size() - 1; i >= 0; i--) {
- Task task = mTasks.get(i);
- for (int j = task.mAppTokens.size() - 1; j >= 0; j--) {
- final AppWindowToken token = task.mAppTokens.get(j);
- if (token.mAppAnimator.animating || token.mWillReplaceWindow) {
- continue;
- }
- WindowState win = token.findMainWindow();
- if (win != null && !win.mWinAnimator.mEnterAnimationPending
- && !win.mWinAnimator.mEnteringAnimation) {
- return true;
- }
- }
- }
- return false;
- }
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 46fab2ae0223..f20c4e51a1bf 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -220,8 +220,8 @@ public class WindowAnimator {
if (appShowWhenLocked != null) {
allowWhenLocked |= appShowWhenLocked == win.mAppToken
- // Show all SHOW_WHEN_LOCKED windows while they're animating
- || (win.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0 && win.isAnimatingLw()
+ // Show all SHOW_WHEN_LOCKED windows if some apps are shown over lockscreen
+ || (win.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0
// Show error dialogs over apps that dismiss keyguard.
|| (win.mAttrs.privateFlags & PRIVATE_FLAG_SYSTEM_ERROR) != 0;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 21d3bbaea730..1bb5ad029c4c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -110,7 +110,6 @@ import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.TypedValue;
@@ -122,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;
@@ -150,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;
@@ -406,7 +408,7 @@ public class WindowManagerService extends IWindowManager.Stub
/**
* Windows whose surface should be destroyed.
*/
- private final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
+ final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
/**
* Windows with a preserved surface waiting to be destroyed. These windows
@@ -443,11 +445,11 @@ public class WindowManagerService extends IWindowManager.Stub
WindowState[] mRebuildTmp = new WindowState[20];
/**
- * Stores for each user whether screencapture is disabled for all their windows.
+ * Stores for each user whether screencapture is disabled
* This array is essentially a cache for all userId for
* {@link android.app.admin.DevicePolicyManager#getScreenCaptureDisabled}
*/
- private SparseBooleanArray mScreenCaptureDisabled = new SparseBooleanArray();
+ SparseArray<Boolean> mScreenCaptureDisabled = new SparseArray<>();
IInputMethodManager mInputMethodManager;
@@ -2109,11 +2111,25 @@ public class WindowManagerService extends IWindowManager.Stub
executeAppTransition();
}
+ /**
+ * Returns whether screen capture is disabled for all windows of a specific user.
+ */
+ boolean isScreenCaptureDisabledLocked(int userId) {
+ Boolean disabled = mScreenCaptureDisabled.get(userId);
+ if (disabled == null) {
+ return false;
+ }
+ return disabled;
+ }
+
boolean isSecureLocked(WindowState w) {
- if ((w.mAttrs.flags & FLAG_SECURE) != 0) {
+ if ((w.mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+ return true;
+ }
+ if (isScreenCaptureDisabledLocked(UserHandle.getUserId(w.mOwnerUid))) {
return true;
}
- return mScreenCaptureDisabled.get(UserHandle.getUserId(w.mOwnerUid));
+ return false;
}
/**
@@ -2512,10 +2528,12 @@ public class WindowManagerService extends IWindowManager.Stub
+ "attached to a parent win=" + win);
}
- win.mFrame.left = left;
- win.mFrame.top = top;
- win.mFrame.right = right;
- win.mFrame.bottom = bottom;
+ win.mAttrs.x = left;
+ win.mAttrs.y = top;
+ win.mAttrs.width = right - left;
+ win.mAttrs.height = bottom - top;
+
+ win.setWindowScale(win.mRequestedWidth, win.mRequestedHeight);
win.mWinAnimator.computeShownFrameLocked();
@@ -2637,10 +2655,8 @@ public class WindowManagerService extends IWindowManager.Stub
Slog.i(TAG, "Relayout " + win + ": oldVis=" + oldVisibility
+ " newVis=" + viewVisibility, stack);
}
- final AppWindowToken appToken = win.mAppToken;
- final boolean visible = viewVisibility == View.VISIBLE
- && (appToken == null ? win.mPolicyVisibility : !appToken.clientHidden);
- if (visible) {
+ if (viewVisibility == View.VISIBLE &&
+ (win.mAppToken == null || !win.mAppToken.clientHidden)) {
result = relayoutVisibleWindow(outConfig, result, win, winAnimator, attrChanges,
oldVisibility);
try {
@@ -2678,8 +2694,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);
@@ -2726,8 +2741,8 @@ public class WindowManagerService extends IWindowManager.Stub
mWallpaperControllerLocked.updateWallpaperOffset(
win, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
}
- if (appToken != null) {
- appToken.updateReportedVisibilityLocked();
+ if (win.mAppToken != null) {
+ win.mAppToken.updateReportedVisibilityLocked();
}
if (winAnimator.mReportSurfaceResized) {
winAnimator.mReportSurfaceResized = false;
@@ -3064,7 +3079,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;
@@ -4776,6 +4791,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);
@@ -7441,6 +7467,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.
@@ -8013,6 +8040,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");
@@ -9093,8 +9131,9 @@ public class WindowManagerService extends IWindowManager.Stub
if (wtoken == token) {
break;
}
- if (mFocusedApp == token) {
- // Whoops, we are below the focused app... no focus for you!
+ if (mFocusedApp == token && token.stackCanReceiveKeys()) {
+ // Whoops, we are below the focused app whose stack can receive keys...
+ // No focus for you!!!
if (localLOGV || DEBUG_FOCUS_LIGHT) Slog.v(TAG,
"findFocusedWindow: Reached focused app=" + mFocusedApp);
return null;
@@ -10200,23 +10239,27 @@ public class WindowManagerService extends IWindowManager.Stub
mDestroySurface.add(win);
}
- boolean destroySurfacesLocked() {
- boolean wallpaperDestroyed = false;
- for (int i = mDestroySurface.size() - 1; i >= 0; i--) {
- WindowState win = mDestroySurface.get(i);
- win.mDestroying = false;
- if (mInputMethodWindow == win) {
- mInputMethodWindow = null;
- }
- if (mWallpaperControllerLocked.isWallpaperTarget(win)) {
- wallpaperDestroyed = true;
- }
- if (!win.shouldSaveSurface()) {
- win.mWinAnimator.destroySurfaceLocked();
- }
+ @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);
}
- mDestroySurface.clear();
- return wallpaperDestroyed;
}
private final class LocalService extends WindowManagerInternal {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a0a2162b0f90..cfa2bb370a4e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -56,6 +56,7 @@ import android.view.WindowManagerPolicy;
import java.io.PrintWriter;
import java.util.ArrayList;
+import static android.app.ActivityManager.StackId;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
@@ -411,10 +412,6 @@ final class WindowState implements WindowManagerPolicy.WindowState {
final private Rect mTmpRect = new Rect();
- // This window often remains added but hidden, so we want to destroy its surface when it's not
- // visible.
- private final boolean mDestroySurfaceWhenHidden;
-
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, final DisplayContent displayContent) {
@@ -462,7 +459,6 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mSubLayer = 0;
mInputWindowHandle = null;
mWinAnimator = null;
- mDestroySurfaceWhenHidden = false;
return;
}
mDeathRecipient = deathRecipient;
@@ -561,7 +557,6 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mInputWindowHandle = new InputWindowHandle(
mAppToken != null ? mAppToken.mInputApplicationHandle : null, this,
displayContent.getDisplayId());
- mDestroySurfaceWhenHidden = mAttrs.type == TYPE_DOCK_DIVIDER;
}
void attach() {
@@ -736,7 +731,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
@@ -1319,10 +1314,6 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mHasSurface = hasSurface;
}
- boolean shouldDestroySurfaceWhenAnimationFinishes() {
- return mExiting || (mDestroySurfaceWhenHidden && !mPolicyVisibilityAfterAnim);
- }
-
private final class DeadWindowEventReceiver extends InputEventReceiver {
DeadWindowEventReceiver(InputChannel inputChannel) {
super(inputChannel, mService.mH.getLooper());
@@ -1574,13 +1565,17 @@ final class WindowState implements WindowManagerPolicy.WindowState {
}
}
- /**
- * @return true if this window desires key events.
- */
- public final boolean canReceiveKeys() {
+ /** @return true if this window desires key events. */
+ boolean canReceiveKeys() {
return isVisibleOrAdding()
&& (mViewVisibility == View.VISIBLE)
- && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0);
+ && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
+ && stackCanReceiveKeys();
+ }
+
+ boolean stackCanReceiveKeys() {
+ final TaskStack stack = getStack();
+ return stack != null && StackId.canReceiveKeys(stack.mStackId);
}
@Override
@@ -1605,11 +1600,6 @@ final class WindowState implements WindowManagerPolicy.WindowState {
// Already showing.
return false;
}
- if (!mHasSurface && mDestroySurfaceWhenHidden) {
- // This is a window that doesn't retain the surface when it's hidden, so immediately
- // when we want to show it again, we need to create the surface for it.
- mWinAnimator.createSurfaceLocked();
- }
if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility true: " + this);
if (doAnimation) {
if (DEBUG_VISIBILITY) Slog.v(TAG, "doAnimation: mPolicyVisibility="
@@ -1645,7 +1635,8 @@ final class WindowState implements WindowManagerPolicy.WindowState {
doAnimation = false;
}
}
- final boolean current = doAnimation ? mPolicyVisibilityAfterAnim : mPolicyVisibility;
+ boolean current = doAnimation ? mPolicyVisibilityAfterAnim
+ : mPolicyVisibility;
if (!current) {
// Already hiding.
return false;
@@ -1656,9 +1647,11 @@ final class WindowState implements WindowManagerPolicy.WindowState {
doAnimation = false;
}
}
- mPolicyVisibilityAfterAnim = false;
- if (!doAnimation) {
+ if (doAnimation) {
+ mPolicyVisibilityAfterAnim = false;
+ } else {
if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility false: " + this);
+ mPolicyVisibilityAfterAnim = false;
mPolicyVisibility = false;
// Window is no longer visible -- make sure if we were waiting
// for it to be displayed before enabling the display, that
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 9726034d0c0f..132b1b60ec4b 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -446,7 +446,7 @@ class WindowStateAnimator {
}
}
- if (!mWin.shouldDestroySurfaceWhenAnimationFinishes()) {
+ if (!mWin.mExiting) {
return;
}
@@ -454,13 +454,12 @@ class WindowStateAnimator {
return;
}
- if (localLOGV) Slog.v(TAG, "Exit animation finished in " + this + ": remove="
- + mWin.mRemoveOnExit);
+ if (WindowManagerService.localLOGV) Slog.v(
+ TAG, "Exit animation finished in " + this
+ + ": remove=" + mWin.mRemoveOnExit);
if (mSurfaceController != null && mSurfaceController.hasSurface()) {
- mService.scheduleSurfaceDestroy(mWin);
- if (mWin.mExiting) {
- mWin.mDestroying = true;
- }
+ mService.mDestroySurface.add(mWin);
+ mWin.mDestroying = true;
hide("finishExit");
}
mWin.mExiting = false;
@@ -646,7 +645,7 @@ class WindowStateAnimator {
return null;
}
- if (localLOGV) {
+ if (WindowManagerService.localLOGV) {
Slog.v(TAG, "Got surface: " + mSurfaceController
+ ", set left=" + w.mFrame.left + " top=" + w.mFrame.top
+ ", animLayer=" + mAnimLayer);
@@ -667,7 +666,7 @@ class WindowStateAnimator {
mAnimLayer);
mLastHidden = true;
- if (localLOGV) Slog.v(
+ if (WindowManagerService.localLOGV) Slog.v(
TAG, "Created surface " + this);
}
return mSurfaceController;
@@ -974,7 +973,7 @@ class WindowStateAnimator {
//Slog.i(TAG, "Not applying alpha transform");
}
- if ((DEBUG_SURFACE_TRACE || localLOGV)
+ if ((DEBUG_SURFACE_TRACE || WindowManagerService.localLOGV)
&& (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
+ " self=" + (selfTransformation ? mTransformation.getAlpha() : "null")
@@ -995,7 +994,7 @@ class WindowStateAnimator {
return;
}
- if (localLOGV) Slog.v(
+ if (WindowManagerService.localLOGV) Slog.v(
TAG, "computeShownFrameLocked: " + this +
" not attached, mAlpha=" + mAlpha);
@@ -1475,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/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index b3c23d1a8ab0..f8b8d6ced8d8 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -296,8 +296,10 @@ class WindowSurfaceController {
}
boolean showRobustlyInTransaction() {
- if (SHOW_TRANSACTIONS) logSurface("SHOW (performLayout)", null);
- if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this + " during relayout");
+ if (SHOW_TRANSACTIONS) logSurface(
+ "SHOW (performLayout)", null);
+ if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this
+ + " during relayout");
if (mHiddenForCrop) {
return false;
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 54d18e9cf363..214901991fac 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -381,7 +381,25 @@ class WindowSurfacePlacer {
}
// Destroy the surface of any windows that are no longer visible.
- final boolean wallpaperDestroyed = mService.destroySurfacesLocked();
+ boolean wallpaperDestroyed = false;
+ i = mService.mDestroySurface.size();
+ if (i > 0) {
+ do {
+ i--;
+ WindowState win = mService.mDestroySurface.get(i);
+ win.mDestroying = false;
+ if (mService.mInputMethodWindow == win) {
+ mService.mInputMethodWindow = null;
+ }
+ if (mWallpaperControllerLocked.isWallpaperTarget(win)) {
+ wallpaperDestroyed = true;
+ }
+ if (!win.shouldSaveSurface()) {
+ win.mWinAnimator.destroySurfaceLocked();
+ }
+ } while (i > 0);
+ mService.mDestroySurface.clear();
+ }
// Time to remove any exiting tokens?
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 819925080ea3..dbfd80d9732c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3855,10 +3855,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
synchronized (this) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(comp, userHandle);
if (admin == null) {
- try {
- result.sendResult(null);
- } catch (RemoteException e) {
- }
+ result.sendResult(null);
return;
}
Intent intent = new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLE_REQUESTED);
@@ -3868,10 +3865,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
null, new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- try {
- result.sendResult(getResultExtras(false));
- } catch (RemoteException e) {
- }
+ result.sendResult(getResultExtras(false));
}
}, null, Activity.RESULT_OK, null, null);
}
@@ -3891,39 +3885,31 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
- DevicePolicyData p = getUserData(userHandle);
-
validateQualityConstant(quality);
- synchronized (this) {
- if (p.mActivePasswordQuality != quality || p.mActivePasswordLength != length
- || p.mFailedPasswordAttempts != 0 || p.mActivePasswordLetters != letters
- || p.mActivePasswordUpperCase != uppercase
- || p.mActivePasswordLowerCase != lowercase
- || p.mActivePasswordNumeric != numbers
- || p.mActivePasswordSymbols != symbols
- || p.mActivePasswordNonLetter != nonletter) {
- long ident = mInjector.binderClearCallingIdentity();
- try {
- p.mActivePasswordQuality = quality;
- p.mActivePasswordLength = length;
- p.mActivePasswordLetters = letters;
- p.mActivePasswordLowerCase = lowercase;
- p.mActivePasswordUpperCase = uppercase;
- p.mActivePasswordNumeric = numbers;
- p.mActivePasswordSymbols = symbols;
- p.mActivePasswordNonLetter = nonletter;
- p.mFailedPasswordAttempts = 0;
- saveSettingsLocked(userHandle);
- updatePasswordExpirationsLocked(userHandle);
- setExpirationAlarmCheckLocked(mContext, p);
- sendAdminCommandToSelfAndProfilesLocked(
- DeviceAdminReceiver.ACTION_PASSWORD_CHANGED,
- DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userHandle);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ DevicePolicyData policy = getUserData(userHandle);
+
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ synchronized (this) {
+ policy.mActivePasswordQuality = quality;
+ policy.mActivePasswordLength = length;
+ policy.mActivePasswordLetters = letters;
+ policy.mActivePasswordLowerCase = lowercase;
+ policy.mActivePasswordUpperCase = uppercase;
+ policy.mActivePasswordNumeric = numbers;
+ policy.mActivePasswordSymbols = symbols;
+ policy.mActivePasswordNonLetter = nonletter;
+ policy.mFailedPasswordAttempts = 0;
+ saveSettingsLocked(userHandle);
+ updatePasswordExpirationsLocked(userHandle);
+ setExpirationAlarmCheckLocked(mContext, policy);
+ sendAdminCommandToSelfAndProfilesLocked(
+ DeviceAdminReceiver.ACTION_PASSWORD_CHANGED,
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userHandle);
}
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
}
}
@@ -5567,8 +5553,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// InputMethodManager fetches input methods for current user.
// So this can only be set when calling user is the current user
// or parent is current user in case of managed profiles.
- InputMethodManager inputMethodManager = (InputMethodManager) mContext
- .getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager inputMethodManager =
+ mContext.getSystemService(InputMethodManager.class);
List<InputMethodInfo> enabledImes = inputMethodManager.getEnabledInputMethodList();
if (enabledImes != null) {
@@ -5646,8 +5632,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// If we have a permitted list add all system input methods.
if (result != null) {
- InputMethodManager inputMethodManager = (InputMethodManager) mContext
- .getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager inputMethodManager =
+ mContext.getSystemService(InputMethodManager.class);
List<InputMethodInfo> imes = inputMethodManager.getInputMethodList();
long id = mInjector.binderClearCallingIdentity();
try {
@@ -6823,6 +6809,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
public boolean isProvisioningAllowed(String action) {
final int callingUserId = mInjector.userHandleGetCallingUserId();
if (DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE.equals(action)) {
+ if (!hasFeatureManagedUsers()) {
+ return false;
+ }
synchronized (this) {
if (mOwners.hasDeviceOwner()) {
if (!mInjector.userManagerIsSplitSystemUser()) {
@@ -6845,13 +6834,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// Managed user cannot have a managed profile.
return false;
}
- try {
- if (!mIPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
- return false;
- }
- } catch (RemoteException e) {
- return false;
- }
final long ident = mInjector.binderClearCallingIdentity();
try {
if (!mUserManager.canAddMoreManagedProfiles(callingUserId, true)) {
@@ -6864,10 +6846,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
} else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE.equals(action)) {
return isDeviceOwnerProvisioningAllowed(callingUserId);
} else if (DevicePolicyManager.ACTION_PROVISION_MANAGED_USER.equals(action)) {
+ if (!hasFeatureManagedUsers()) {
+ return false;
+ }
if (!mInjector.userManagerIsSplitSystemUser()) {
// ACTION_PROVISION_MANAGED_USER only supported on split-user systems.
return false;
}
+ if (callingUserId == UserHandle.USER_SYSTEM) {
+ // System user cannot be a managed user.
+ return false;
+ }
if (hasUserSetupCompleted(callingUserId)) {
return false;
}
@@ -6901,6 +6890,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return true;
}
+ private boolean hasFeatureManagedUsers() {
+ try {
+ return mIPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
@Override
public String getWifiMacAddress() {
// Make sure caller has DO.
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index 92bd81fd0fd6..0a8c014c4d37 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -228,6 +228,11 @@ public final class PrintManagerService extends SystemService {
return null;
}
userState = getOrCreateUserStateLocked(resolvedUserId);
+
+ // The user state might be updated via the same observer-set as the caller of this
+ // interface. If the caller is called back first the user state is not yet updated
+ // and the user gets and inconsistent view. Hence force an update.
+ userState.updateIfNeededLocked();
}
final long identity = Binder.clearCallingIdentity();
try {
diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java
index 0ab1657fcc52..77a47f81e62b 100644
--- a/services/print/java/com/android/server/print/RemotePrintService.java
+++ b/services/print/java/com/android/server/print/RemotePrintService.java
@@ -16,6 +16,9 @@
package com.android.server.print;
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -757,6 +760,33 @@ final class RemotePrintService implements DeathRecipient {
}
@Override
+ public void setProgress(@NonNull PrintJobId printJobId,
+ @FloatRange(from=0.0, to=1.0) float progress) {
+ RemotePrintService service = mWeakService.get();
+ if (service != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ service.mSpooler.setProgress(printJobId, progress);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
+ RemotePrintService service = mWeakService.get();
+ if (service != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ service.mSpooler.setStatus(printJobId, status);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
@SuppressWarnings({"rawtypes", "unchecked"})
public void onPrintersAdded(ParceledListSlice printers) {
RemotePrintService service = mWeakService.get();
diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java
index e5370f489e9b..c506b6f5f495 100644
--- a/services/print/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java
@@ -16,6 +16,9 @@
package com.android.server.print;
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -229,6 +232,61 @@ final class RemotePrintSpooler {
return false;
}
+ /**
+ * Set progress of a print job.
+ *
+ * @param printJobId The print job to update
+ * @param progress The new progress
+ */
+ public final void setProgress(@NonNull PrintJobId printJobId,
+ @FloatRange(from=0.0, to=1.0) float progress) {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ getRemoteInstanceLazy().setProgress(printJobId, progress);
+ } catch (RemoteException|TimeoutException re) {
+ Slog.e(LOG_TAG, "Error setting progress.", re);
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setProgress()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Set status of a print job.
+ *
+ * @param printJobId The print job to update
+ * @param status The new status
+ */
+ public final void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ getRemoteInstanceLazy().setStatus(printJobId, status);
+ } catch (RemoteException|TimeoutException re) {
+ Slog.e(LOG_TAG, "Error setting status.", re);
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setStatus()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
public final boolean setPrintJobTag(PrintJobId printJobId, String tag) {
throwIfCalledOnMainThread();
synchronized (mLock) {
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index 97e16da42732..27deb720c4b7 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -591,6 +591,12 @@ public class ConnectivityServiceTest extends AndroidTestCase {
public void setUp() throws Exception {
super.setUp();
+ // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
+ // http://b/25897652 .
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
mServiceContext = new MockContext(getContext());
mService = new WrappedConnectivityService(mServiceContext,
mock(INetworkManagementService.class),
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 66c7dbb16ede..b4bca3eb761c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -31,7 +31,8 @@ import java.util.List;
/** Test {@link UserManager} functionality. */
public class UserManagerTest extends AndroidTestCase {
-
+ private static final int REMOVE_CHECK_INTERVAL = 500;
+ private static final int REMOVE_TIMEOUT = 60 * 1000;
private UserManager mUserManager = null;
private final Object mUserLock = new Object();
private List<Integer> usersToRemove;
@@ -227,10 +228,16 @@ public class UserManagerTest extends AndroidTestCase {
private void removeUser(int userId) {
synchronized (mUserLock) {
mUserManager.removeUser(userId);
+ long time = System.currentTimeMillis();
while (mUserManager.getUserInfo(userId) != null) {
try {
- mUserLock.wait(500);
+ mUserLock.wait(REMOVE_CHECK_INTERVAL);
} catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ return;
+ }
+ if (System.currentTimeMillis() - time > REMOVE_TIMEOUT) {
+ fail("Timeout waiting for removeUser. userId = " + userId);
}
}
}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 41157565bfdd..17bd08c183c5 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -1805,6 +1805,13 @@ public abstract class Connection extends Conferenceable {
public void onReject(String replyMessage) {}
/**
+ * Notifies the Connection of a request to silence the ringer.
+ *
+ * @hide
+ */
+ public void onSilence() {}
+
+ /**
* Notifies this Connection whether the user wishes to proceed with the post-dial DTMF codes.
*/
public void onPostDialContinue(boolean proceed) {}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 62234958517d..b4a7ce0f56b1 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -102,6 +102,7 @@ public abstract class ConnectionService extends Service {
private static final int MSG_MERGE_CONFERENCE = 18;
private static final int MSG_SWAP_CONFERENCE = 19;
private static final int MSG_REJECT_WITH_MESSAGE = 20;
+ private static final int MSG_SILENCE = 21;
private static Connection sNullConnection;
@@ -177,6 +178,11 @@ public abstract class ConnectionService extends Service {
}
@Override
+ public void silence(String callId) {
+ mHandler.obtainMessage(MSG_SILENCE, callId).sendToTarget();
+ }
+
+ @Override
public void disconnect(String callId) {
mHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget();
}
@@ -319,6 +325,9 @@ public abstract class ConnectionService extends Service {
case MSG_DISCONNECT:
disconnect((String) msg.obj);
break;
+ case MSG_SILENCE:
+ silence((String) msg.obj);
+ break;
case MSG_HOLD:
hold((String) msg.obj);
break;
@@ -708,6 +717,11 @@ public abstract class ConnectionService extends Service {
findConnectionForAction(callId, "reject").onReject(rejectWithMessage);
}
+ private void silence(String callId) {
+ Log.d(this, "silence %s", callId);
+ findConnectionForAction(callId, "silence").onSilence();
+ }
+
private void disconnect(String callId) {
Log.d(this, "disconnect %s", callId);
if (mConnectionById.containsKey(callId)) {
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index dd253cfdfbe4..8a54addcf06e 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -54,6 +54,8 @@ oneway interface IConnectionService {
void disconnect(String callId);
+ void silence(String callId);
+
void hold(String callId);
void unhold(String callId);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 32b7383ff9a5..36407e195ac6 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -75,7 +75,7 @@ public class SubscriptionManager {
/**
* Indicates the caller wants the default phone id.
- * Used in SubscriptionController and PhoneBase but do we really need it???
+ * Used in SubscriptionController and Phone but do we really need it???
* @hide
*/
public static final int DEFAULT_PHONE_INDEX = Integer.MAX_VALUE;
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/Package.cpp b/tools/aapt/Package.cpp
index cb244eccfe21..641c34bd2dda 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -33,7 +33,7 @@ static const char* kNoCompressExt[] = {
".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
- ".amr", ".awb", ".wma", ".wmv"
+ ".amr", ".awb", ".wma", ".wmv", ".webm"
};
/* fwd decls, so I can write this downward */
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/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 17a658ee27cf..90e35d52788c 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -22,6 +22,7 @@
#include "compile/IdAssigner.h"
#include "compile/Png.h"
#include "compile/XmlIdCollector.h"
+#include "flatten/Archive.h"
#include "flatten/FileExportWriter.h"
#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
@@ -31,6 +32,7 @@
#include "xml/XmlDom.h"
#include "xml/XmlPullParser.h"
+#include <dirent.h>
#include <fstream>
#include <string>
@@ -90,7 +92,7 @@ static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
}
return ResourcePathData{
- Source{ path },
+ Source(path),
util::utf8ToUtf16(dirStr),
util::utf8ToUtf16(name),
extension.toString(),
@@ -101,25 +103,79 @@ static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
struct CompileOptions {
std::string outputPath;
+ Maybe<std::string> resDir;
Maybe<std::u16string> product;
bool verbose = false;
};
-static std::string buildIntermediateFilename(const std::string outDir,
- const ResourcePathData& data) {
+static std::string buildIntermediateFilename(const ResourcePathData& data) {
std::stringstream name;
name << data.resourceDir;
if (!data.configStr.empty()) {
name << "-" << data.configStr;
}
name << "_" << data.name << "." << data.extension << ".flat";
- std::string outPath = outDir;
- file::appendPath(&outPath, name.str());
- return outPath;
+ return name.str();
+}
+
+static bool isHidden(const StringPiece& filename) {
+ return util::stringStartsWith<char>(filename, ".");
+}
+
+/**
+ * Walks the res directory structure, looking for resource files.
+ */
+static bool loadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
+ std::vector<ResourcePathData>* outPathData) {
+ const std::string& rootDir = options.resDir.value();
+ std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()), closedir);
+ if (!d) {
+ context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ while (struct dirent* entry = readdir(d.get())) {
+ if (isHidden(entry->d_name)) {
+ continue;
+ }
+
+ std::string prefixPath = rootDir;
+ file::appendPath(&prefixPath, entry->d_name);
+
+ if (file::getFileType(prefixPath) != file::FileType::kDirectory) {
+ continue;
+ }
+
+ std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()), closedir);
+ if (!subDir) {
+ context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ while (struct dirent* leafEntry = readdir(subDir.get())) {
+ if (isHidden(leafEntry->d_name)) {
+ continue;
+ }
+
+ std::string fullPath = prefixPath;
+ file::appendPath(&fullPath, leafEntry->d_name);
+
+ std::string errStr;
+ Maybe<ResourcePathData> pathData = extractResourcePathData(fullPath, &errStr);
+ if (!pathData) {
+ context->getDiagnostics()->error(DiagMessage() << errStr);
+ return false;
+ }
+
+ outPathData->push_back(std::move(pathData.value()));
+ }
+ }
+ return true;
}
static bool compileTable(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, const std::string& outputPath) {
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
ResourceTable table;
{
std::ifstream fin(pathData.source.path, std::ifstream::binary);
@@ -150,6 +206,7 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options,
// Ensure we have the compilation package at least.
table.createPackage(context->getCompilationPackage());
+ // Assign an ID to any package that has resources.
for (auto& pkg : table.packages) {
if (!pkg->id) {
// If no package ID was set while parsing (public identifiers), auto assign an ID.
@@ -172,23 +229,24 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options,
return false;
}
- // Build the output filename.
- std::ofstream fout(outputPath, std::ofstream::binary);
- if (!fout) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
return false;
}
- // Write it to disk.
- if (!util::writeAll(fout, buffer)) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
}
- return true;
+
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
}
static bool compileXml(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, const std::string& outputPath) {
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
std::unique_ptr<xml::XmlResource> xmlRes;
@@ -214,7 +272,7 @@ static bool compileXml(IAaptContext* context, const CompileOptions& options,
return false;
}
- xmlRes->file.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+ xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
xmlRes->file.config = pathData.config;
xmlRes->file.source = pathData.source;
@@ -230,25 +288,27 @@ static bool compileXml(IAaptContext* context, const CompileOptions& options,
fileExportWriter.finish();
- std::ofstream fout(outputPath, std::ofstream::binary);
- if (!fout) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
return false;
}
- // Write it to disk.
- if (!util::writeAll(fout, buffer)) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
}
- return true;
+
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
}
static bool compilePng(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, const std::string& outputPath) {
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
BigBuffer buffer(4096);
ResourceFile resFile;
- resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+ resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
resFile.config = pathData.config;
resFile.source = pathData.source;
@@ -269,24 +329,27 @@ static bool compilePng(IAaptContext* context, const CompileOptions& options,
fileExportWriter.finish();
- std::ofstream fout(outputPath, std::ofstream::binary);
- if (!fout) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
return false;
}
- if (!util::writeAll(fout, buffer)) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
}
- return true;
+
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
}
static bool compileFile(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, const std::string& outputPath) {
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
BigBuffer buffer(256);
ResourceFile resFile;
- resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+ resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
resFile.config = pathData.config;
resFile.source = pathData.source;
@@ -299,9 +362,8 @@ static bool compileFile(IAaptContext* context, const CompileOptions& options,
return false;
}
- std::ofstream fout(outputPath, std::ofstream::binary);
- if (!fout) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
return false;
}
@@ -309,16 +371,17 @@ static bool compileFile(IAaptContext* context, const CompileOptions& options,
// the buffer the entire file.
fileExportWriter.getChunkHeader()->size =
util::hostToDevice32(buffer.size() + f.value().getDataLength());
- if (!util::writeAll(fout, buffer)) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
- }
- if (!fout.write((const char*) f.value().getDataPtr(), f.value().getDataLength())) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
+ if (writer->writeEntry(buffer)) {
+ if (writer->writeEntry(f.value().getDataPtr(), f.value().getDataLength())) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
}
- return true;
+
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
}
class CompileContext : public IAaptContext {
@@ -359,6 +422,7 @@ int compile(const std::vector<StringPiece>& args) {
Flags flags = Flags()
.requiredFlag("-o", "Output path", &options.outputPath)
.optionalFlag("--product", "Product type to compile", &product)
+ .optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
.optionalSwitch("-v", "Enables verbose logging", &options.verbose);
if (!flags.parse("aapt2 compile", args, &std::cerr)) {
return 1;
@@ -369,19 +433,42 @@ int compile(const std::vector<StringPiece>& args) {
}
CompileContext context;
+ std::unique_ptr<IArchiveWriter> archiveWriter;
std::vector<ResourcePathData> inputData;
- inputData.reserve(flags.getArgs().size());
+ if (options.resDir) {
+ if (!flags.getArgs().empty()) {
+ // Can't have both files and a resource directory.
+ context.getDiagnostics()->error(DiagMessage() << "files given but --dir specified");
+ flags.usage("aapt2 compile", &std::cerr);
+ return 1;
+ }
- // Collect data from the path for each input file.
- for (const std::string& arg : flags.getArgs()) {
- std::string errorStr;
- if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
- inputData.push_back(std::move(pathData.value()));
- } else {
- context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
+ if (!loadInputFilesFromDir(&context, options, &inputData)) {
return 1;
}
+
+ archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath);
+
+ } else {
+ inputData.reserve(flags.getArgs().size());
+
+ // Collect data from the path for each input file.
+ for (const std::string& arg : flags.getArgs()) {
+ std::string errorStr;
+ if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
+ inputData.push_back(std::move(pathData.value()));
+ } else {
+ context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
+ return 1;
+ }
+ }
+
+ archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(), options.outputPath);
+ }
+
+ if (!archiveWriter) {
+ return false;
}
bool error = false;
@@ -394,32 +481,34 @@ int compile(const std::vector<StringPiece>& args) {
// Overwrite the extension.
pathData.extension = "arsc";
- const std::string outputFilename = buildIntermediateFilename(
- options.outputPath, pathData);
- if (!compileTable(&context, options, pathData, outputFilename)) {
+ const std::string outputFilename = buildIntermediateFilename(pathData);
+ if (!compileTable(&context, options, pathData, archiveWriter.get(), outputFilename)) {
error = true;
}
} else {
- const std::string outputFilename = buildIntermediateFilename(options.outputPath,
- pathData);
+ const std::string outputFilename = buildIntermediateFilename(pathData);
if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
if (*type != ResourceType::kRaw) {
if (pathData.extension == "xml") {
- if (!compileXml(&context, options, pathData, outputFilename)) {
+ if (!compileXml(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
error = true;
}
} else if (pathData.extension == "png" || pathData.extension == "9.png") {
- if (!compilePng(&context, options, pathData, outputFilename)) {
+ if (!compilePng(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
error = true;
}
} else {
- if (!compileFile(&context, options, pathData, outputFilename)) {
+ if (!compileFile(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
error = true;
}
}
} else {
- if (!compileFile(&context, options, pathData, outputFilename)) {
+ if (!compileFile(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
error = true;
}
}
diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp
index 6db13b86fbfa..3a244c05efec 100644
--- a/tools/aapt2/flatten/Archive.cpp
+++ b/tools/aapt2/flatten/Archive.cpp
@@ -18,7 +18,7 @@
#include "util/Files.h"
#include "util/StringPiece.h"
-#include <fstream>
+#include <cstdio>
#include <memory>
#include <string>
#include <vector>
@@ -30,70 +30,85 @@ namespace {
struct DirectoryWriter : public IArchiveWriter {
std::string mOutDir;
- std::vector<std::unique_ptr<ArchiveEntry>> mEntries;
-
- explicit DirectoryWriter(const StringPiece& outDir) : mOutDir(outDir.toString()) {
+ std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
+
+ bool open(IDiagnostics* diag, const StringPiece& outDir) {
+ mOutDir = outDir.toString();
+ file::FileType type = file::getFileType(mOutDir);
+ if (type == file::FileType::kNonexistant) {
+ diag->error(DiagMessage() << "directory " << mOutDir << " does not exist");
+ return false;
+ } else if (type != file::FileType::kDirectory) {
+ diag->error(DiagMessage() << mOutDir << " is not a directory");
+ return false;
+ }
+ return true;
}
- ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
- const BigBuffer& buffer) override {
- std::string fullPath = mOutDir;
- file::appendPath(&fullPath, path);
- file::mkdirs(file::getStem(fullPath));
-
- std::ofstream fout(fullPath, std::ofstream::binary);
- if (!fout) {
- return nullptr;
- }
-
- if (!util::writeAll(fout, buffer)) {
- return nullptr;
+ bool startEntry(const StringPiece& path, uint32_t flags) override {
+ if (mFile) {
+ return false;
}
- mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, buffer.size()));
- return mEntries.back().get();
- }
-
- ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap,
- size_t offset, size_t len) override {
std::string fullPath = mOutDir;
file::appendPath(&fullPath, path);
file::mkdirs(file::getStem(fullPath));
- std::ofstream fout(fullPath, std::ofstream::binary);
- if (!fout) {
- return nullptr;
+ mFile = { fopen(fullPath.data(), "wb"), fclose };
+ if (!mFile) {
+ return false;
}
+ return true;
+ }
- if (!fout.write((const char*) fileMap->getDataPtr() + offset, len)) {
- return nullptr;
+ bool writeEntry(const BigBuffer& buffer) override {
+ if (!mFile) {
+ return false;
}
- mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, len));
- return mEntries.back().get();
+ for (const BigBuffer::Block& b : buffer) {
+ if (fwrite(b.buffer.get(), 1, b.size, mFile.get()) != b.size) {
+ mFile.reset(nullptr);
+ return false;
+ }
+ }
+ return true;
}
- virtual ~DirectoryWriter() {
+ bool writeEntry(const void* data, size_t len) override {
+ if (fwrite(data, 1, len, mFile.get()) != len) {
+ mFile.reset(nullptr);
+ return false;
+ }
+ return true;
+ }
+ bool finishEntry() override {
+ if (!mFile) {
+ return false;
+ }
+ mFile.reset(nullptr);
+ return true;
}
};
struct ZipFileWriter : public IArchiveWriter {
- FILE* mFile;
+ std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
std::unique_ptr<ZipWriter> mWriter;
- std::vector<std::unique_ptr<ArchiveEntry>> mEntries;
- explicit ZipFileWriter(const StringPiece& path) {
- mFile = fopen(path.data(), "w+b");
- if (mFile) {
- mWriter = util::make_unique<ZipWriter>(mFile);
+ bool open(IDiagnostics* diag, const StringPiece& path) {
+ mFile = { fopen(path.data(), "w+b"), fclose };
+ if (!mFile) {
+ diag->error(DiagMessage() << "failed to open " << path << ": " << strerror(errno));
+ return false;
}
+ mWriter = util::make_unique<ZipWriter>(mFile.get());
+ return true;
}
- ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
- const BigBuffer& buffer) override {
+ bool startEntry(const StringPiece& path, uint32_t flags) override {
if (!mWriter) {
- return nullptr;
+ return false;
}
size_t zipFlags = 0;
@@ -107,75 +122,63 @@ struct ZipFileWriter : public IArchiveWriter {
int32_t result = mWriter->StartEntry(path.data(), zipFlags);
if (result != 0) {
- return nullptr;
- }
-
- for (const BigBuffer::Block& b : buffer) {
- result = mWriter->WriteBytes(reinterpret_cast<const uint8_t*>(b.buffer.get()), b.size);
- if (result != 0) {
- return nullptr;
- }
- }
-
- result = mWriter->FinishEntry();
- if (result != 0) {
- return nullptr;
+ return false;
}
-
- mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, buffer.size()));
- return mEntries.back().get();
+ return true;
}
- ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap,
- size_t offset, size_t len) override {
- if (!mWriter) {
- return nullptr;
- }
-
- size_t zipFlags = 0;
- if (flags & ArchiveEntry::kCompress) {
- zipFlags |= ZipWriter::kCompress;
- }
-
- if (flags & ArchiveEntry::kAlign) {
- zipFlags |= ZipWriter::kAlign32;
- }
-
- int32_t result = mWriter->StartEntry(path.data(), zipFlags);
+ bool writeEntry(const void* data, size_t len) override {
+ int32_t result = mWriter->WriteBytes(data, len);
if (result != 0) {
- return nullptr;
+ return false;
}
+ return true;
+ }
- result = mWriter->WriteBytes((const char*) fileMap->getDataPtr() + offset, len);
- if (result != 0) {
- return nullptr;
+ bool writeEntry(const BigBuffer& buffer) override {
+ for (const BigBuffer::Block& b : buffer) {
+ int32_t result = mWriter->WriteBytes(b.buffer.get(), b.size);
+ if (result != 0) {
+ return false;
+ }
}
+ return true;
+ }
- result = mWriter->FinishEntry();
+ bool finishEntry() override {
+ int32_t result = mWriter->FinishEntry();
if (result != 0) {
- return nullptr;
+ return false;
}
-
- mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, len));
- return mEntries.back().get();
+ return true;
}
virtual ~ZipFileWriter() {
if (mWriter) {
mWriter->Finish();
- fclose(mFile);
}
}
};
} // namespace
-std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path) {
- return util::make_unique<DirectoryWriter>(path);
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path) {
+
+ std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
+ if (!writer->open(diag, path)) {
+ return {};
+ }
+ return std::move(writer);
}
-std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path) {
- return util::make_unique<ZipFileWriter>(path);
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path) {
+ std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
+ if (!writer->open(diag, path)) {
+ return {};
+ }
+ return std::move(writer);
}
} // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
index c4ddeb3163c0..6da1d2ac5620 100644
--- a/tools/aapt2/flatten/Archive.h
+++ b/tools/aapt2/flatten/Archive.h
@@ -17,6 +17,7 @@
#ifndef AAPT_FLATTEN_ARCHIVE_H
#define AAPT_FLATTEN_ARCHIVE_H
+#include "Diagnostics.h"
#include "util/BigBuffer.h"
#include "util/Files.h"
#include "util/StringPiece.h"
@@ -42,15 +43,17 @@ struct ArchiveEntry {
struct IArchiveWriter {
virtual ~IArchiveWriter() = default;
- virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
- const BigBuffer& buffer) = 0;
- virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
- android::FileMap* fileMap, size_t offset, size_t len) = 0;
+ virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0;
+ virtual bool writeEntry(const BigBuffer& buffer) = 0;
+ virtual bool writeEntry(const void* data, size_t len) = 0;
+ virtual bool finishEntry() = 0;
};
-std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path);
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path);
-std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path);
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path);
} // namespace aapt
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
new file mode 100644
index 000000000000..9081c55fc6e1
--- /dev/null
+++ b/tools/aapt2/io/Data.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_IO_DATA_H
+#define AAPT_IO_DATA_H
+
+#include <utils/FileMap.h>
+
+#include <memory>
+
+namespace aapt {
+namespace io {
+
+/**
+ * Interface for a block of contiguous memory. An instance of this interface owns the data.
+ */
+class IData {
+public:
+ virtual ~IData() = default;
+
+ virtual const void* data() const = 0;
+ virtual size_t size() const = 0;
+};
+
+/**
+ * Implementation of IData that exposes a memory mapped file. The mmapped file is owned by this
+ * object.
+ */
+class MmappedData : public IData {
+public:
+ explicit MmappedData(android::FileMap&& map) : mMap(std::forward<android::FileMap>(map)) {
+ }
+
+ const void* data() const override {
+ return mMap.getDataPtr();
+ }
+
+ size_t size() const override {
+ return mMap.getDataLength();
+ }
+
+private:
+ android::FileMap mMap;
+};
+
+/**
+ * Implementation of IData that exposes a block of memory that was malloc'ed (new'ed). The
+ * memory is owned by this object.
+ */
+class MallocData : public IData {
+public:
+ MallocData(std::unique_ptr<const uint8_t[]> data, size_t size) :
+ mData(std::move(data)), mSize(size) {
+ }
+
+ const void* data() const override {
+ return mData.get();
+ }
+
+ size_t size() const override {
+ return mSize;
+ }
+
+private:
+ std::unique_ptr<const uint8_t[]> mData;
+ size_t mSize;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_DATA_H */
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
new file mode 100644
index 000000000000..9fca3980d964
--- /dev/null
+++ b/tools/aapt2/io/File.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_IO_FILE_H
+#define AAPT_IO_FILE_H
+
+#include "Source.h"
+#include "io/Data.h"
+
+#include <memory>
+#include <vector>
+
+namespace aapt {
+namespace io {
+
+/**
+ * Interface for a file, which could be a real file on the file system, or a file inside
+ * a ZIP archive.
+ */
+class IFile {
+public:
+ virtual ~IFile() = default;
+
+ /**
+ * Open the file and return it as a block of contiguous memory. How this occurs is
+ * implementation dependent. For example, if this is a file on the file system, it may
+ * simply mmap the contents. If this file represents a compressed file in a ZIP archive,
+ * it may need to inflate it to memory, incurring a copy.
+ *
+ * Returns nullptr on failure.
+ */
+ virtual std::unique_ptr<IData> openAsData() = 0;
+
+ /**
+ * Returns the source of this file. This is for presentation to the user and may not be a
+ * valid file system path (for example, it may contain a '@' sign to separate the files within
+ * a ZIP archive from the path to the containing ZIP archive.
+ */
+ virtual const Source& getSource() const = 0;
+};
+
+/**
+ * Interface for a collection of files, all of which share a common source. That source may
+ * simply be the filesystem, or a ZIP archive.
+ */
+class IFileCollection {
+public:
+ virtual ~IFileCollection() = default;
+
+ using const_iterator = std::vector<std::unique_ptr<IFile>>::const_iterator;
+
+ virtual const_iterator begin() const = 0;
+ virtual const_iterator end() const = 0;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_FILE_H */
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
new file mode 100644
index 000000000000..5dbefcc0f687
--- /dev/null
+++ b/tools/aapt2/io/FileSystem.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_IO_FILESYSTEM_H
+#define AAPT_IO_FILESYSTEM_H
+
+#include "io/File.h"
+#include "util/Files.h"
+
+namespace aapt {
+namespace io {
+
+/**
+ * A regular file from the file system. Uses mmap to open the data.
+ */
+class RegularFile : public IFile {
+public:
+ RegularFile(const Source& source) : mSource(source) {
+ }
+
+ std::unique_ptr<IData> openAsData() override {
+ android::FileMap map;
+ if (Maybe<android::FileMap> map = file::mmapPath(mSource.path, nullptr)) {
+ return util::make_unique<MmappedData>(std::move(map.value()));
+ }
+ return {};
+ }
+
+ const Source& getSource() const override {
+ return mSource;
+ }
+
+private:
+ Source mSource;
+};
+
+/**
+ * An IFileCollection representing the file system.
+ */
+class FileCollection : public IFileCollection {
+public:
+ /**
+ * Adds a file located at path. Returns the IFile representation of that file.
+ */
+ IFile* insertFile(const StringPiece& path) {
+ mFiles.push_back(util::make_unique<RegularFile>(Source(path)));
+ return mFiles.back().get();
+ }
+
+ const_iterator begin() const override {
+ return mFiles.begin();
+ }
+
+ const_iterator end() const override {
+ return mFiles.end();
+ }
+
+private:
+ std::vector<std::unique_ptr<IFile>> mFiles;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif // AAPT_IO_FILESYSTEM_H
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
new file mode 100644
index 000000000000..98afc498708f
--- /dev/null
+++ b/tools/aapt2/io/ZipArchive.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_IO_ZIPARCHIVE_H
+#define AAPT_IO_ZIPARCHIVE_H
+
+#include "io/File.h"
+#include "util/StringPiece.h"
+
+#include <utils/FileMap.h>
+#include <ziparchive/zip_archive.h>
+
+namespace aapt {
+namespace io {
+
+/**
+ * An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed
+ * and copied into memory when opened. Otherwise it is mmapped from the ZIP archive.
+ */
+class ZipFile : public IFile {
+public:
+ ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source) :
+ mZipHandle(handle), mZipEntry(entry), mSource(source) {
+ }
+
+ std::unique_ptr<IData> openAsData() override {
+ if (mZipEntry.method == kCompressStored) {
+ int fd = GetFileDescriptor(mZipHandle);
+
+ android::FileMap fileMap;
+ bool result = fileMap.create(nullptr, fd, mZipEntry.offset,
+ mZipEntry.uncompressed_length, true);
+ if (!result) {
+ return {};
+ }
+ return util::make_unique<MmappedData>(std::move(fileMap));
+
+ } else {
+ std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(
+ new uint8_t[mZipEntry.uncompressed_length]);
+ int32_t result = ExtractToMemory(mZipHandle, &mZipEntry, data.get(),
+ static_cast<uint32_t>(mZipEntry.uncompressed_length));
+ if (result != 0) {
+ return {};
+ }
+ return util::make_unique<MallocData>(std::move(data), mZipEntry.uncompressed_length);
+ }
+ }
+
+ const Source& getSource() const override {
+ return mSource;
+ }
+
+private:
+ ZipArchiveHandle mZipHandle;
+ ZipEntry mZipEntry;
+ Source mSource;
+};
+
+/**
+ * An IFileCollection that represents a ZIP archive and the entries within it.
+ */
+class ZipFileCollection : public IFileCollection {
+public:
+ static std::unique_ptr<ZipFileCollection> create(const StringPiece& path,
+ std::string* outError) {
+ std::unique_ptr<ZipFileCollection> collection = std::unique_ptr<ZipFileCollection>(
+ new ZipFileCollection());
+
+ int32_t result = OpenArchive(path.data(), &collection->mHandle);
+ if (result != 0) {
+ if (outError) *outError = ErrorCodeString(result);
+ return {};
+ }
+
+ ZipString suffix(".flat");
+ void* cookie = nullptr;
+ result = StartIteration(collection->mHandle, &cookie, nullptr, &suffix);
+ if (result != 0) {
+ if (outError) *outError = ErrorCodeString(result);
+ return {};
+ }
+
+ using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>;
+ IterationEnder iterationEnder(cookie, EndIteration);
+
+ ZipString zipEntryName;
+ ZipEntry zipData;
+ while ((result = Next(cookie, &zipData, &zipEntryName)) == 0) {
+ std::string nestedPath = path.toString();
+ nestedPath += "@" + std::string(reinterpret_cast<const char*>(zipEntryName.name),
+ zipEntryName.name_length);
+ collection->mFiles.push_back(util::make_unique<ZipFile>(collection->mHandle,
+ zipData,
+ Source(nestedPath)));
+ }
+
+ if (result != -1) {
+ if (outError) *outError = ErrorCodeString(result);
+ return {};
+ }
+ return collection;
+ }
+
+ const_iterator begin() const override {
+ return mFiles.begin();
+ }
+
+ const_iterator end() const override {
+ return mFiles.end();
+ }
+
+ ~ZipFileCollection() override {
+ if (mHandle) {
+ CloseArchive(mHandle);
+ }
+ }
+
+private:
+ ZipFileCollection() : mHandle(nullptr) {
+ }
+
+ ZipArchiveHandle mHandle;
+ std::vector<std::unique_ptr<IFile>> mFiles;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_ZIPARCHIVE_H */
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 9850ae5cf57b..33d9272b39fd 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -22,6 +22,8 @@
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
+#include "io/FileSystem.h"
+#include "io/ZipArchive.h"
#include "java/JavaClassGenerator.h"
#include "java/ManifestClassGenerator.h"
#include "java/ProguardRules.h"
@@ -39,7 +41,6 @@
#include <fstream>
#include <sys/stat.h>
-#include <utils/FileMap.h>
#include <vector>
namespace aapt {
@@ -92,7 +93,15 @@ struct LinkContext : public IAaptContext {
class LinkCommand {
public:
LinkCommand(const LinkOptions& options) :
- mOptions(options), mContext(), mFinalTable() {
+ mOptions(options), mContext(), mFinalTable(), mFileCollection(nullptr) {
+ std::unique_ptr<io::FileCollection> fileCollection =
+ util::make_unique<io::FileCollection>();
+
+ // Get a pointer to the FileCollection for convenience, but it will be owned by the vector.
+ mFileCollection = fileCollection.get();
+
+ // Move it to the collection.
+ mCollections.push_back(std::move(fileCollection));
}
std::string buildResourceFileName(const ResourceFile& resFile) {
@@ -136,20 +145,9 @@ public:
return builder.build();
}
- /**
- * Loads the resource table (not inside an apk) at the given path.
- */
- std::unique_ptr<ResourceTable> loadTable(const std::string& input) {
- std::string errorStr;
- Maybe<android::FileMap> map = file::mmapPath(input, &errorStr);
- if (!map) {
- mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
- return {};
- }
-
+ std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) {
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
- BinaryResourceParser parser(&mContext, table.get(), Source(input),
- map.value().getDataPtr(), map.value().getDataLength());
+ BinaryResourceParser parser(&mContext, table.get(), source, data, len);
if (!parser.parse()) {
return {};
}
@@ -159,90 +157,79 @@ public:
/**
* Inflates an XML file from the source path.
*/
- std::unique_ptr<xml::XmlResource> loadXml(const std::string& path) {
+ static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
std::ifstream fin(path, std::ifstream::binary);
if (!fin) {
- mContext.getDiagnostics()->error(DiagMessage(path) << strerror(errno));
+ diag->error(DiagMessage(path) << strerror(errno));
return {};
}
- return xml::inflate(&fin, mContext.getDiagnostics(), Source(path));
+ return xml::inflate(&fin, diag, Source(path));
}
- /**
- * Inflates a binary XML file from the source path.
- */
- std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) {
- // Read header for symbol info and export info.
+ static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(
+ const Source& source,
+ const void* data, size_t len,
+ IDiagnostics* diag) {
std::string errorStr;
- Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
- if (!maybeF) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
- return {};
- }
-
- ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
- maybeF.value().getDataLength(), &errorStr);
+ ssize_t offset = getWrappedDataOffset(data, len, &errorStr);
if (offset < 0) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+ diag->error(DiagMessage(source) << errorStr);
return {};
}
std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(
- (const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset,
- maybeF.value().getDataLength() - offset,
- mContext.getDiagnostics(), Source(path));
+ reinterpret_cast<const uint8_t*>(data) + static_cast<size_t>(offset),
+ len - static_cast<size_t>(offset),
+ diag,
+ source);
if (!xmlRes) {
return {};
}
return xmlRes;
}
- Maybe<ResourceFile> loadFileExportHeader(const std::string& path) {
- // Read header for symbol info and export info.
+ static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
+ const void* data, size_t len,
+ IDiagnostics* diag) {
+ std::unique_ptr<ResourceFile> resFile = util::make_unique<ResourceFile>();
std::string errorStr;
- Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
- if (!maybeF) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
- return {};
- }
-
- ResourceFile resFile;
- ssize_t offset = unwrapFileExportHeader(maybeF.value().getDataPtr(),
- maybeF.value().getDataLength(),
- &resFile, &errorStr);
+ ssize_t offset = unwrapFileExportHeader(data, len, resFile.get(), &errorStr);
if (offset < 0) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+ diag->error(DiagMessage(source) << errorStr);
return {};
}
- return std::move(resFile);
+ return resFile;
}
- bool copyFileToArchive(const std::string& path, const std::string& outPath, uint32_t flags,
+ bool copyFileToArchive(io::IFile* file, const std::string& outPath, uint32_t flags,
IArchiveWriter* writer) {
- std::string errorStr;
- Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
- if (!maybeF) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext.getDiagnostics()->error(DiagMessage(file->getSource())
+ << "failed to open file");
return false;
}
- ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
- maybeF.value().getDataLength(),
- &errorStr);
+ std::string errorStr;
+ ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr);
if (offset < 0) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+ mContext.getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr);
return false;
}
- ArchiveEntry* entry = writer->writeEntry(outPath, flags, &maybeF.value(),
- offset, maybeF.value().getDataLength() - offset);
- if (!entry) {
- mContext.getDiagnostics()->error(
- DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
- return false;
+ if (writer->startEntry(outPath, flags)) {
+ if (writer->writeEntry(reinterpret_cast<const uint8_t*>(data->data()) + offset,
+ data->size() - static_cast<size_t>(offset))) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
}
- return true;
+
+ mContext.getDiagnostics()->error(
+ DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
+ return false;
}
Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
@@ -285,9 +272,9 @@ public:
std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
if (mOptions.outputToDirectory) {
- return createDirectoryArchiveWriter(mOptions.outputPath);
+ return createDirectoryArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
} else {
- return createZipFileArchiveWriter(mOptions.outputPath);
+ return createZipFileArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
}
}
@@ -300,13 +287,17 @@ public:
return false;
}
- ArchiveEntry* entry = writer->writeEntry("resources.arsc", ArchiveEntry::kAlign, buffer);
- if (!entry) {
- mContext.getDiagnostics()->error(
- DiagMessage() << "failed to write resources.arsc to archive");
- return false;
+ if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
}
- return true;
+
+ mContext.getDiagnostics()->error(
+ DiagMessage() << "failed to write resources.arsc to archive");
+ return false;
}
bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
@@ -320,13 +311,17 @@ public:
return false;
}
- ArchiveEntry* entry = writer->writeEntry(path, ArchiveEntry::kCompress, buffer);
- if (!entry) {
- mContext.getDiagnostics()->error(
- DiagMessage() << "failed to write " << path << " to archive");
- return false;
+
+ if (writer->startEntry(path, ArchiveEntry::kCompress)) {
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
}
- return true;
+ mContext.getDiagnostics()->error(
+ DiagMessage() << "failed to write " << path << " to archive");
+ return false;
}
bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
@@ -412,34 +407,44 @@ public:
return true;
}
- bool mergeResourceTable(const std::string& input, bool override) {
+ bool mergeResourceTable(io::IFile* file, bool override) {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage() << "linking " << input);
+ mContext.getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
+ }
+
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext.getDiagnostics()->error(DiagMessage(file->getSource())
+ << "failed to open file");
+ return false;
}
- std::unique_ptr<ResourceTable> table = loadTable(input);
+ std::unique_ptr<ResourceTable> table = loadTable(file->getSource(), data->data(),
+ data->size());
if (!table) {
return false;
}
- if (!mTableMerger->merge(Source(input), table.get(), override)) {
+ if (!mTableMerger->merge(file->getSource(), table.get(), override)) {
return false;
}
return true;
}
- bool mergeCompiledFile(const std::string& input, ResourceFile&& file, bool override) {
- if (file.name.package.empty()) {
- file.name.package = mContext.getCompilationPackage().toString();
+ bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool override) {
+ // Apply the package name used for this compilation phase if none was specified.
+ if (fileDesc->name.package.empty()) {
+ fileDesc->name.package = mContext.getCompilationPackage().toString();
}
- ResourceNameRef resName = file.name;
-
- Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(file.name);
+ // Mangle the name if necessary.
+ ResourceNameRef resName = fileDesc->name;
+ Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(fileDesc->name);
if (mangledName) {
resName = mangledName.value();
}
+ // If we are overriding resources, we supply a custom resolver function.
std::function<int(Value*,Value*)> resolver;
if (override) {
resolver = [](Value* a, Value* b) -> int {
@@ -456,14 +461,14 @@ public:
}
// Add this file to the table.
- if (!mFinalTable.addFileReference(resName, file.config, file.source,
- util::utf8ToUtf16(buildResourceFileName(file)),
+ if (!mFinalTable.addFileReference(resName, fileDesc->config, fileDesc->source,
+ util::utf8ToUtf16(buildResourceFileName(*fileDesc)),
resolver, mContext.getDiagnostics())) {
return false;
}
// Add the exports of this file to the table.
- for (SourcedResourceName& exportedSymbol : file.exportedSymbols) {
+ for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
if (exportedSymbol.name.package.empty()) {
exportedSymbol.name.package = mContext.getCompilationPackage().toString();
}
@@ -477,32 +482,78 @@ public:
}
std::unique_ptr<Id> id = util::make_unique<Id>();
- id->setSource(file.source.withLine(exportedSymbol.line));
+ id->setSource(fileDesc->source.withLine(exportedSymbol.line));
bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id),
- mContext.getDiagnostics());
+ mContext.getDiagnostics());
if (!result) {
return false;
}
}
- mFilesToProcess.insert(FileToProcess{ std::move(file), Source(input) });
+ // Now add this file for later processing. Once the table is assigned IDs, we can compile
+ // this file.
+ mFilesToProcess.insert(FileToProcess{ std::move(fileDesc), file });
return true;
}
- bool processFile(const std::string& input, bool override) {
- if (util::stringEndsWith<char>(input, ".apk")) {
- return mergeStaticLibrary(input);
- } else if (util::stringEndsWith<char>(input, ".arsc.flat")) {
- return mergeResourceTable(input, override);
- } else if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) {
- return mergeCompiledFile(input, std::move(maybeF.value()), override);
+ /**
+ * Creates an io::IFileCollection from the ZIP archive and processes the files within.
+ */
+ bool mergeArchive(const std::string& input, bool override) {
+ std::string errorStr;
+ std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
+ input, &errorStr);
+ if (!collection) {
+ mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
+ return false;
+ }
+
+ bool error = false;
+ for (const std::unique_ptr<io::IFile>& file : *collection) {
+ if (!processFile(file.get(), override)) {
+ error = true;
+ }
+ }
+
+ // Make sure to move the collection into the set of IFileCollections.
+ mCollections.push_back(std::move(collection));
+ return !error;
+ }
+
+ bool processFile(const std::string& path, bool override) {
+ if (util::stringEndsWith<char>(path, ".flata")) {
+ return mergeArchive(path, override);
+ }
+
+ io::IFile* file = mFileCollection->insertFile(path);
+ return processFile(file, override);
+ }
+
+ bool processFile(io::IFile* file, bool override) {
+ const Source& src = file->getSource();
+ if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
+ return mergeResourceTable(file, override);
+ } else {
+ // Try opening the file and looking for an Export header.
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext.getDiagnostics()->error(DiagMessage(src) << "failed to open");
+ return false;
+ }
+
+ std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
+ src, data->data(), data->size(), mContext.getDiagnostics());
+ if (resourceFile) {
+ return mergeCompiledFile(file, std::move(resourceFile), override);
+ }
}
return false;
}
int run(const std::vector<std::string>& inputFiles) {
// Load the AndroidManifest.xml
- std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath);
+ std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
+ mContext.getDiagnostics());
if (!manifestXml) {
return 1;
}
@@ -648,20 +699,30 @@ public:
}
for (const FileToProcess& file : mFilesToProcess) {
- if (file.file.name.type != ResourceType::kRaw &&
- util::stringEndsWith<char>(file.source.path, ".xml.flat")) {
+ const StringPiece path = file.file->getSource().path;
+
+ if (file.fileExport->name.type != ResourceType::kRaw &&
+ util::stringEndsWith<char>(path, ".xml.flat")) {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage()
- << "linking " << file.source.path);
+ mContext.getDiagnostics()->note(DiagMessage() << "linking " << path);
+ }
+
+ std::unique_ptr<io::IData> data = file.file->openAsData();
+ if (!data) {
+ mContext.getDiagnostics()->error(DiagMessage(file.file->getSource())
+ << "failed to open file");
+ return 1;
}
std::unique_ptr<xml::XmlResource> xmlRes = loadBinaryXmlSkipFileExport(
- file.source.path);
+ file.file->getSource(), data->data(), data->size(),
+ mContext.getDiagnostics());
if (!xmlRes) {
return 1;
}
- xmlRes->file = std::move(file.file);
+ // Move the file description over.
+ xmlRes->file = std::move(*file.fileExport);
XmlReferenceLinker xmlLinker;
if (xmlLinker.consume(&mContext, xmlRes.get())) {
@@ -689,12 +750,13 @@ public:
xmlRes->file.config,
sdkLevel)) {
xmlRes->file.config.sdkVersion = sdkLevel;
- if (!mFinalTable.addFileReference(xmlRes->file.name,
- xmlRes->file.config,
- xmlRes->file.source,
- util::utf8ToUtf16(
- buildResourceFileName(xmlRes->file)),
- mContext.getDiagnostics())) {
+ bool added = mFinalTable.addFileReference(
+ xmlRes->file.name,
+ xmlRes->file.config,
+ xmlRes->file.source,
+ util::utf8ToUtf16(buildResourceFileName(xmlRes->file)),
+ mContext.getDiagnostics());
+ if (!added) {
error = true;
continue;
}
@@ -712,11 +774,10 @@ public:
}
} else {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage() << "copying "
- << file.source.path);
+ mContext.getDiagnostics()->note(DiagMessage() << "copying " << path);
}
- if (!copyFileToArchive(file.source.path, buildResourceFileName(file.file), 0,
+ if (!copyFileToArchive(file.file, buildResourceFileName(*file.fileExport), 0,
archiveWriter.get())) {
error = true;
}
@@ -802,14 +863,18 @@ private:
ResourceTable mFinalTable;
std::unique_ptr<TableMerger> mTableMerger;
+ io::FileCollection* mFileCollection;
+ std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
+
struct FileToProcess {
- ResourceFile file;
- Source source;
+ std::unique_ptr<ResourceFile> fileExport;
+ io::IFile* file;
};
struct FileToProcessComparator {
bool operator()(const FileToProcess& a, const FileToProcess& b) {
- return std::tie(a.file.name, a.file.config) < std::tie(b.file.name, b.file.config);
+ return std::tie(a.fileExport->name, a.fileExport->config) <
+ std::tie(b.fileExport->name, b.fileExport->config);
}
};
diff --git a/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java b/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java
deleted file mode 100644
index 78aedc5a3102..000000000000
--- a/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java
+++ /dev/null
@@ -1,52 +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 android.animation;
-
-/**
- * A fake implementation of Animator which doesn't do anything.
- */
-public class FakeAnimator extends Animator {
- @Override
- public long getStartDelay() {
- return 0;
- }
-
- @Override
- public void setStartDelay(long startDelay) {
-
- }
-
- @Override
- public Animator setDuration(long duration) {
- return this;
- }
-
- @Override
- public long getDuration() {
- return 0;
- }
-
- @Override
- public void setInterpolator(TimeInterpolator value) {
-
- }
-
- @Override
- public boolean isRunning() {
- return false;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
index 4603b6362b0e..54021c9f988d 100644
--- a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
@@ -16,9 +16,16 @@
package android.animation;
+import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Delegate implementing the native methods of android.animation.PropertyValuesHolder
*
@@ -29,81 +36,161 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
* around to map int to instance of the delegate.
*
* The main goal of this class' methods are to provide a native way to access setters and getters
- * on some object. In this case we want to default to using Java reflection instead so the native
- * methods do nothing.
+ * on some object. We override these methods to use reflection since the original reflection
+ * implementation of the PropertyValuesHolder won't be able to access protected methods.
*
*/
-/*package*/ class PropertyValuesHolder_Delegate {
+/*package*/
+@SuppressWarnings("unused")
+class PropertyValuesHolder_Delegate {
+ // This code is copied from android.animation.PropertyValuesHolder and must be kept in sync
+ // We try several different types when searching for appropriate setter/getter functions.
+ // The caller may have supplied values in a type that does not match the setter/getter
+ // functions (such as the integers 0 and 1 to represent floating point values for alpha).
+ // Also, the use of generics in constructors means that we end up with the Object versions
+ // of primitive types (Float vs. float). But most likely, the setter/getter functions
+ // will take primitive types instead.
+ // So we supply an ordered array of other types to try before giving up.
+ private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class,
+ Double.class, Integer.class};
+ private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class,
+ Float.class, Double.class};
+
+ private static final Object sMethodIndexLock = new Object();
+ private static final Map<Long, Method> ID_TO_METHOD = new HashMap<Long, Method>();
+ private static final Map<String, Long> METHOD_NAME_TO_ID = new HashMap<String, Long>();
+ private static long sNextId = 1;
+
+ private static long registerMethod(Class<?> targetClass, String methodName, Class[] types,
+ int nArgs) {
+ // Encode the number of arguments in the method name
+ String methodIndexName = String.format("%1$s#%2$d", methodName, nArgs);
+ synchronized (sMethodIndexLock) {
+ Long methodId = METHOD_NAME_TO_ID.get(methodIndexName);
+
+ if (methodId != null) {
+ // The method was already registered
+ return methodId;
+ }
+
+ Class[] args = new Class[nArgs];
+ Method method = null;
+ for (Class typeVariant : types) {
+ for (int i = 0; i < nArgs; i++) {
+ args[i] = typeVariant;
+ }
+ try {
+ method = targetClass.getDeclaredMethod(methodName, args);
+ } catch (NoSuchMethodException ignore) {
+ }
+ }
+
+ if (method != null) {
+ methodId = sNextId++;
+ ID_TO_METHOD.put(methodId, method);
+ METHOD_NAME_TO_ID.put(methodIndexName, methodId);
+
+ return methodId;
+ }
+ }
+
+ // Method not found
+ return 0;
+ }
+
+ private static void callMethod(Object target, long methodID, Object... args) {
+ Method method = ID_TO_METHOD.get(methodID);
+ assert method != null;
+
+ try {
+ method.setAccessible(true);
+ method.invoke(target, args);
+ } catch (IllegalAccessException e) {
+ Bridge.getLog().error(null, "Unable to update property during animation", e, null);
+ } catch (InvocationTargetException e) {
+ Bridge.getLog().error(null, "Unable to update property during animation", e, null);
+ }
+ }
@LayoutlibDelegate
/*package*/ static long nGetIntMethod(Class<?> targetClass, String methodName) {
- // return 0 to force PropertyValuesHolder to use Java reflection.
- return 0;
+ return nGetMultipleIntMethod(targetClass, methodName, 1);
}
@LayoutlibDelegate
/*package*/ static long nGetFloatMethod(Class<?> targetClass, String methodName) {
- // return 0 to force PropertyValuesHolder to use Java reflection.
- return 0;
+ return nGetMultipleFloatMethod(targetClass, methodName, 1);
}
@LayoutlibDelegate
/*package*/ static long nGetMultipleIntMethod(Class<?> targetClass, String methodName,
int numParams) {
- // TODO: return the right thing.
- return 0;
+ return registerMethod(targetClass, methodName, INTEGER_VARIANTS, numParams);
}
@LayoutlibDelegate
/*package*/ static long nGetMultipleFloatMethod(Class<?> targetClass, String methodName,
int numParams) {
- // TODO: return the right thing.
- return 0;
+ return registerMethod(targetClass, methodName, FLOAT_VARIANTS, numParams);
}
@LayoutlibDelegate
/*package*/ static void nCallIntMethod(Object target, long methodID, int arg) {
- // do nothing
+ callMethod(target, methodID, arg);
}
@LayoutlibDelegate
/*package*/ static void nCallFloatMethod(Object target, long methodID, float arg) {
- // do nothing
+ callMethod(target, methodID, arg);
}
@LayoutlibDelegate
/*package*/ static void nCallTwoIntMethod(Object target, long methodID, int arg1,
int arg2) {
- // do nothing
+ callMethod(target, methodID, arg1, arg2);
}
@LayoutlibDelegate
/*package*/ static void nCallFourIntMethod(Object target, long methodID, int arg1,
int arg2, int arg3, int arg4) {
- // do nothing
+ callMethod(target, methodID, arg1, arg2, arg3, arg4);
}
@LayoutlibDelegate
/*package*/ static void nCallMultipleIntMethod(Object target, long methodID,
int[] args) {
- // do nothing
+ assert args != null;
+
+ // Box parameters
+ Object[] params = new Object[args.length];
+ for (int i = 0; i < args.length; i++) {
+ params[i] = args;
+ }
+ callMethod(target, methodID, params);
}
@LayoutlibDelegate
/*package*/ static void nCallTwoFloatMethod(Object target, long methodID, float arg1,
float arg2) {
- // do nothing
+ callMethod(target, methodID, arg1, arg2);
}
@LayoutlibDelegate
/*package*/ static void nCallFourFloatMethod(Object target, long methodID, float arg1,
float arg2, float arg3, float arg4) {
- // do nothing
+ callMethod(target, methodID, arg1, arg2, arg3, arg4);
}
@LayoutlibDelegate
/*package*/ static void nCallMultipleFloatMethod(Object target, long methodID,
float[] args) {
- // do nothing
+ assert args != null;
+
+ // Box parameters
+ Object[] params = new Object[args.length];
+ for (int i = 0; i < args.length; i++) {
+ params[i] = args;
+ }
+ callMethod(target, methodID, params);
}
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
index dd2978f5c414..3c71233b1df5 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
@@ -44,7 +44,7 @@ public final class PathMeasure_Delegate {
// ---- delegate data ----
// This governs how accurate the approximation of the Path is.
- private static final float PRECISION = 0.002f;
+ private static final float PRECISION = 0.0002f;
/**
* Array containing the path points components. There are three components for each point:
diff --git a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
index 5f0d98b35431..9677aaf5d07a 100644
--- a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
@@ -18,6 +18,7 @@ package android.os;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.android.tools.layoutlib.java.System_Delegate;
/**
* Delegate implementing the native methods of android.os.SystemClock
@@ -30,9 +31,6 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
*
*/
public class SystemClock_Delegate {
- private static long sBootTime = System.currentTimeMillis();
- private static long sBootTimeNano = System.nanoTime();
-
/**
* Returns milliseconds since boot, not counting time spent in deep sleep.
* <b>Note:</b> This value may get reset occasionally (before it would
@@ -42,7 +40,7 @@ public class SystemClock_Delegate {
*/
@LayoutlibDelegate
/*package*/ static long uptimeMillis() {
- return System.currentTimeMillis() - sBootTime;
+ return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis();
}
/**
@@ -52,7 +50,7 @@ public class SystemClock_Delegate {
*/
@LayoutlibDelegate
/*package*/ static long elapsedRealtime() {
- return System.currentTimeMillis() - sBootTime;
+ return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis();
}
/**
@@ -62,7 +60,7 @@ public class SystemClock_Delegate {
*/
@LayoutlibDelegate
/*package*/ static long elapsedRealtimeNanos() {
- return System.nanoTime() - sBootTimeNano;
+ return System_Delegate.nanoTime() - System_Delegate.bootTime();
}
/**
@@ -72,7 +70,7 @@ public class SystemClock_Delegate {
*/
@LayoutlibDelegate
/*package*/ static long currentThreadTimeMillis() {
- return System.currentTimeMillis();
+ return System_Delegate.currentTimeMillis();
}
/**
@@ -84,7 +82,7 @@ public class SystemClock_Delegate {
*/
@LayoutlibDelegate
/*package*/ static long currentThreadTimeMicro() {
- return System.currentTimeMillis() * 1000;
+ return System_Delegate.currentTimeMillis() * 1000;
}
/**
diff --git a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
index f75ee5030674..01af669e39d3 100644
--- a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
@@ -17,6 +17,8 @@ package android.view;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import java.util.concurrent.atomic.AtomicReference;
+
/**
* Delegate used to provide new implementation of a select few methods of {@link Choreographer}
*
@@ -25,9 +27,41 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
*
*/
public class Choreographer_Delegate {
+ static final AtomicReference<Choreographer> mInstance = new AtomicReference<Choreographer>();
+
+ @LayoutlibDelegate
+ public static Choreographer getInstance() {
+ if (mInstance.get() == null) {
+ mInstance.compareAndSet(null, Choreographer.getInstance_Original());
+ }
+
+ return mInstance.get();
+ }
@LayoutlibDelegate
public static float getRefreshRate() {
return 60.f;
}
+
+ @LayoutlibDelegate
+ static void scheduleVsyncLocked(Choreographer thisChoreographer) {
+ // do nothing
+ }
+
+ public static void doFrame(long frameTimeNanos) {
+ Choreographer thisChoreographer = Choreographer.getInstance();
+
+ thisChoreographer.mLastFrameTimeNanos = frameTimeNanos;
+
+ thisChoreographer.mFrameInfo.markInputHandlingStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
+
+ thisChoreographer.mFrameInfo.markAnimationsStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
+
+ thisChoreographer.mFrameInfo.markPerformTraversalsStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
+
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
+ }
}
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/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 48ca7d8d5fb6..683c4aabf6d9 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -183,7 +183,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
*/
private static LayoutLog sCurrentLog = sDefaultLog;
- private static final int LAST_SUPPORTED_FEATURE = Features.RECYCLER_VIEW_ADAPTER;
+ private static final int LAST_SUPPORTED_FEATURE = Features.CHOREOGRAPHER;
@Override
public int getApiLevel() {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index feb25905390c..2ac212c312c0 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -23,6 +23,7 @@ import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.layoutlib.bridge.impl.RenderSessionImpl;
+import com.android.tools.layoutlib.java.System_Delegate;
import android.view.View;
import android.view.ViewGroup;
@@ -191,6 +192,21 @@ public class BridgeRenderSession extends RenderSession {
}
@Override
+ public void setSystemTimeNanos(long nanos) {
+ System_Delegate.setNanosTime(nanos);
+ }
+
+ @Override
+ public void setSystemBootTimeNanos(long nanos) {
+ System_Delegate.setBootTimeNanos(nanos);
+ }
+
+ @Override
+ public void setElapsedFrameTimeNanos(long nanos) {
+ mSession.setElapsedFrameTimeNanos(nanos);
+ }
+
+ @Override
public void dispose() {
}
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;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
index 8899e53648d2..01c3c500855d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
@@ -184,8 +184,10 @@ public class BridgeIInputMethodManager implements IInputMethodManager {
}
@Override
- public InputBindResult startInput(IInputMethodClient client, IInputContext inputContext,
- EditorInfo attribute, int controlFlags) throws RemoteException {
+ public InputBindResult startInput(
+ /* @InputMethodClient.StartInputReason */ int startInputReason,
+ IInputMethodClient client, IInputContext inputContext, EditorInfo attribute,
+ int controlFlags) throws RemoteException {
// TODO Auto-generated method stub
return null;
}
@@ -226,9 +228,11 @@ public class BridgeIInputMethodManager implements IInputMethodManager {
}
@Override
- public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
- int controlFlags, int softInputMode, int windowFlags, EditorInfo attribute,
- IInputContext inputContext) throws RemoteException {
+ public InputBindResult windowGainedFocus(
+ /* @InputMethodClient.StartInputReason */ int startInputReason,
+ IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
+ int windowFlags, EditorInfo attribute, IInputContext inputContext)
+ throws RemoteException {
// TODO Auto-generated method stub
return null;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 0ffa35733180..ec50cfe55651 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -46,6 +46,7 @@ import com.android.layoutlib.bridge.android.support.DesignLibUtil;
import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.java.System_Delegate;
import com.android.util.Pair;
import android.animation.AnimationThread;
@@ -62,6 +63,7 @@ import android.graphics.Canvas;
import android.preference.Preference_Delegate;
import android.view.AttachInfo_Accessor;
import android.view.BridgeInflater;
+import android.view.Choreographer_Delegate;
import android.view.IWindowManager;
import android.view.IWindowManagerImpl;
import android.view.Surface;
@@ -120,6 +122,10 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
private int mMeasuredScreenWidth = -1;
private int mMeasuredScreenHeight = -1;
private boolean mIsAlphaChannelImage;
+ /** If >= 0, a frame will be executed */
+ private long mElapsedFrameTimeNanos = -1;
+ /** True if one frame has been already executed to start the animations */
+ private boolean mFirstFrameExecuted = false;
// information being returned through the API
private BufferedImage mImage;
@@ -252,6 +258,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
}
/**
+ * Sets the time for which the next frame will be selected. The time is the elapsed time from
+ * the current system nanos time. You
+ */
+ public void setElapsedFrameTimeNanos(long nanos) {
+ mElapsedFrameTimeNanos = nanos;
+ }
+
+ /**
* Renders the scene.
* <p>
* {@link #acquire(long)} must have been called before this.
@@ -428,6 +442,16 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
gc.dispose();
}
+ if (mElapsedFrameTimeNanos >= 0) {
+ long initialTime = System_Delegate.nanoTime();
+ if (!mFirstFrameExecuted) {
+ // The first frame will initialize the animations
+ Choreographer_Delegate.doFrame(initialTime);
+ mFirstFrameExecuted = true;
+ }
+ // Second frame will move the animations
+ Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos);
+ }
mViewRoot.draw(mCanvas);
}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png
new file mode 100644
index 000000000000..9f266278c352
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png
new file mode 100644
index 000000000000..89009be843e7
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml
new file mode 100644
index 000000000000..70d739692e29
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:padding="16dp"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <ProgressBar
+ android:layout_height="fill_parent"
+ android:layout_width="fill_parent" />
+
+</LinearLayout>
+
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index 9ebeebd49c82..2dca07cc4faf 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -48,6 +48,8 @@ import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
import static org.junit.Assert.fail;
@@ -348,16 +350,46 @@ public class Main {
renderAndVerify(params, "expand_horz_layout.png");
}
+ /** Test expand_layout.xml */
+ @Test
+ public void testVectorAnimation() throws ClassNotFoundException {
+ // Create the layout pull parser.
+ LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
+ "indeterminate_progressbar.xml");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+ RenderingMode.V_SCROLL, 22);
+
+ renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2));
+
+ parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
+ "indeterminate_progressbar.xml");
+ params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+ RenderingMode.V_SCROLL, 22);
+ renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3));
+ }
+
/**
* Create a new rendering session and test that rendering given layout on nexus 5
* doesn't throw any exceptions and matches the provided image.
+ * <p/>If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time
+ * indicates how far in the future is.
*/
- private void renderAndVerify(SessionParams params, String goldenFileName)
+ private void renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos)
throws ClassNotFoundException {
// TODO: Set up action bar handler properly to test menu rendering.
// Create session params.
RenderSession session = sBridge.createSession(params);
+ if (frameTimeNanos != -1) {
+ session.setElapsedFrameTimeNanos(frameTimeNanos);
+ }
+
if (!session.getResult().isSuccess()) {
getLogger().error(session.getResult().getException(),
session.getResult().getErrorMessage());
@@ -380,6 +412,15 @@ public class Main {
* Create a new rendering session and test that rendering given layout on nexus 5
* doesn't throw any exceptions and matches the provided image.
*/
+ private void renderAndVerify(SessionParams params, String goldenFileName)
+ throws ClassNotFoundException {
+ renderAndVerify(params, goldenFileName, -1);
+ }
+
+ /**
+ * Create a new rendering session and test that rendering given layout on nexus 5
+ * doesn't throw any exceptions and matches the provided image.
+ */
private void renderAndVerify(String layoutFileName, String goldenFileName)
throws ClassNotFoundException {
// Create the layout pull parser.
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index f6c2626e4271..8f0ad01c6dc3 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -77,6 +77,8 @@ public class AsmGenerator {
/** Methods to inject. FQCN of class in which method should be injected => runnable that does
* the injection. */
private final Map<String, ICreateInfo.InjectMethodRunnable> mInjectedMethodsMap;
+ /** A map { FQCN => set { field names } } which should be promoted to public visibility */
+ private final Map<String, Set<String>> mPromotedFields;
/**
* Creates a new generator that can generate the output JAR with the stubbed classes.
@@ -109,20 +111,8 @@ public class AsmGenerator {
// Create the map/set of methods to change to delegates
mDelegateMethods = new HashMap<String, Set<String>>();
- for (String signature : createInfo.getDelegateMethods()) {
- int pos = signature.indexOf('#');
- if (pos <= 0 || pos >= signature.length() - 1) {
- continue;
- }
- String className = binaryToInternalClassName(signature.substring(0, pos));
- String methodName = signature.substring(pos + 1);
- Set<String> methods = mDelegateMethods.get(className);
- if (methods == null) {
- methods = new HashSet<String>();
- mDelegateMethods.put(className, methods);
- }
- methods.add(methodName);
- }
+ addToMap(createInfo.getDelegateMethods(), mDelegateMethods);
+
for (String className : createInfo.getDelegateClassNatives()) {
className = binaryToInternalClassName(className);
Set<String> methods = mDelegateMethods.get(className);
@@ -187,10 +177,34 @@ public class AsmGenerator {
returnTypes.add(binaryToInternalClassName(className));
}
+ mPromotedFields = new HashMap<String, Set<String>>();
+ addToMap(createInfo.getPromotedFields(), mPromotedFields);
+
mInjectedMethodsMap = createInfo.getInjectedMethodsMap();
}
/**
+ * For each value in the array, split the value on '#' and add the parts to the map as key
+ * and value.
+ */
+ private void addToMap(String[] entries, Map<String, Set<String>> map) {
+ for (String entry : entries) {
+ int pos = entry.indexOf('#');
+ if (pos <= 0 || pos >= entry.length() - 1) {
+ return;
+ }
+ String className = binaryToInternalClassName(entry.substring(0, pos));
+ String methodOrFieldName = entry.substring(pos + 1);
+ Set<String> set = map.get(className);
+ if (set == null) {
+ set = new HashSet<String>();
+ map.put(className, set);
+ }
+ set.add(methodOrFieldName);
+ }
+ }
+
+ /**
* Returns the list of classes that have not been renamed yet.
* <p/>
* The names are "internal class names" rather than FQCN, i.e. they use "/" instead "."
@@ -380,6 +394,10 @@ public class AsmGenerator {
}
}
+ Set<String> promoteFields = mPromotedFields.get(className);
+ if (promoteFields != null && !promoteFields.isEmpty()) {
+ cv = new PromoteFieldClassAdapter(cv, promoteFields);
+ }
cr.accept(cv, 0);
return cw.toByteArray();
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index e480eadc0cf6..b571c5a486e9 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -120,6 +120,11 @@ public final class CreateInfo implements ICreateInfo {
}
@Override
+ public String[] getPromotedFields() {
+ return PROMOTED_FIELDS;
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return INJECTED_METHODS;
}
@@ -169,7 +174,9 @@ public final class CreateInfo implements ICreateInfo {
"android.text.format.DateFormat#is24HourFormat",
"android.text.Hyphenator#getSystemHyphenatorLocation",
"android.util.Xml#newPullParser",
+ "android.view.Choreographer#getInstance",
"android.view.Choreographer#getRefreshRate",
+ "android.view.Choreographer#scheduleVsyncLocked",
"android.view.Display#updateDisplayInfoLocked",
"android.view.Display#getWindowManager",
"android.view.LayoutInflater#rInflate",
@@ -290,6 +297,10 @@ public final class CreateInfo implements ICreateInfo {
"org.kxml2.io.KXmlParser"
};
+ private final static String[] PROMOTED_FIELDS = new String[] {
+ "android.view.Choreographer#mLastFrameTimeNanos"
+ };
+
/**
* List of classes for which the methods returning them should be deleted.
* The array contains a list of null terminated section starting with the name of the class
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
index 54b1fe628769..6c62423a2a89 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -78,6 +78,13 @@ public interface ICreateInfo {
Set<String> getExcludedClasses();
/**
+ * Returns a list of fields which should be promoted to public visibility. The array values
+ * are in the form of the binary FQCN of the class containing the field and the field name
+ * separated by a '#'.
+ */
+ String[] getPromotedFields();
+
+ /**
* Returns a map from binary FQCN className to {@link InjectMethodRunnable} which will be
* called to inject methods into a class.
* Can be empty but must not be null.
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java
new file mode 100644
index 000000000000..e4b70da2504f
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.tools.layoutlib.create;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+
+import java.util.Set;
+
+import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
+import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ASM4;
+
+/**
+ * Promotes given fields to public visibility.
+ */
+public class PromoteFieldClassAdapter extends ClassVisitor {
+
+ private final Set<String> mFieldNames;
+ private static final int ACC_NOT_PUBLIC = ~(ACC_PRIVATE | ACC_PROTECTED);
+
+ public PromoteFieldClassAdapter(ClassVisitor cv, Set<String> fieldNames) {
+ super(ASM4, cv);
+ mFieldNames = fieldNames;
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature,
+ Object value) {
+ if (mFieldNames.contains(name)) {
+ if ((access & ACC_PUBLIC) == 0) {
+ access = (access & ACC_NOT_PUBLIC) | ACC_PUBLIC;
+ }
+ }
+ return super.visitField(access, name, desc, signature, value);
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
index 0b85c48b4e68..5e47261ea89c 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
@@ -134,7 +134,33 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
}
});
- // Case 5: java.util.LinkedHashMap.eldest()
+ // Case 5: java.lang.System time calls
+ METHOD_REPLACERS.add(new MethodReplacer() {
+ @Override
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
+ return JAVA_LANG_SYSTEM.equals(owner) && name.equals("nanoTime");
+ }
+
+ @Override
+ public void replace(MethodInformation mi) {
+ mi.name = "nanoTime";
+ mi.owner = Type.getInternalName(System_Delegate.class);
+ }
+ });
+ METHOD_REPLACERS.add(new MethodReplacer() {
+ @Override
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
+ return JAVA_LANG_SYSTEM.equals(owner) && name.equals("currentTimeMillis");
+ }
+
+ @Override
+ public void replace(MethodInformation mi) {
+ mi.name = "currentTimeMillis";
+ mi.owner = Type.getInternalName(System_Delegate.class);
+ }
+ });
+
+ // Case 6: java.util.LinkedHashMap.eldest()
METHOD_REPLACERS.add(new MethodReplacer() {
private final String VOID_TO_MAP_ENTRY =
@@ -157,7 +183,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
}
});
- // Case 6: android.content.Context.getClassLoader() in LayoutInflater
+ // Case 7: android.content.Context.getClassLoader() in LayoutInflater
METHOD_REPLACERS.add(new MethodReplacer() {
// When LayoutInflater asks for a class loader, we must return the class loader that
// cannot return app's custom views/classes. This is so that in case of any failure
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
index 613c8d96b862..be937445c33d 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
@@ -18,12 +18,22 @@ package com.android.tools.layoutlib.java;
import com.android.tools.layoutlib.create.ReplaceMethodCallsAdapter;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
/**
* Provides dummy implementation of methods that don't exist on the host VM.
+ * This also providers a time control that allows to set a specific system time.
*
* @see ReplaceMethodCallsAdapter
*/
+@SuppressWarnings("unused")
public class System_Delegate {
+ // Current system time
+ private static AtomicLong mNanosTime = new AtomicLong(System.nanoTime());
+ // Time that the system booted up in nanos
+ private static AtomicLong mBootNanosTime = new AtomicLong(System.nanoTime());
+
public static void log(String message) {
// ignore.
}
@@ -31,4 +41,28 @@ public class System_Delegate {
public static void log(String message, Throwable th) {
// ignore.
}
+
+ public static void setNanosTime(long nanos) {
+ mNanosTime.set(nanos);
+ }
+
+ public static void setBootTimeNanos(long nanos) {
+ mBootNanosTime.set(nanos);
+ }
+
+ public static long nanoTime() {
+ return mNanosTime.get();
+ }
+
+ public static long currentTimeMillis() {
+ return TimeUnit.NANOSECONDS.toMillis(mNanosTime.get());
+ }
+
+ public static long bootTime() {
+ return mBootNanosTime.get();
+ }
+
+ public static long bootTimeMillis() {
+ return TimeUnit.NANOSECONDS.toMillis(mBootNanosTime.get());
+ }
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index 2c21470d6a2f..8a2235b8526c 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -138,6 +138,11 @@ public class AsmGeneratorTest {
}
@Override
+ public String[] getPromotedFields() {
+ return new String[0];
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return new HashMap<String, InjectMethodRunnable>(0);
}
@@ -213,6 +218,11 @@ public class AsmGeneratorTest {
}
@Override
+ public String[] getPromotedFields() {
+ return new String[0];
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return new HashMap<String, InjectMethodRunnable>(0);
}
@@ -296,6 +306,11 @@ public class AsmGeneratorTest {
}
@Override
+ public String[] getPromotedFields() {
+ return new String[0];
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return new HashMap<String, InjectMethodRunnable>(0);
}
@@ -374,6 +389,11 @@ public class AsmGeneratorTest {
}
@Override
+ public String[] getPromotedFields() {
+ return new String[0];
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
HashMap<String, InjectMethodRunnable> map =
new HashMap<String, InjectMethodRunnable>(1);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index bd030e8e5f32..b054f7c8d7a2 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -665,7 +665,7 @@ public class WifiManager {
* </ul>
* @return a list of network configurations in the form of a list
* of {@link WifiConfiguration} objects. Upon failure to fetch or
- * when when Wi-Fi is turned off, it can be null.
+ * when Wi-Fi is turned off, it can be null.
*/
public List<WifiConfiguration> getConfiguredNetworks() {
try {