summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt135
-rw-r--r--api/removed.txt10
-rw-r--r--api/system-current.txt144
-rw-r--r--api/system-removed.txt10
-rw-r--r--core/java/android/animation/Animator.java118
-rw-r--r--core/java/android/animation/AnimatorInflater.java71
-rw-r--r--core/java/android/animation/AnimatorSet.java1
-rw-r--r--core/java/android/animation/ValueAnimator.java11
-rw-r--r--core/java/android/app/ActivityManager.java30
-rw-r--r--core/java/android/app/ActivityThread.java12
-rw-r--r--core/java/android/app/AlarmManager.java63
-rw-r--r--core/java/android/app/AlertDialog.java17
-rw-r--r--core/java/android/app/AssistAction.java277
-rw-r--r--core/java/android/app/AssistContent.java2
-rw-r--r--core/java/android/app/Dialog.java70
-rw-r--r--core/java/android/app/EnterTransitionCoordinator.java53
-rw-r--r--core/java/android/app/ExitTransitionCoordinator.java42
-rw-r--r--core/java/android/app/IAlarmManager.aidl2
-rw-r--r--core/java/android/app/INotificationManager.aidl8
-rw-r--r--core/java/android/app/NotificationManager.java31
-rw-r--r--core/java/android/app/PendingIntent.java15
-rw-r--r--core/java/android/app/SharedElementCallback.java38
-rw-r--r--core/java/android/app/VoiceInteractor.java2
-rw-r--r--core/java/android/app/backup/BackupManager.java26
-rw-r--r--core/java/android/app/backup/IBackupManager.aidl13
-rw-r--r--core/java/android/bluetooth/le/BluetoothLeScanner.java2
-rw-r--r--core/java/android/content/pm/PackageManager.java9
-rw-r--r--core/java/android/database/AbstractCursor.java90
-rw-r--r--core/java/android/database/BulkCursorToCursorAdaptor.java1
-rw-r--r--core/java/android/database/Cursor.java7
-rw-r--r--core/java/android/database/CursorWrapper.java55
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java1
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java123
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java123
-rw-r--r--core/java/android/os/BatteryManager.java39
-rw-r--r--core/java/android/os/BatteryStats.java16
-rw-r--r--core/java/android/os/IUserManager.aidl1
-rw-r--r--core/java/android/os/UserManager.java15
-rw-r--r--core/java/android/provider/Settings.java21
-rw-r--r--core/java/android/security/keymaster/KeymasterDefs.java51
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java703
-rw-r--r--core/java/android/service/persistentdata/IPersistentDataBlockService.aidl3
-rw-r--r--core/java/android/service/persistentdata/PersistentDataBlockManager.java74
-rw-r--r--core/java/android/text/method/AllCapsTransformationMethod.java21
-rw-r--r--core/java/android/text/method/BaseKeyListener.java76
-rw-r--r--core/java/android/transition/TransitionManager.java1
-rw-r--r--core/java/android/view/InputDevice.java39
-rw-r--r--core/java/android/view/animation/AnimationUtils.java85
-rw-r--r--core/java/android/widget/ActionMenuPresenter.java2
-rw-r--r--core/java/android/widget/Editor.java88
-rw-r--r--core/java/android/widget/FastScroller.java142
-rw-r--r--core/java/android/widget/ProgressBar.java79
-rw-r--r--core/java/android/widget/TextView.java6
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl3
-rw-r--r--core/java/com/android/internal/app/ProcessStats.java2
-rw-r--r--core/java/com/android/internal/logging/EventLogTags.logtags2
-rw-r--r--core/java/com/android/internal/logging/MetricsConstants.java18
-rw-r--r--core/java/com/android/internal/logging/MetricsLogger.java28
-rw-r--r--core/java/com/android/internal/midi/EventScheduler.java11
-rw-r--r--core/java/com/android/internal/midi/MidiConstants.java14
-rw-r--r--core/java/com/android/internal/midi/MidiDispatcher.java7
-rw-r--r--core/java/com/android/internal/midi/MidiEventScheduler.java37
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHelper.java18
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java93
-rw-r--r--core/java/com/android/internal/os/InstallerConnection.java30
-rw-r--r--core/java/com/android/internal/os/WifiPowerCalculator.java7
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java11
-rw-r--r--core/java/com/android/internal/widget/FloatingToolbar.java1045
-rw-r--r--core/jni/android/graphics/BitmapFactory.cpp5
-rw-r--r--core/jni/android/graphics/BitmapFactory.h2
-rw-r--r--core/jni/android_emoji_EmojiFactory.cpp10
-rw-r--r--core/jni/android_view_InputDevice.cpp16
-rw-r--r--core/res/AndroidManifest.xml10
-rw-r--r--core/res/res/color/ratingbar_background_material.xml25
-rw-r--r--core/res/res/drawable-hdpi/ic_star_black_16dp.pngbin0 -> 263 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_star_black_36dp.pngbin0 -> 541 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_star_black_48dp.pngbin0 -> 668 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_star_half_black_16dp.pngbin0 -> 273 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_star_half_black_36dp.pngbin0 -> 435 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_star_half_black_48dp.pngbin0 -> 537 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_star_black_16dp.pngbin0 -> 193 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_star_black_36dp.pngbin0 -> 369 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_star_black_48dp.pngbin0 -> 467 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_star_half_black_16dp.pngbin0 -> 216 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_star_half_black_36dp.pngbin0 -> 339 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_star_half_black_48dp.pngbin0 -> 412 bytes
-rw-r--r--core/res/res/drawable-xhdpi/ic_star_black_16dp.pngbin0 -> 341 bytes
-rw-r--r--core/res/res/drawable-xhdpi/ic_star_black_36dp.pngbin0 -> 668 bytes
-rw-r--r--core/res/res/drawable-xhdpi/ic_star_black_48dp.pngbin0 -> 887 bytes
-rw-r--r--core/res/res/drawable-xhdpi/ic_star_half_black_16dp.pngbin0 -> 306 bytes
-rw-r--r--core/res/res/drawable-xhdpi/ic_star_half_black_36dp.pngbin0 -> 537 bytes
-rw-r--r--core/res/res/drawable-xhdpi/ic_star_half_black_48dp.pngbin0 -> 712 bytes
-rw-r--r--core/res/res/drawable-xxhdpi/ic_star_black_16dp.pngbin0 -> 467 bytes
-rw-r--r--core/res/res/drawable-xxhdpi/ic_star_black_36dp.pngbin0 -> 1010 bytes
-rw-r--r--core/res/res/drawable-xxhdpi/ic_star_black_48dp.pngbin0 -> 1291 bytes
-rw-r--r--core/res/res/drawable-xxhdpi/ic_star_half_black_16dp.pngbin0 -> 412 bytes
-rw-r--r--core/res/res/drawable-xxhdpi/ic_star_half_black_36dp.pngbin0 -> 756 bytes
-rw-r--r--core/res/res/drawable-xxhdpi/ic_star_half_black_48dp.pngbin0 -> 973 bytes
-rw-r--r--core/res/res/drawable-xxxhdpi/ic_star_black_16dp.pngbin0 -> 620 bytes
-rw-r--r--core/res/res/drawable-xxxhdpi/ic_star_black_36dp.pngbin0 -> 1291 bytes
-rw-r--r--core/res/res/drawable-xxxhdpi/ic_star_black_48dp.pngbin0 -> 1680 bytes
-rw-r--r--core/res/res/drawable-xxxhdpi/ic_star_half_black_16dp.pngbin0 -> 498 bytes
-rw-r--r--core/res/res/drawable-xxxhdpi/ic_star_half_black_36dp.pngbin0 -> 1205 bytes
-rw-r--r--core/res/res/drawable-xxxhdpi/ic_star_half_black_48dp.pngbin0 -> 1288 bytes
-rw-r--r--core/res/res/drawable/fastscroll_label_left_material.xml26
-rw-r--r--core/res/res/drawable/fastscroll_label_right_material.xml26
-rw-r--r--core/res/res/drawable/ratingbar_full_empty_material.xml9
-rw-r--r--core/res/res/drawable/ratingbar_full_filled_material.xml6
-rw-r--r--core/res/res/drawable/ratingbar_full_half_material.xml (renamed from core/res/res/drawable/ratingbar_full_material.xml)20
-rw-r--r--core/res/res/drawable/ratingbar_indicator_material.xml35
-rw-r--r--core/res/res/drawable/ratingbar_material.xml33
-rw-r--r--core/res/res/drawable/ratingbar_small_material.xml35
-rw-r--r--core/res/res/layout/floating_popup_close_overflow_button.xml24
-rw-r--r--core/res/res/layout/floating_popup_open_overflow_button.xml2
-rw-r--r--core/res/res/layout/floating_popup_overflow_list_item33
-rw-r--r--core/res/res/values/arrays.xml6
-rw-r--r--core/res/res/values/attrs.xml27
-rwxr-xr-xcore/res/res/values/config.xml10
-rw-r--r--core/res/res/values/dimens.xml12
-rw-r--r--core/res/res/values/public.xml4
-rw-r--r--core/res/res/values/strings.xml35
-rw-r--r--core/res/res/values/styles_material.xml42
-rwxr-xr-xcore/res/res/values/symbols.xml19
-rw-r--r--core/res/res/xml/default_zen_mode_config.xml5
-rw-r--r--docs/html/google/play-services/setup.jd50
-rw-r--r--graphics/java/android/graphics/drawable/LayerDrawable.java8
-rw-r--r--include/androidfw/ResourceTypes.h2
-rw-r--r--keystore/java/android/security/AndroidKeyStore.java21
-rw-r--r--keystore/java/android/security/AndroidKeyStoreProvider.java1
-rw-r--r--keystore/java/android/security/KeyGeneratorSpec.java64
-rw-r--r--keystore/java/android/security/KeyPairGeneratorSpec.java81
-rw-r--r--keystore/java/android/security/KeyStoreKeyCharacteristics.java17
-rw-r--r--keystore/java/android/security/KeyStoreKeyConstraints.java139
-rw-r--r--keystore/java/android/security/KeyStoreKeyGeneratorSpi.java23
-rw-r--r--keystore/java/android/security/KeyStoreKeySpec.java11
-rw-r--r--keystore/java/android/security/KeyStoreParameter.java82
-rw-r--r--keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java16
-rw-r--r--keystore/tests/src/android/security/KeyStoreTest.java18
-rw-r--r--libs/hwui/AmbientShadow.cpp15
-rw-r--r--libs/hwui/FontRenderer.cpp2
-rw-r--r--libs/hwui/ProgramCache.cpp5
-rw-r--r--libs/hwui/RenderBufferCache.cpp5
-rw-r--r--libs/hwui/SpotShadow.cpp11
-rw-r--r--media/java/android/media/MediaCodec.java32
-rw-r--r--media/java/android/media/MediaCrypto.java14
-rw-r--r--media/java/android/media/MediaCryptoException.java4
-rw-r--r--media/java/android/media/MediaDrm.java218
-rw-r--r--media/java/android/media/MediaFormat.java16
-rw-r--r--media/java/android/media/MediaMetadataRetriever.java6
-rw-r--r--media/java/android/media/MediaSync.java32
-rw-r--r--media/java/android/media/MediaTimestamp.java52
-rw-r--r--media/java/android/media/midi/IMidiDeviceServer.aidl3
-rw-r--r--media/java/android/media/midi/MidiDeviceInfo.java32
-rw-r--r--media/java/android/media/midi/MidiDeviceServer.java9
-rw-r--r--media/java/android/media/midi/MidiDeviceService.java2
-rw-r--r--media/java/android/media/midi/MidiInputPort.java13
-rw-r--r--media/java/android/media/midi/MidiManager.java92
-rw-r--r--media/java/android/media/midi/MidiOutputPort.java24
-rw-r--r--media/java/android/media/midi/MidiPortImpl.java70
-rw-r--r--media/java/android/media/midi/MidiReceiver.java7
-rw-r--r--media/jni/android_media_MediaCodec.cpp8
-rw-r--r--media/jni/android_media_MediaCrypto.cpp43
-rw-r--r--media/jni/android_media_MediaDrm.cpp33
-rw-r--r--media/jni/android_media_MediaSync.cpp55
-rw-r--r--media/jni/android_media_MediaSync.h3
-rw-r--r--media/packages/BluetoothMidiService/Android.mk11
-rw-r--r--media/packages/BluetoothMidiService/AndroidManifest.xml17
-rw-r--r--media/packages/BluetoothMidiService/res/values/strings.xml19
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java276
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java61
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java106
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java157
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketDecoder.java33
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java41
-rw-r--r--packages/DocumentsUI/res/values/strings.xml2
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/CopyService.java227
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java34
-rw-r--r--packages/Keyguard/res/values/strings.xml3
-rw-r--r--packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java70
-rw-r--r--packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java5
-rw-r--r--packages/Shell/AndroidManifest.xml1
-rw-r--r--packages/StatementService/Android.mk2
-rw-r--r--packages/SystemUI/Android.mk5
-rw-r--r--packages/SystemUI/res/drawable/ic_fingerprint.xml36
-rw-r--r--packages/SystemUI/res/drawable/ic_fingerprint_error.xml30
-rw-r--r--packages/SystemUI/res/drawable/ic_volume_media_bt.xml (renamed from packages/SystemUI/res/drawable/ic_volume_bt.xml)15
-rw-r--r--packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml (renamed from packages/SystemUI/res/drawable/ic_volume_bt_mute.xml)12
-rw-r--r--packages/SystemUI/res/layout/keyguard_bottom_area.xml1
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/Prefs.java94
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Constants.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/Util.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java116
-rw-r--r--rs/java/android/renderscript/RenderScript.java14
-rw-r--r--rs/jni/android_renderscript_RenderScript.cpp12
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java11
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java5
-rw-r--r--services/backup/java/com/android/server/backup/Trampoline.java6
-rw-r--r--services/core/java/com/android/server/AlarmManagerService.java312
-rw-r--r--services/core/java/com/android/server/InputMethodManagerService.java30
-rw-r--r--services/core/java/com/android/server/PersistentDataBlockService.java39
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java68
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java2
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java6
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java19
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java16
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java27
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerState.java4
-rw-r--r--services/core/java/com/android/server/job/controllers/BatteryController.java54
-rw-r--r--services/core/java/com/android/server/lights/LightsService.java3
-rw-r--r--services/core/java/com/android/server/notification/ConditionProviders.java414
-rw-r--r--services/core/java/com/android/server/notification/CountdownConditionProvider.java31
-rw-r--r--services/core/java/com/android/server/notification/DowntimeConditionProvider.java409
-rw-r--r--services/core/java/com/android/server/notification/NextAlarmConditionProvider.java224
-rw-r--r--services/core/java/com/android/server/notification/NextAlarmTracker.java263
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java47
-rw-r--r--services/core/java/com/android/server/notification/NotificationUsageStats.java7
-rw-r--r--services/core/java/com/android/server/notification/ScheduleCalendar.java (renamed from services/core/java/com/android/server/notification/DowntimeCalendar.java)58
-rw-r--r--services/core/java/com/android/server/notification/ScheduleConditionProvider.java238
-rw-r--r--services/core/java/com/android/server/notification/SystemConditionProviderService.java36
-rw-r--r--services/core/java/com/android/server/notification/ValidateNotificationPeople.java4
-rw-r--r--services/core/java/com/android/server/notification/ZenModeConditions.java144
-rw-r--r--services/core/java/com/android/server/notification/ZenModeFiltering.java279
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java671
-rw-r--r--services/core/java/com/android/server/pm/BasePermission.java4
-rw-r--r--services/core/java/com/android/server/pm/Installer.java135
-rw-r--r--services/core/java/com/android/server/pm/PackageDexOptimizer.java91
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java10
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java34
-rw-r--r--services/core/java/com/android/server/pm/PermissionsState.java20
-rw-r--r--services/core/java/com/android/server/pm/SELinuxMMAC.java217
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java45
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java78
-rw-r--r--services/core/java/com/android/server/power/DeviceIdleController.java11
-rw-r--r--services/core/java/com/android/server/tv/TvInputManagerService.java8
-rw-r--r--services/midi/java/com/android/server/midi/MidiService.java4
-rw-r--r--services/print/java/com/android/server/print/UserState.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java109
-rw-r--r--services/usb/java/com/android/server/usb/UsbAlsaManager.java4
-rw-r--r--services/usb/java/com/android/server/usb/UsbMidiDevice.java66
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java4
-rw-r--r--telephony/java/com/android/internal/telephony/ISms.aidl56
-rw-r--r--test-runner/src/android/test/mock/MockCursor.java47
-rw-r--r--tests/Compatibility/Android.mk2
-rw-r--r--tests/Compatibility/AndroidManifest.xml2
-rw-r--r--tools/aapt2/Android.mk12
-rw-r--r--tools/aapt2/BindingXmlPullParser.cpp263
-rw-r--r--tools/aapt2/BindingXmlPullParser.h88
-rw-r--r--tools/aapt2/BindingXmlPullParser_test.cpp110
-rw-r--r--tools/aapt2/Files.cpp11
-rw-r--r--tools/aapt2/Files.h5
-rw-r--r--tools/aapt2/Flag.cpp109
-rw-r--r--tools/aapt2/Flag.h28
-rw-r--r--tools/aapt2/Main.cpp1086
-rw-r--r--tools/aapt2/Png.cpp1284
-rw-r--r--tools/aapt2/Png.h38
-rw-r--r--tools/aapt2/Util.cpp7
-rw-r--r--tools/aapt2/Util.h19
-rw-r--r--tools/aapt2/Util_test.cpp6
-rw-r--r--tools/aapt2/data/res/drawable/icon.pngbin0 -> 2341 bytes
-rw-r--r--tools/aapt2/data/res/drawable/test.9.pngbin0 -> 124 bytes
-rw-r--r--tools/aapt2/data/res/layout/main.xml4
-rw-r--r--tools/apilint/apilint.py2
285 files changed, 11192 insertions, 4266 deletions
diff --git a/api/current.txt b/api/current.txt
index 9bd6367..fcbfa04 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -525,6 +525,7 @@ package android {
field public static final int dropDownWidth = 16843362; // 0x1010262
field public static final int duplicateParentState = 16842985; // 0x10100e9
field public static final int duration = 16843160; // 0x1010198
+ field public static final int durationScaleHint = 16844014; // 0x10104ee
field public static final int editTextBackground = 16843602; // 0x1010352
field public static final int editTextColor = 16843601; // 0x1010351
field public static final int editTextPreferenceStyle = 16842898; // 0x1010092
@@ -1301,6 +1302,7 @@ package android {
field public static final int thicknessRatio = 16843164; // 0x101019c
field public static final int thumb = 16843074; // 0x1010142
field public static final int thumbOffset = 16843075; // 0x1010143
+ field public static final int thumbPosition = 16844013; // 0x10104ed
field public static final int thumbTextPadding = 16843634; // 0x1010372
field public static final int thumbTint = 16843889; // 0x1010471
field public static final int thumbTintMode = 16843890; // 0x1010472
@@ -2878,6 +2880,7 @@ package android.animation {
method public void cancel();
method public android.animation.Animator clone();
method public void end();
+ method public long getDistanceBasedDuration();
method public abstract long getDuration();
method public android.animation.TimeInterpolator getInterpolator();
method public java.util.ArrayList<android.animation.Animator.AnimatorListener> getListeners();
@@ -2891,12 +2894,16 @@ package android.animation {
method public void removePauseListener(android.animation.Animator.AnimatorPauseListener);
method public void resume();
method public abstract android.animation.Animator setDuration(long);
+ method public void setDurationScaleHint(int, android.content.res.Resources);
method public abstract void setInterpolator(android.animation.TimeInterpolator);
method public abstract void setStartDelay(long);
method public void setTarget(java.lang.Object);
method public void setupEndValues();
method public void setupStartValues();
method public void start();
+ field public static final int HINT_DISTANCE_DEFINED_IN_DP = 2; // 0x2
+ field public static final int HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE = 1; // 0x1
+ field public static final int HINT_NO_SCALE = 0; // 0x0
}
public static abstract interface Animator.AnimatorListener {
@@ -3975,6 +3982,48 @@ package android.app {
field public java.lang.String serviceDetails;
}
+ public final class AssistAction {
+ method public static void updateAssistData(android.os.Bundle, android.os.Bundle);
+ field public static final java.lang.String ASSIST_ACTION_KEY = "android:assist_action";
+ field public static final java.lang.String KEY_ACTION_OBJECT = "object";
+ field public static final java.lang.String KEY_ACTION_STATUS = "actionStatus";
+ field public static final java.lang.String KEY_DESCRIPTION = "description";
+ field public static final java.lang.String KEY_ID = "@id";
+ field public static final java.lang.String KEY_NAME = "name";
+ field public static final java.lang.String KEY_TYPE = "@type";
+ field public static final java.lang.String KEY_URL = "url";
+ field public static final java.lang.String STATUS_TYPE_ACTIVE = "ActiveActionStatus";
+ field public static final java.lang.String STATUS_TYPE_COMPLETED = "CompletedActionStatus";
+ field public static final java.lang.String TYPE_ADD_ACTION = "AddAction";
+ field public static final java.lang.String TYPE_BOOKMARK_ACTION = "BookmarkAction";
+ field public static final java.lang.String TYPE_LIKE_ACTION = "LikeAction";
+ field public static final java.lang.String TYPE_LISTEN_ACTION = "ListenAction";
+ field public static final java.lang.String TYPE_VIEW_ACTION = "ViewAction";
+ field public static final java.lang.String TYPE_WANT_ACTION = "WantAction";
+ field public static final java.lang.String TYPE_WATCH_ACTION = "WatchAction";
+ }
+
+ public static final class AssistAction.ActionBuilder {
+ ctor public AssistAction.ActionBuilder();
+ method public android.os.Bundle build();
+ method public android.app.AssistAction.ActionBuilder set(java.lang.String, java.lang.String);
+ method public android.app.AssistAction.ActionBuilder set(java.lang.String, android.os.Bundle);
+ method public android.app.AssistAction.ActionBuilder setObject(android.os.Bundle);
+ method public android.app.AssistAction.ActionBuilder setType(java.lang.String);
+ }
+
+ public static final class AssistAction.ThingBuilder {
+ ctor public AssistAction.ThingBuilder();
+ method public android.os.Bundle build();
+ method public android.app.AssistAction.ThingBuilder set(java.lang.String, java.lang.String);
+ method public android.app.AssistAction.ThingBuilder set(java.lang.String, android.os.Bundle);
+ method public android.app.AssistAction.ThingBuilder setDescription(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setId(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setName(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setType(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setUrl(android.net.Uri);
+ }
+
public class AssistContent implements android.os.Parcelable {
ctor public AssistContent();
method public int describeContents();
@@ -5100,6 +5149,7 @@ package android.app {
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.PendingIntent> CREATOR;
field public static final int FLAG_CANCEL_CURRENT = 268435456; // 0x10000000
+ field public static final int FLAG_IMMUTABLE = 67108864; // 0x4000000
field public static final int FLAG_NO_CREATE = 536870912; // 0x20000000
field public static final int FLAG_ONE_SHOT = 1073741824; // 0x40000000
field public static final int FLAG_UPDATE_CURRENT = 134217728; // 0x8000000
@@ -5316,6 +5366,11 @@ package android.app {
method public void onRejectSharedElements(java.util.List<android.view.View>);
method public void onSharedElementEnd(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
method public void onSharedElementStart(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
+ method public void onSharedElementsArrived(java.util.List<java.lang.String>, java.util.List<android.view.View>, android.app.SharedElementCallback.OnSharedElementsReadyListener);
+ }
+
+ public static abstract interface SharedElementCallback.OnSharedElementsReadyListener {
+ method public abstract void onSharedElementsReady();
}
public deprecated class TabActivity extends android.app.ActivityGroup {
@@ -9161,6 +9216,7 @@ package android.content.pm {
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand";
field public static final java.lang.String FEATURE_GAMEPAD = "android.hardware.gamepad";
+ field public static final java.lang.String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors";
field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen";
field public static final java.lang.String FEATURE_INPUT_METHODS = "android.software.input_methods";
field public static final java.lang.String FEATURE_LEANBACK = "android.software.leanback";
@@ -9725,15 +9781,13 @@ package android.database {
method public void registerDataSetObserver(android.database.DataSetObserver);
method public boolean requery();
method public android.os.Bundle respond(android.os.Bundle);
+ method public void setExtras(android.os.Bundle);
method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public void unregisterContentObserver(android.database.ContentObserver);
method public void unregisterDataSetObserver(android.database.DataSetObserver);
- field protected boolean mClosed;
- field protected android.content.ContentResolver mContentResolver;
- field protected deprecated java.lang.Long mCurrentRowID;
- field protected int mPos;
- field protected deprecated int mRowIdColumnIndex;
- field protected deprecated java.util.HashMap<java.lang.Long, java.util.Map<java.lang.String, java.lang.Object>> mUpdatedRows;
+ field protected deprecated boolean mClosed;
+ field protected deprecated android.content.ContentResolver mContentResolver;
+ field protected deprecated int mPos;
}
protected static class AbstractCursor.SelfContentObserver extends android.database.ContentObserver {
@@ -9833,6 +9887,7 @@ package android.database {
method public abstract void registerDataSetObserver(android.database.DataSetObserver);
method public abstract deprecated boolean requery();
method public abstract android.os.Bundle respond(android.os.Bundle);
+ method public abstract void setExtras(android.os.Bundle);
method public abstract void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public abstract void unregisterContentObserver(android.database.ContentObserver);
method public abstract void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -9904,7 +9959,7 @@ package android.database {
ctor public CursorWrapper(android.database.Cursor);
method public void close();
method public void copyStringToBuffer(int, android.database.CharArrayBuffer);
- method public void deactivate();
+ method public deprecated void deactivate();
method public byte[] getBlob(int);
method public int getColumnCount();
method public int getColumnIndex(java.lang.String);
@@ -9938,8 +9993,9 @@ package android.database {
method public boolean moveToPrevious();
method public void registerContentObserver(android.database.ContentObserver);
method public void registerDataSetObserver(android.database.DataSetObserver);
- method public boolean requery();
+ method public deprecated boolean requery();
method public android.os.Bundle respond(android.os.Bundle);
+ method public void setExtras(android.os.Bundle);
method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public void unregisterContentObserver(android.database.ContentObserver);
method public void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -13020,6 +13076,10 @@ package android.hardware.camera2 {
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> LENS_INFO_FOCUS_DISTANCE_CALIBRATION;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> LENS_INFO_HYPERFOCAL_DISTANCE;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> LENS_INFO_MINIMUM_FOCUS_DISTANCE;
+ field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_INTRINSIC_CALIBRATION;
+ field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_POSE_ROTATION;
+ field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_POSE_TRANSLATION;
+ field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_RADIAL_DISTORTION;
field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES;
field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> REQUEST_AVAILABLE_CAPABILITIES;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_MAX_NUM_INPUT_STREAMS;
@@ -13439,7 +13499,11 @@ package android.hardware.camera2 {
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> LENS_FOCAL_LENGTH;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> LENS_FOCUS_DISTANCE;
field public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Float, java.lang.Float>> LENS_FOCUS_RANGE;
+ field public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_INTRINSIC_CALIBRATION;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> LENS_OPTICAL_STABILIZATION_MODE;
+ field public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_POSE_ROTATION;
+ field public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_POSE_TRANSLATION;
+ field public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_RADIAL_DISTORTION;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> LENS_STATE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> NOISE_REDUCTION_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
@@ -14992,6 +15056,7 @@ package android.media {
field public static final java.lang.String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
field public static final java.lang.String PARAMETER_KEY_SUSPEND = "drop-input-frames";
field public static final java.lang.String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate";
+ field public static final int REASON_RECLAIMED = 1; // 0x1
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
}
@@ -15007,6 +15072,7 @@ package android.media {
public static abstract class MediaCodec.Callback {
ctor public MediaCodec.Callback();
+ method public void onCodecReleased(android.media.MediaCodec, int);
method public abstract void onError(android.media.MediaCodec, android.media.MediaCodec.CodecException);
method public abstract void onInputBufferAvailable(android.media.MediaCodec, int);
method public abstract void onOutputBufferAvailable(android.media.MediaCodec, int, android.media.MediaCodec.BufferInfo);
@@ -15272,6 +15338,7 @@ package android.media {
method public static final boolean isCryptoSchemeSupported(java.util.UUID);
method public final void release();
method public final boolean requiresSecureDecoderComponent(java.lang.String);
+ method public final void setMediaDrmSession(byte[]) throws android.media.MediaCryptoException;
}
public final class MediaCryptoException extends java.lang.Exception {
@@ -15327,6 +15394,8 @@ package android.media {
method public void removeKeys(byte[]);
method public void restoreKeys(byte[], byte[]);
method public void setOnEventListener(android.media.MediaDrm.OnEventListener);
+ method public void setOnExpirationUpdateListener(android.media.MediaDrm.OnExpirationUpdateListener, android.os.Handler);
+ method public void setOnKeysChangeListener(android.media.MediaDrm.OnKeysChangeListener, android.os.Handler);
method public void setPropertyByteArray(java.lang.String, byte[]);
method public void setPropertyString(java.lang.String, java.lang.String);
field public static final int EVENT_KEY_EXPIRED = 3; // 0x3
@@ -15334,6 +15403,11 @@ package android.media {
field public static final deprecated int EVENT_PROVISION_REQUIRED = 1; // 0x1
field public static final int EVENT_SESSION_RECLAIMED = 5; // 0x5
field public static final int EVENT_VENDOR_DEFINED = 4; // 0x4
+ field public static final int KEY_STATUS_EXPIRED = 1; // 0x1
+ field public static final int KEY_STATUS_INTERNAL_ERROR = 4; // 0x4
+ field public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2; // 0x2
+ field public static final int KEY_STATUS_PENDING = 3; // 0x3
+ field public static final int KEY_STATUS_USABLE = 0; // 0x0
field public static final int KEY_TYPE_OFFLINE = 2; // 0x2
field public static final int KEY_TYPE_RELEASE = 3; // 0x3
field public static final int KEY_TYPE_STREAMING = 1; // 0x1
@@ -15360,6 +15434,11 @@ package android.media {
method public int getRequestType();
}
+ public static final class MediaDrm.KeyStatus {
+ method public byte[] getKeyId();
+ method public int getStatusCode();
+ }
+
public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
method public java.lang.String getDiagnosticInfo();
}
@@ -15368,6 +15447,14 @@ package android.media {
method public abstract void onEvent(android.media.MediaDrm, byte[], int, int, byte[]);
}
+ public static abstract interface MediaDrm.OnExpirationUpdateListener {
+ method public abstract void onExpirationUpdate(android.media.MediaDrm, byte[], long);
+ }
+
+ public static abstract interface MediaDrm.OnKeysChangeListener {
+ method public abstract void onKeysChange(android.media.MediaDrm, byte[], java.util.List<android.media.MediaDrm.KeyStatus>, boolean);
+ }
+
public static final class MediaDrm.ProvisionRequest {
method public byte[] getData();
method public java.lang.String getDefaultUrl();
@@ -15454,6 +15541,7 @@ package android.media {
field public static final java.lang.String KEY_MAX_INPUT_SIZE = "max-input-size";
field public static final java.lang.String KEY_MAX_WIDTH = "max-width";
field public static final java.lang.String KEY_MIME = "mime";
+ field public static final java.lang.String KEY_OPERATING_RATE = "operating-rate";
field public static final java.lang.String KEY_PRIORITY = "priority";
field public static final java.lang.String KEY_PROFILE = "profile";
field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown";
@@ -15576,6 +15664,7 @@ package android.media {
field public static final int METADATA_KEY_ARTIST = 2; // 0x2
field public static final int METADATA_KEY_AUTHOR = 3; // 0x3
field public static final int METADATA_KEY_BITRATE = 20; // 0x14
+ field public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25; // 0x19
field public static final int METADATA_KEY_CD_TRACK_NUMBER = 0; // 0x0
field public static final int METADATA_KEY_COMPILATION = 15; // 0xf
field public static final int METADATA_KEY_COMPOSER = 4; // 0x4
@@ -15979,6 +16068,7 @@ package android.media {
method public void configureAudioTrack(android.media.AudioTrack, int);
method public void configureSurface(android.view.Surface);
method public final android.view.Surface createInputSurface();
+ method public boolean getTimestamp(android.media.MediaTimestamp);
method public void queueAudio(java.nio.ByteBuffer, int, int, long);
method public final void release();
method public void setCallback(android.media.MediaSync.Callback, android.os.Handler);
@@ -16000,6 +16090,13 @@ package android.media {
field public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; // 0x1
}
+ public final class MediaTimestamp {
+ ctor public MediaTimestamp();
+ field public float clockRate;
+ field public long mediaTimeUs;
+ field public long nanoTime;
+ }
+
public final class NotProvisionedException extends android.media.MediaDrmException {
ctor public NotProvisionedException(java.lang.String);
}
@@ -16761,9 +16858,8 @@ package android.media.midi {
method public int describeContents();
method public int getId();
method public int getInputPortCount();
- method public android.media.midi.MidiDeviceInfo.PortInfo getInputPortInfo(int);
method public int getOutputPortCount();
- method public android.media.midi.MidiDeviceInfo.PortInfo getOutputPortInfo(int);
+ method public android.media.midi.MidiDeviceInfo.PortInfo[] getPortList();
method public android.os.Bundle getProperties();
method public int getType();
method public boolean isPrivate();
@@ -16815,11 +16911,17 @@ package android.media.midi {
public final class MidiManager {
method public android.media.midi.MidiDeviceInfo[] getDeviceList();
+ method public void openBluetoothDevice(android.bluetooth.BluetoothDevice, android.media.midi.MidiManager.BluetoothOpenCallback, android.os.Handler);
method public void openDevice(android.media.midi.MidiDeviceInfo, android.media.midi.MidiManager.DeviceOpenCallback, android.os.Handler);
method public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler);
method public void unregisterDeviceCallback(android.media.midi.MidiManager.DeviceCallback);
}
+ public static abstract class MidiManager.BluetoothOpenCallback {
+ ctor public MidiManager.BluetoothOpenCallback();
+ method public abstract void onDeviceOpened(android.bluetooth.BluetoothDevice, android.media.midi.MidiDevice);
+ }
+
public static class MidiManager.DeviceCallback {
ctor public MidiManager.DeviceCallback();
method public void onDeviceAdded(android.media.midi.MidiDeviceInfo);
@@ -16841,6 +16943,7 @@ package android.media.midi {
public abstract class MidiReceiver {
ctor public MidiReceiver();
+ method public void flush() throws java.io.IOException;
method public int getMaxMessageSize();
method public abstract void onReceive(byte[], int, int, long) throws java.io.IOException;
method public void send(byte[], int, int) throws java.io.IOException;
@@ -22246,6 +22349,9 @@ package android.os {
public class BatteryManager {
method public int getIntProperty(int);
method public long getLongProperty(int);
+ method public boolean isCharging();
+ field public static final java.lang.String ACTION_CHARGING = "android.os.action.CHARGING";
+ field public static final java.lang.String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
field public static final int BATTERY_HEALTH_COLD = 7; // 0x7
field public static final int BATTERY_HEALTH_DEAD = 4; // 0x4
field public static final int BATTERY_HEALTH_GOOD = 2; // 0x2
@@ -23284,6 +23390,7 @@ package android.os {
method public android.os.Bundle getApplicationRestrictions(java.lang.String);
method public long getSerialNumberForUser(android.os.UserHandle);
method public int getUserCount();
+ method public long getUserCreationTime(int);
method public android.os.UserHandle getUserForSerialNumber(long);
method public java.lang.String getUserName();
method public java.util.List<android.os.UserHandle> getUserProfiles();
@@ -30524,7 +30631,7 @@ package android.test.mock {
ctor public MockCursor();
method public void close();
method public void copyStringToBuffer(int, android.database.CharArrayBuffer);
- method public void deactivate();
+ method public deprecated void deactivate();
method public byte[] getBlob(int);
method public int getColumnCount();
method public int getColumnIndex(java.lang.String);
@@ -30557,8 +30664,9 @@ package android.test.mock {
method public boolean moveToPrevious();
method public void registerContentObserver(android.database.ContentObserver);
method public void registerDataSetObserver(android.database.DataSetObserver);
- method public boolean requery();
+ method public deprecated boolean requery();
method public android.os.Bundle respond(android.os.Bundle);
+ method public void setExtras(android.os.Bundle);
method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public void unregisterContentObserver(android.database.ContentObserver);
method public void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -32508,6 +32616,7 @@ package android.transition {
ctor public TransitionManager();
method public static void beginDelayedTransition(android.view.ViewGroup);
method public static void beginDelayedTransition(android.view.ViewGroup, android.transition.Transition);
+ method public static void endTransitions(android.view.ViewGroup);
method public static void go(android.transition.Scene);
method public static void go(android.transition.Scene, android.transition.Transition);
method public void setTransition(android.transition.Scene, android.transition.Transition);
@@ -33526,10 +33635,10 @@ package android.view {
method public java.lang.String getName();
method public int getProductId();
method public int getSources();
- method public java.lang.String getUniqueId();
method public int getVendorId();
method public android.os.Vibrator getVibrator();
method public boolean[] hasKeys(int...);
+ method public boolean hasMic();
method public boolean isVirtual();
method public boolean supportsSource(int);
method public void writeToParcel(android.os.Parcel, int);
diff --git a/api/removed.txt b/api/removed.txt
index c2b9d3e..0c433c3 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -6,6 +6,16 @@ package android.content.pm {
}
+package android.database {
+
+ public abstract class AbstractCursor implements android.database.CrossProcessCursor {
+ field protected java.lang.Long mCurrentRowID;
+ field protected int mRowIdColumnIndex;
+ field protected java.util.HashMap<java.lang.Long, java.util.Map<java.lang.String, java.lang.Object>> mUpdatedRows;
+ }
+
+}
+
package android.media {
public class AudioFormat {
diff --git a/api/system-current.txt b/api/system-current.txt
index ca6b777..5351778 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -598,6 +598,7 @@ package android {
field public static final int dropDownWidth = 16843362; // 0x1010262
field public static final int duplicateParentState = 16842985; // 0x10100e9
field public static final int duration = 16843160; // 0x1010198
+ field public static final int durationScaleHint = 16844014; // 0x10104ee
field public static final int editTextBackground = 16843602; // 0x1010352
field public static final int editTextColor = 16843601; // 0x1010351
field public static final int editTextPreferenceStyle = 16842898; // 0x1010092
@@ -1378,6 +1379,7 @@ package android {
field public static final int thicknessRatio = 16843164; // 0x101019c
field public static final int thumb = 16843074; // 0x1010142
field public static final int thumbOffset = 16843075; // 0x1010143
+ field public static final int thumbPosition = 16844013; // 0x10104ed
field public static final int thumbTextPadding = 16843634; // 0x1010372
field public static final int thumbTint = 16843889; // 0x1010471
field public static final int thumbTintMode = 16843890; // 0x1010472
@@ -2958,6 +2960,7 @@ package android.animation {
method public void cancel();
method public android.animation.Animator clone();
method public void end();
+ method public long getDistanceBasedDuration();
method public abstract long getDuration();
method public android.animation.TimeInterpolator getInterpolator();
method public java.util.ArrayList<android.animation.Animator.AnimatorListener> getListeners();
@@ -2971,12 +2974,16 @@ package android.animation {
method public void removePauseListener(android.animation.Animator.AnimatorPauseListener);
method public void resume();
method public abstract android.animation.Animator setDuration(long);
+ method public void setDurationScaleHint(int, android.content.res.Resources);
method public abstract void setInterpolator(android.animation.TimeInterpolator);
method public abstract void setStartDelay(long);
method public void setTarget(java.lang.Object);
method public void setupEndValues();
method public void setupStartValues();
method public void start();
+ field public static final int HINT_DISTANCE_DEFINED_IN_DP = 2; // 0x2
+ field public static final int HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE = 1; // 0x1
+ field public static final int HINT_NO_SCALE = 0; // 0x0
}
public static abstract interface Animator.AnimatorListener {
@@ -4066,6 +4073,48 @@ package android.app {
field public java.lang.String serviceDetails;
}
+ public final class AssistAction {
+ method public static void updateAssistData(android.os.Bundle, android.os.Bundle);
+ field public static final java.lang.String ASSIST_ACTION_KEY = "android:assist_action";
+ field public static final java.lang.String KEY_ACTION_OBJECT = "object";
+ field public static final java.lang.String KEY_ACTION_STATUS = "actionStatus";
+ field public static final java.lang.String KEY_DESCRIPTION = "description";
+ field public static final java.lang.String KEY_ID = "@id";
+ field public static final java.lang.String KEY_NAME = "name";
+ field public static final java.lang.String KEY_TYPE = "@type";
+ field public static final java.lang.String KEY_URL = "url";
+ field public static final java.lang.String STATUS_TYPE_ACTIVE = "ActiveActionStatus";
+ field public static final java.lang.String STATUS_TYPE_COMPLETED = "CompletedActionStatus";
+ field public static final java.lang.String TYPE_ADD_ACTION = "AddAction";
+ field public static final java.lang.String TYPE_BOOKMARK_ACTION = "BookmarkAction";
+ field public static final java.lang.String TYPE_LIKE_ACTION = "LikeAction";
+ field public static final java.lang.String TYPE_LISTEN_ACTION = "ListenAction";
+ field public static final java.lang.String TYPE_VIEW_ACTION = "ViewAction";
+ field public static final java.lang.String TYPE_WANT_ACTION = "WantAction";
+ field public static final java.lang.String TYPE_WATCH_ACTION = "WatchAction";
+ }
+
+ public static final class AssistAction.ActionBuilder {
+ ctor public AssistAction.ActionBuilder();
+ method public android.os.Bundle build();
+ method public android.app.AssistAction.ActionBuilder set(java.lang.String, java.lang.String);
+ method public android.app.AssistAction.ActionBuilder set(java.lang.String, android.os.Bundle);
+ method public android.app.AssistAction.ActionBuilder setObject(android.os.Bundle);
+ method public android.app.AssistAction.ActionBuilder setType(java.lang.String);
+ }
+
+ public static final class AssistAction.ThingBuilder {
+ ctor public AssistAction.ThingBuilder();
+ method public android.os.Bundle build();
+ method public android.app.AssistAction.ThingBuilder set(java.lang.String, java.lang.String);
+ method public android.app.AssistAction.ThingBuilder set(java.lang.String, android.os.Bundle);
+ method public android.app.AssistAction.ThingBuilder setDescription(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setId(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setName(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setType(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setUrl(android.net.Uri);
+ }
+
public class AssistContent implements android.os.Parcelable {
ctor public AssistContent();
method public int describeContents();
@@ -5191,6 +5240,7 @@ package android.app {
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.PendingIntent> CREATOR;
field public static final int FLAG_CANCEL_CURRENT = 268435456; // 0x10000000
+ field public static final int FLAG_IMMUTABLE = 67108864; // 0x4000000
field public static final int FLAG_NO_CREATE = 536870912; // 0x20000000
field public static final int FLAG_ONE_SHOT = 1073741824; // 0x40000000
field public static final int FLAG_UPDATE_CURRENT = 134217728; // 0x8000000
@@ -5407,6 +5457,11 @@ package android.app {
method public void onRejectSharedElements(java.util.List<android.view.View>);
method public void onSharedElementEnd(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
method public void onSharedElementStart(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
+ method public void onSharedElementsArrived(java.util.List<java.lang.String>, java.util.List<android.view.View>, android.app.SharedElementCallback.OnSharedElementsReadyListener);
+ }
+
+ public static abstract interface SharedElementCallback.OnSharedElementsReadyListener {
+ method public abstract void onSharedElementsReady();
}
public deprecated class TabActivity extends android.app.ActivityGroup {
@@ -5914,6 +5969,7 @@ package android.app.backup {
method public android.app.backup.RestoreSession beginRestoreSession();
method public void dataChanged();
method public static void dataChanged(java.lang.String);
+ method public long getAvailableRestoreToken(java.lang.String);
method public java.lang.String getCurrentTransport();
method public boolean isBackupEnabled();
method public java.lang.String[] listAllTransports();
@@ -9416,6 +9472,7 @@ package android.content.pm {
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand";
field public static final java.lang.String FEATURE_GAMEPAD = "android.hardware.gamepad";
+ field public static final java.lang.String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors";
field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen";
field public static final java.lang.String FEATURE_INPUT_METHODS = "android.software.input_methods";
field public static final java.lang.String FEATURE_LEANBACK = "android.software.leanback";
@@ -10015,15 +10072,13 @@ package android.database {
method public void registerDataSetObserver(android.database.DataSetObserver);
method public boolean requery();
method public android.os.Bundle respond(android.os.Bundle);
+ method public void setExtras(android.os.Bundle);
method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public void unregisterContentObserver(android.database.ContentObserver);
method public void unregisterDataSetObserver(android.database.DataSetObserver);
- field protected boolean mClosed;
- field protected android.content.ContentResolver mContentResolver;
- field protected deprecated java.lang.Long mCurrentRowID;
- field protected int mPos;
- field protected deprecated int mRowIdColumnIndex;
- field protected deprecated java.util.HashMap<java.lang.Long, java.util.Map<java.lang.String, java.lang.Object>> mUpdatedRows;
+ field protected deprecated boolean mClosed;
+ field protected deprecated android.content.ContentResolver mContentResolver;
+ field protected deprecated int mPos;
}
protected static class AbstractCursor.SelfContentObserver extends android.database.ContentObserver {
@@ -10123,6 +10178,7 @@ package android.database {
method public abstract void registerDataSetObserver(android.database.DataSetObserver);
method public abstract deprecated boolean requery();
method public abstract android.os.Bundle respond(android.os.Bundle);
+ method public abstract void setExtras(android.os.Bundle);
method public abstract void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public abstract void unregisterContentObserver(android.database.ContentObserver);
method public abstract void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -10194,7 +10250,7 @@ package android.database {
ctor public CursorWrapper(android.database.Cursor);
method public void close();
method public void copyStringToBuffer(int, android.database.CharArrayBuffer);
- method public void deactivate();
+ method public deprecated void deactivate();
method public byte[] getBlob(int);
method public int getColumnCount();
method public int getColumnIndex(java.lang.String);
@@ -10228,8 +10284,9 @@ package android.database {
method public boolean moveToPrevious();
method public void registerContentObserver(android.database.ContentObserver);
method public void registerDataSetObserver(android.database.DataSetObserver);
- method public boolean requery();
+ method public deprecated boolean requery();
method public android.os.Bundle respond(android.os.Bundle);
+ method public void setExtras(android.os.Bundle);
method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public void unregisterContentObserver(android.database.ContentObserver);
method public void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -13312,6 +13369,10 @@ package android.hardware.camera2 {
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> LENS_INFO_FOCUS_DISTANCE_CALIBRATION;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> LENS_INFO_HYPERFOCAL_DISTANCE;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Float> LENS_INFO_MINIMUM_FOCUS_DISTANCE;
+ field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_INTRINSIC_CALIBRATION;
+ field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_POSE_ROTATION;
+ field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_POSE_TRANSLATION;
+ field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_RADIAL_DISTORTION;
field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES;
field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> REQUEST_AVAILABLE_CAPABILITIES;
field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REQUEST_MAX_NUM_INPUT_STREAMS;
@@ -13731,7 +13792,11 @@ package android.hardware.camera2 {
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> LENS_FOCAL_LENGTH;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> LENS_FOCUS_DISTANCE;
field public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Float, java.lang.Float>> LENS_FOCUS_RANGE;
+ field public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_INTRINSIC_CALIBRATION;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> LENS_OPTICAL_STABILIZATION_MODE;
+ field public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_POSE_ROTATION;
+ field public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_POSE_TRANSLATION;
+ field public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_RADIAL_DISTORTION;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> LENS_STATE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> NOISE_REDUCTION_MODE;
field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
@@ -16200,6 +16265,7 @@ package android.media {
field public static final java.lang.String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
field public static final java.lang.String PARAMETER_KEY_SUSPEND = "drop-input-frames";
field public static final java.lang.String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate";
+ field public static final int REASON_RECLAIMED = 1; // 0x1
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
}
@@ -16215,6 +16281,7 @@ package android.media {
public static abstract class MediaCodec.Callback {
ctor public MediaCodec.Callback();
+ method public void onCodecReleased(android.media.MediaCodec, int);
method public abstract void onError(android.media.MediaCodec, android.media.MediaCodec.CodecException);
method public abstract void onInputBufferAvailable(android.media.MediaCodec, int);
method public abstract void onOutputBufferAvailable(android.media.MediaCodec, int, android.media.MediaCodec.BufferInfo);
@@ -16480,6 +16547,7 @@ package android.media {
method public static final boolean isCryptoSchemeSupported(java.util.UUID);
method public final void release();
method public final boolean requiresSecureDecoderComponent(java.lang.String);
+ method public final void setMediaDrmSession(byte[]) throws android.media.MediaCryptoException;
}
public final class MediaCryptoException extends java.lang.Exception {
@@ -16535,6 +16603,8 @@ package android.media {
method public void removeKeys(byte[]);
method public void restoreKeys(byte[], byte[]);
method public void setOnEventListener(android.media.MediaDrm.OnEventListener);
+ method public void setOnExpirationUpdateListener(android.media.MediaDrm.OnExpirationUpdateListener, android.os.Handler);
+ method public void setOnKeysChangeListener(android.media.MediaDrm.OnKeysChangeListener, android.os.Handler);
method public void setPropertyByteArray(java.lang.String, byte[]);
method public void setPropertyString(java.lang.String, java.lang.String);
method public void unprovisionDevice();
@@ -16543,6 +16613,11 @@ package android.media {
field public static final deprecated int EVENT_PROVISION_REQUIRED = 1; // 0x1
field public static final int EVENT_SESSION_RECLAIMED = 5; // 0x5
field public static final int EVENT_VENDOR_DEFINED = 4; // 0x4
+ field public static final int KEY_STATUS_EXPIRED = 1; // 0x1
+ field public static final int KEY_STATUS_INTERNAL_ERROR = 4; // 0x4
+ field public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2; // 0x2
+ field public static final int KEY_STATUS_PENDING = 3; // 0x3
+ field public static final int KEY_STATUS_USABLE = 0; // 0x0
field public static final int KEY_TYPE_OFFLINE = 2; // 0x2
field public static final int KEY_TYPE_RELEASE = 3; // 0x3
field public static final int KEY_TYPE_STREAMING = 1; // 0x1
@@ -16569,6 +16644,11 @@ package android.media {
method public int getRequestType();
}
+ public static final class MediaDrm.KeyStatus {
+ method public byte[] getKeyId();
+ method public int getStatusCode();
+ }
+
public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
method public java.lang.String getDiagnosticInfo();
}
@@ -16577,6 +16657,14 @@ package android.media {
method public abstract void onEvent(android.media.MediaDrm, byte[], int, int, byte[]);
}
+ public static abstract interface MediaDrm.OnExpirationUpdateListener {
+ method public abstract void onExpirationUpdate(android.media.MediaDrm, byte[], long);
+ }
+
+ public static abstract interface MediaDrm.OnKeysChangeListener {
+ method public abstract void onKeysChange(android.media.MediaDrm, byte[], java.util.List<android.media.MediaDrm.KeyStatus>, boolean);
+ }
+
public static final class MediaDrm.ProvisionRequest {
method public byte[] getData();
method public java.lang.String getDefaultUrl();
@@ -16663,6 +16751,7 @@ package android.media {
field public static final java.lang.String KEY_MAX_INPUT_SIZE = "max-input-size";
field public static final java.lang.String KEY_MAX_WIDTH = "max-width";
field public static final java.lang.String KEY_MIME = "mime";
+ field public static final java.lang.String KEY_OPERATING_RATE = "operating-rate";
field public static final java.lang.String KEY_PRIORITY = "priority";
field public static final java.lang.String KEY_PROFILE = "profile";
field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown";
@@ -16785,6 +16874,7 @@ package android.media {
field public static final int METADATA_KEY_ARTIST = 2; // 0x2
field public static final int METADATA_KEY_AUTHOR = 3; // 0x3
field public static final int METADATA_KEY_BITRATE = 20; // 0x14
+ field public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25; // 0x19
field public static final int METADATA_KEY_CD_TRACK_NUMBER = 0; // 0x0
field public static final int METADATA_KEY_COMPILATION = 15; // 0xf
field public static final int METADATA_KEY_COMPOSER = 4; // 0x4
@@ -17190,6 +17280,7 @@ package android.media {
method public void configureAudioTrack(android.media.AudioTrack, int);
method public void configureSurface(android.view.Surface);
method public final android.view.Surface createInputSurface();
+ method public boolean getTimestamp(android.media.MediaTimestamp);
method public void queueAudio(java.nio.ByteBuffer, int, int, long);
method public final void release();
method public void setCallback(android.media.MediaSync.Callback, android.os.Handler);
@@ -17211,6 +17302,13 @@ package android.media {
field public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; // 0x1
}
+ public final class MediaTimestamp {
+ ctor public MediaTimestamp();
+ field public float clockRate;
+ field public long mediaTimeUs;
+ field public long nanoTime;
+ }
+
public final class NotProvisionedException extends android.media.MediaDrmException {
ctor public NotProvisionedException(java.lang.String);
}
@@ -18036,9 +18134,8 @@ package android.media.midi {
method public int describeContents();
method public int getId();
method public int getInputPortCount();
- method public android.media.midi.MidiDeviceInfo.PortInfo getInputPortInfo(int);
method public int getOutputPortCount();
- method public android.media.midi.MidiDeviceInfo.PortInfo getOutputPortInfo(int);
+ method public android.media.midi.MidiDeviceInfo.PortInfo[] getPortList();
method public android.os.Bundle getProperties();
method public int getType();
method public boolean isPrivate();
@@ -18090,11 +18187,17 @@ package android.media.midi {
public final class MidiManager {
method public android.media.midi.MidiDeviceInfo[] getDeviceList();
+ method public void openBluetoothDevice(android.bluetooth.BluetoothDevice, android.media.midi.MidiManager.BluetoothOpenCallback, android.os.Handler);
method public void openDevice(android.media.midi.MidiDeviceInfo, android.media.midi.MidiManager.DeviceOpenCallback, android.os.Handler);
method public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler);
method public void unregisterDeviceCallback(android.media.midi.MidiManager.DeviceCallback);
}
+ public static abstract class MidiManager.BluetoothOpenCallback {
+ ctor public MidiManager.BluetoothOpenCallback();
+ method public abstract void onDeviceOpened(android.bluetooth.BluetoothDevice, android.media.midi.MidiDevice);
+ }
+
public static class MidiManager.DeviceCallback {
ctor public MidiManager.DeviceCallback();
method public void onDeviceAdded(android.media.midi.MidiDeviceInfo);
@@ -18116,6 +18219,7 @@ package android.media.midi {
public abstract class MidiReceiver {
ctor public MidiReceiver();
+ method public void flush() throws java.io.IOException;
method public int getMaxMessageSize();
method public abstract void onReceive(byte[], int, int, long) throws java.io.IOException;
method public void send(byte[], int, int) throws java.io.IOException;
@@ -24117,6 +24221,9 @@ package android.os {
public class BatteryManager {
method public int getIntProperty(int);
method public long getLongProperty(int);
+ method public boolean isCharging();
+ field public static final java.lang.String ACTION_CHARGING = "android.os.action.CHARGING";
+ field public static final java.lang.String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
field public static final int BATTERY_HEALTH_COLD = 7; // 0x7
field public static final int BATTERY_HEALTH_DEAD = 4; // 0x4
field public static final int BATTERY_HEALTH_GOOD = 2; // 0x2
@@ -25166,6 +25273,7 @@ package android.os {
method public android.os.Bundle getApplicationRestrictions(java.lang.String);
method public long getSerialNumberForUser(android.os.UserHandle);
method public int getUserCount();
+ method public long getUserCreationTime(int);
method public android.os.UserHandle getUserForSerialNumber(long);
method public java.lang.String getUserName();
method public java.util.List<android.os.UserHandle> getUserProfiles();
@@ -30232,6 +30340,7 @@ package android.service.persistentdata {
method public abstract byte[] read() throws android.os.RemoteException;
method public abstract void setOemUnlockEnabled(boolean) throws android.os.RemoteException;
method public abstract void wipe() throws android.os.RemoteException;
+ method public abstract void wipeIfAllowed(android.os.Bundle, android.app.PendingIntent) throws android.os.RemoteException;
method public abstract int write(byte[]) throws android.os.RemoteException;
}
@@ -30243,7 +30352,14 @@ package android.service.persistentdata {
method public byte[] read();
method public void setOemUnlockEnabled(boolean);
method public void wipe();
+ method public void wipeIfAllowed(android.os.Bundle, android.app.PendingIntent);
method public int write(byte[]);
+ field public static final java.lang.String ACTION_WIPE_IF_ALLOWED = "android.service.persistentdata.action.WIPE_IF_ALLOWED";
+ field public static final java.lang.String EXTRA_WIPE_IF_ALLOWED_CALLBACK = "android.service.persistentdata.extra.WIPE_IF_ALLOWED_CALLBACK";
+ field public static final int STATUS_ERROR_NETWORK_ERROR = 2; // 0x2
+ field public static final int STATUS_ERROR_NOT_COMPLIANT = 3; // 0x3
+ field public static final int STATUS_ERROR_REMOTE_EXCEPTION = 1; // 0x1
+ field public static final int STATUS_SUCCESS = 0; // 0x0
}
}
@@ -33126,7 +33242,7 @@ package android.test.mock {
ctor public MockCursor();
method public void close();
method public void copyStringToBuffer(int, android.database.CharArrayBuffer);
- method public void deactivate();
+ method public deprecated void deactivate();
method public byte[] getBlob(int);
method public int getColumnCount();
method public int getColumnIndex(java.lang.String);
@@ -33159,8 +33275,9 @@ package android.test.mock {
method public boolean moveToPrevious();
method public void registerContentObserver(android.database.ContentObserver);
method public void registerDataSetObserver(android.database.DataSetObserver);
- method public boolean requery();
+ method public deprecated boolean requery();
method public android.os.Bundle respond(android.os.Bundle);
+ method public void setExtras(android.os.Bundle);
method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public void unregisterContentObserver(android.database.ContentObserver);
method public void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -35112,6 +35229,7 @@ package android.transition {
ctor public TransitionManager();
method public static void beginDelayedTransition(android.view.ViewGroup);
method public static void beginDelayedTransition(android.view.ViewGroup, android.transition.Transition);
+ method public static void endTransitions(android.view.ViewGroup);
method public static void go(android.transition.Scene);
method public static void go(android.transition.Scene, android.transition.Transition);
method public void setTransition(android.transition.Scene, android.transition.Transition);
@@ -36130,10 +36248,10 @@ package android.view {
method public java.lang.String getName();
method public int getProductId();
method public int getSources();
- method public java.lang.String getUniqueId();
method public int getVendorId();
method public android.os.Vibrator getVibrator();
method public boolean[] hasKeys(int...);
+ method public boolean hasMic();
method public boolean isVirtual();
method public boolean supportsSource(int);
method public void writeToParcel(android.os.Parcel, int);
diff --git a/api/system-removed.txt b/api/system-removed.txt
index c2b9d3e..0c433c3 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -6,6 +6,16 @@ package android.content.pm {
}
+package android.database {
+
+ public abstract class AbstractCursor implements android.database.CrossProcessCursor {
+ field protected java.lang.Long mCurrentRowID;
+ field protected int mRowIdColumnIndex;
+ field protected java.util.HashMap<java.lang.Long, java.util.Map<java.lang.String, java.lang.Object>> mUpdatedRows;
+ }
+
+}
+
package android.media {
public class AudioFormat {
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index da48709..02a329d 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -16,7 +16,12 @@
package android.animation;
+import android.content.res.Configuration;
import android.content.res.ConstantState;
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.animation.AnimationUtils;
import java.util.ArrayList;
@@ -25,6 +30,29 @@ import java.util.ArrayList;
* started, ended, and have <code>AnimatorListeners</code> added to them.
*/
public abstract class Animator implements Cloneable {
+ /**
+ * Set this hint when duration for the animation does not need to be scaled. By default, no
+ * scaling is applied to the duration.
+ */
+ public static final int HINT_NO_SCALE = 0;
+
+ /**
+ * Set this scale hint (using {@link #setDurationScaleHint(int, Resources)} when the animation's
+ * moving distance is proportional to the screen size. (e.g. a view coming in from the bottom of
+ * the screen to top/center). With this scale hint set, the animation duration will be
+ * automatically scaled based on screen size.
+ */
+ public static final int HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE = 1;
+
+ /**
+ * Set this scale hint (using {@link #setDurationScaleHint(int, Resources)}) if the animation
+ * has pre-defined moving distance in dp that does not vary from device to device. This is
+ * extremely useful when the animation needs to run on both phones/tablets and TV, because TV
+ * has inflated dp and therefore will have a longer visual arc for the same animation than on
+ * the phone. This hint is used to calculate a scaling factor to compensate for different
+ * visual arcs while maintaining the same angular velocity for the animation.
+ */
+ public static final int HINT_DISTANCE_DEFINED_IN_DP = 2;
/**
* The set of listeners to be sent events through the life of an animation.
@@ -55,6 +83,24 @@ public abstract class Animator implements Cloneable {
private AnimatorConstantState mConstantState;
/**
+ * Scaling factor for an animation that moves across the whole screen.
+ */
+ float mScreenSizeBasedDurationScale = 1.0f;
+
+ /**
+ * Scaling factor for an animation that is defined to move the same amount of dp across all
+ * devices.
+ */
+ float mDpBasedDurationScale = 1.0f;
+
+ /**
+ * By default, the scaling assumes the animation moves across the entire screen.
+ */
+ int mDurationScaleHint = HINT_NO_SCALE;
+
+ private final static boolean ANIM_DEBUG = false;
+
+ /**
* Starts this animation. If the animation has a nonzero startDelay, the animation will start
* running after that delay elapses. A non-delayed animation will have its initial
* value(s) set immediately, followed by calls to
@@ -184,6 +230,78 @@ public abstract class Animator implements Cloneable {
public abstract long getDuration();
/**
+ * Hints how duration scaling factor should be calculated. The duration will not be scaled when
+ * hint is set to {@link #HINT_NO_SCALE}. Otherwise, the duration will be automatically scaled
+ * per device to achieve the same look and feel across different devices. In order to do
+ * that, the same angular velocity of the animation will be needed on different devices in
+ * users' field of view. Therefore, the duration scale factor is determined by the ratio of the
+ * angular movement on current devices to that on the baseline device (i.e. Nexus 5).
+ *
+ * @param hint an indicator on how the animation is defined. The hint could be
+ * {@link #HINT_NO_SCALE}, {@link #HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE} or
+ * {@link #HINT_DISTANCE_DEFINED_IN_DP}.
+ * @param res The resources {@see android.content.res.Resources} for getting display metrics
+ */
+ public void setDurationScaleHint(int hint, Resources res) {
+ if (ANIM_DEBUG) {
+ Log.d("ANIM_DEBUG", "distance based duration hint: " + hint);
+ }
+ if (hint == mDurationScaleHint) {
+ return;
+ }
+ mDurationScaleHint = hint;
+ if (hint != HINT_NO_SCALE) {
+ int uiMode = res.getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK;
+ DisplayMetrics metrics = res.getDisplayMetrics();
+ float width = metrics.widthPixels / metrics.xdpi;
+ float height = metrics.heightPixels / metrics.ydpi;
+ float viewingDistance = AnimationUtils.getViewingDistance(width, height, uiMode);
+ if (ANIM_DEBUG) {
+ Log.d("ANIM_DEBUG", "width, height, viewing distance, uimode: "
+ + width + ", " + height + ", " + viewingDistance + ", " + uiMode);
+ }
+ mScreenSizeBasedDurationScale = AnimationUtils
+ .getScreenSizeBasedDurationScale(width, height, viewingDistance);
+ mDpBasedDurationScale = AnimationUtils.getDpBasedDurationScale(
+ metrics.density, metrics.xdpi, viewingDistance);
+ if (ANIM_DEBUG) {
+ Log.d("ANIM_DEBUG", "screen based scale, dp based scale: " +
+ mScreenSizeBasedDurationScale + ", " + mDpBasedDurationScale);
+ }
+ }
+ }
+
+ // Copies duration scale hint and scaling factors to the new animation.
+ void copyDurationScaleInfoTo(Animator anim) {
+ anim.mDurationScaleHint = mDurationScaleHint;
+ anim.mScreenSizeBasedDurationScale = mScreenSizeBasedDurationScale;
+ anim.mDpBasedDurationScale = mDpBasedDurationScale;
+ }
+
+ /**
+ * @return The scaled duration calculated based on distance of movement (as defined by the
+ * animation) and perceived velocity (derived from the duration set on the animation for
+ * baseline device)
+ */
+ public long getDistanceBasedDuration() {
+ return (long) (getDuration() * getDistanceBasedDurationScale());
+ }
+
+ /**
+ * @return scaling factor of duration based on the duration scale hint. A scaling factor of 1
+ * means no scaling will be applied to the duration.
+ */
+ float getDistanceBasedDurationScale() {
+ if (mDurationScaleHint == HINT_DISTANCE_PROPORTIONAL_TO_SCREEN_SIZE) {
+ return mScreenSizeBasedDurationScale;
+ } else if (mDurationScaleHint == HINT_DISTANCE_DEFINED_IN_DP) {
+ return mDpBasedDurationScale;
+ } else {
+ return 1f;
+ }
+ }
+
+ /**
* The time interpolator used in calculating the elapsed fraction of the
* animation. The interpolator determines whether the animation runs with
* linear or non-linear motion, such as acceleration and deceleration. The
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 81a01ee..df5a4cb 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -68,6 +68,14 @@ public class AnimatorInflater {
private static final int VALUE_TYPE_INT = 1;
private static final int VALUE_TYPE_PATH = 2;
private static final int VALUE_TYPE_COLOR = 3;
+ private static final int VALUE_TYPE_UNDEFINED = 4;
+
+ /**
+ * Enum values used in XML attributes to indicate the duration scale hint.
+ */
+ private static final int HINT_NO_SCALE = 0;
+ private static final int HINT_PROPORTIONAL_TO_SCREEN = 1;
+ private static final int HINT_DEFINED_IN_DP = 2;
private static final boolean DBG_ANIMATOR_INFLATER = false;
@@ -299,8 +307,6 @@ public class AnimatorInflater {
private static PropertyValuesHolder getPVH(TypedArray styledAttributes, int valueType,
int valueFromId, int valueToId, String propertyName) {
- boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
-
TypedValue tvFrom = styledAttributes.peekValue(valueFromId);
boolean hasFrom = (tvFrom != null);
int fromType = hasFrom ? tvFrom.type : 0;
@@ -308,6 +314,17 @@ public class AnimatorInflater {
boolean hasTo = (tvTo != null);
int toType = hasTo ? tvTo.type : 0;
+ if (valueType == VALUE_TYPE_UNDEFINED) {
+ // Check whether it's color type. If not, fall back to default type (i.e. float type)
+ if ((hasFrom && isColorType(fromType)) || (hasTo && isColorType(toType))) {
+ valueType = VALUE_TYPE_COLOR;
+ } else {
+ valueType = VALUE_TYPE_FLOAT;
+ }
+ }
+
+ boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
+
PropertyValuesHolder returnValue = null;
if (valueType == VALUE_TYPE_PATH) {
@@ -341,12 +358,8 @@ public class AnimatorInflater {
} else {
TypeEvaluator evaluator = null;
// Integer and float value types are handled here.
- if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) ||
- (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
+ if (valueType == VALUE_TYPE_COLOR) {
// special case for colors: ignore valueType and get ints
- getFloats = false;
evaluator = ArgbEvaluator.getInstance();
}
if (getFloats) {
@@ -383,8 +396,7 @@ public class AnimatorInflater {
if (hasFrom) {
if (fromType == TypedValue.TYPE_DIMENSION) {
valueFrom = (int) styledAttributes.getDimension(valueFromId, 0f);
- } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ } else if (isColorType(fromType)) {
valueFrom = styledAttributes.getColor(valueFromId, 0);
} else {
valueFrom = styledAttributes.getInt(valueFromId, 0);
@@ -392,8 +404,7 @@ public class AnimatorInflater {
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
- } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ } else if (isColorType(toType)) {
valueTo = styledAttributes.getColor(valueToId, 0);
} else {
valueTo = styledAttributes.getInt(valueToId, 0);
@@ -406,8 +417,7 @@ public class AnimatorInflater {
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
- } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ } else if (isColorType(toType)) {
valueTo = styledAttributes.getColor(valueToId, 0);
} else {
valueTo = styledAttributes.getInt(valueToId, 0);
@@ -613,8 +623,7 @@ public class AnimatorInflater {
if (hasFrom) {
if (fromType == TypedValue.TYPE_DIMENSION) {
valueFrom = (int) arrayAnimator.getDimension(valueFromIndex, 0f);
- } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ } else if (isColorType(fromType)) {
valueFrom = arrayAnimator.getColor(valueFromIndex, 0);
} else {
valueFrom = arrayAnimator.getInt(valueFromIndex, 0);
@@ -622,8 +631,7 @@ public class AnimatorInflater {
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
- } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ } else if (isColorType(toType)) {
valueTo = arrayAnimator.getColor(valueToIndex, 0);
} else {
valueTo = arrayAnimator.getInt(valueToIndex, 0);
@@ -636,8 +644,7 @@ public class AnimatorInflater {
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
- } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ } else if (isColorType(toType)) {
valueTo = arrayAnimator.getColor(valueToIndex, 0);
} else {
valueTo = arrayAnimator.getInt(valueToIndex, 0);
@@ -691,6 +698,9 @@ public class AnimatorInflater {
int ordering = a.getInt(R.styleable.AnimatorSet_ordering, TOGETHER);
createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering,
pixelSize);
+ final int hint = a.getInt(R.styleable.Animator_durationScaleHint,
+ HINT_NO_SCALE);
+ anim.setDurationScaleHint(hint, res);
a.recycle();
} else if (name.equals("propertyValuesHolder")) {
PropertyValuesHolder[] values = loadValues(res, theme, parser,
@@ -749,7 +759,8 @@ public class AnimatorInflater {
}
String propertyName = a.getString(R.styleable.PropertyValuesHolder_propertyName);
int valueType = a.getInt(R.styleable.PropertyValuesHolder_valueType,
- VALUE_TYPE_FLOAT);
+ VALUE_TYPE_UNDEFINED);
+
PropertyValuesHolder pvh = loadPvh(res, theme, parser, propertyName, valueType);
if (pvh == null) {
pvh = getPVH(a, valueType,
@@ -793,6 +804,7 @@ public class AnimatorInflater {
}
}
+ // Load property values holder if there are keyframes defined in it. Otherwise return null.
private static PropertyValuesHolder loadPvh(Resources res, Theme theme, XmlPullParser parser,
String propertyName, int valueType)
throws XmlPullParserException, IOException {
@@ -928,7 +940,17 @@ public class AnimatorInflater {
float fraction = a.getFloat(R.styleable.Keyframe_fraction, -1);
- boolean hasValue = a.peekValue(R.styleable.Keyframe_value) != null;
+ TypedValue keyframeValue = a.peekValue(R.styleable.Keyframe_value);
+ boolean hasValue = (keyframeValue != null);
+ if (valueType == VALUE_TYPE_UNDEFINED) {
+ // When no value type is provided, check whether it's a color type first.
+ // If not, fall back to default value type (i.e. float type).
+ if (hasValue && isColorType(keyframeValue.type)) {
+ valueType = VALUE_TYPE_COLOR;
+ } else {
+ valueType = VALUE_TYPE_FLOAT;
+ }
+ }
if (hasValue) {
switch (valueType) {
@@ -1015,6 +1037,9 @@ public class AnimatorInflater {
anim.setInterpolator(interpolator);
}
+ final int hint = arrayAnimator.getInt(R.styleable.Animator_durationScaleHint,
+ HINT_NO_SCALE);
+ anim.setDurationScaleHint(hint, res);
arrayAnimator.recycle();
if (arrayObjectAnimator != null) {
arrayObjectAnimator.recycle();
@@ -1028,4 +1053,8 @@ public class AnimatorInflater {
return sTmpTypedValue.changingConfigurations;
}
}
+
+ private static boolean isColorType(int type) {
+ return (type >= TypedValue.TYPE_FIRST_COLOR_INT) && (type <= TypedValue.TYPE_LAST_COLOR_INT);
+ }
}
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 53d5237..dd5f18e 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -519,6 +519,7 @@ public final class AnimatorSet extends Animator {
for (Node node : mNodes) {
node.animation.setAllowRunningAsynchronously(false);
+ copyDurationScaleInfoTo(node.animation);
}
if (mDuration >= 0) {
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 2386007..275e78e 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -17,9 +17,12 @@
package android.animation;
import android.annotation.CallSuper;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.os.Looper;
import android.os.Trace;
import android.util.AndroidRuntimeException;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Choreographer;
import android.view.animation.AccelerateDecelerateInterpolator;
@@ -561,7 +564,7 @@ public class ValueAnimator extends Animator {
}
private void updateScaledDuration() {
- mDuration = (long)(mUnscaledDuration * sDurationScale);
+ mDuration = (long)(mUnscaledDuration * sDurationScale * getDistanceBasedDurationScale());
}
/**
@@ -1478,6 +1481,12 @@ public class ValueAnimator extends Animator {
anim.mInitialized = false;
anim.mPlayingState = STOPPED;
anim.mStartedDelay = false;
+ anim.mStarted = false;
+ anim.mRunning = false;
+ anim.mPaused = false;
+ anim.mResumed = false;
+ anim.mStartListenersCalled = false;
+
PropertyValuesHolder[] oldValues = mValues;
if (oldValues != null) {
int numValues = oldValues.length;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 8f125d7..2b35cd4 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -269,45 +269,51 @@ public class ActivityManager {
* all activities that are visible to the user. */
public static final int PROCESS_STATE_TOP = 2;
+ /** @hide Process is hosting a foreground service. */
+ public static final int PROCESS_STATE_FOREGROUND_SERVICE = 3;
+
+ /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
+ public static final int PROCESS_STATE_TOP_SLEEPING = 4;
+
/** @hide Process is important to the user, and something they are aware of. */
- public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 3;
+ public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
/** @hide Process is important to the user, but not something they are aware of. */
- public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 4;
+ public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 6;
/** @hide Process is in the background running a backup/restore operation. */
- public static final int PROCESS_STATE_BACKUP = 5;
+ public static final int PROCESS_STATE_BACKUP = 7;
/** @hide Process is in the background, but it can't restore its state so we want
* to try to avoid killing it. */
- public static final int PROCESS_STATE_HEAVY_WEIGHT = 6;
+ public static final int PROCESS_STATE_HEAVY_WEIGHT = 8;
/** @hide Process is in the background running a service. Unlike oom_adj, this level
* is used for both the normal running in background state and the executing
* operations state. */
- public static final int PROCESS_STATE_SERVICE = 7;
+ public static final int PROCESS_STATE_SERVICE = 9;
/** @hide Process is in the background running a receiver. Note that from the
* perspective of oom_adj receivers run at a higher foreground level, but for our
* prioritization here that is not necessary and putting them below services means
* many fewer changes in some process states as they receive broadcasts. */
- public static final int PROCESS_STATE_RECEIVER = 8;
+ public static final int PROCESS_STATE_RECEIVER = 10;
/** @hide Process is in the background but hosts the home activity. */
- public static final int PROCESS_STATE_HOME = 9;
+ public static final int PROCESS_STATE_HOME = 11;
/** @hide Process is in the background but hosts the last shown activity. */
- public static final int PROCESS_STATE_LAST_ACTIVITY = 10;
+ public static final int PROCESS_STATE_LAST_ACTIVITY = 12;
/** @hide Process is being cached for later use and contains activities. */
- public static final int PROCESS_STATE_CACHED_ACTIVITY = 11;
+ public static final int PROCESS_STATE_CACHED_ACTIVITY = 13;
/** @hide Process is being cached for later use and is a client of another cached
* process that contains activities. */
- public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 12;
+ public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 14;
/** @hide Process is being cached for later use and is empty. */
- public static final int PROCESS_STATE_CACHED_EMPTY = 13;
+ public static final int PROCESS_STATE_CACHED_EMPTY = 15;
/** @hide requestType for assist context: only basic information. */
public static final int ASSIST_CONTEXT_BASIC = 0;
@@ -2064,7 +2070,7 @@ public class ActivityManager {
return ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE;
} else if (procState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
return ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE;
- } else if (procState >= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ } else if (procState >= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
return ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
} else {
return ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ed05321..3b96fd5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5322,19 +5322,25 @@ public final class ActivityThread {
private DropBoxManager dropBox;
- public DropBoxReporter() {
- dropBox = (DropBoxManager) getSystemContext().getSystemService(Context.DROPBOX_SERVICE);
- }
+ public DropBoxReporter() {}
@Override
public void addData(String tag, byte[] data, int flags) {
+ ensureInitialized();
dropBox.addData(tag, data, flags);
}
@Override
public void addText(String tag, String data) {
+ ensureInitialized();
dropBox.addText(tag, data);
}
+
+ private synchronized void ensureInitialized() {
+ if (dropBox == null) {
+ dropBox = (DropBoxManager) getSystemContext().getSystemService(Context.DROPBOX_SERVICE);
+ }
+ }
}
public static void main(String[] args) {
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index 5dd02ae..179957d 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -115,6 +115,40 @@ public class AlarmManager
/** @hide */
public static final long WINDOW_HEURISTIC = -1;
+ /**
+ * Flag for alarms: this is to be a stand-alone alarm, that should not be batched with
+ * other alarms.
+ * @hide
+ */
+ public static final int FLAG_STANDALONE = 1<<0;
+
+ /**
+ * Flag for alarms: this alarm would like to wake the device even if it is idle. This
+ * is, for example, an alarm for an alarm clock.
+ * @hide
+ */
+ public static final int FLAG_WAKE_FROM_IDLE = 1<<1;
+
+ /**
+ * Flag for alarms: this alarm would like to still execute even if the device is
+ * idle. This won't bring the device out of idle, just allow this specific alarm to
+ * run. Note that this means the actual time this alarm goes off can be inconsistent
+ * with the time of non-allow-while-idle alarms (it could go earlier than the time
+ * requested by another alarm).
+ *
+ * @hide
+ */
+ public static final int FLAG_ALLOW_WHILE_IDLE = 1<<2;
+
+ /**
+ * Flag for alarms: this alarm marks the point where we would like to come out of idle
+ * mode. It may be moved by the alarm manager to match the first wake-from-idle alarm.
+ * Scheduling an alarm with this flag puts the alarm manager in to idle mode, where it
+ * avoids scheduling any further alarms until the marker alarm is executed.
+ * @hide
+ */
+ public static final int FLAG_IDLE_UNTIL = 1<<3;
+
private final IAlarmManager mService;
private final boolean mAlwaysExact;
@@ -204,7 +238,7 @@ public class AlarmManager
* @see #RTC_WAKEUP
*/
public void set(int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, legacyExactLength(), 0, operation, null, null);
+ setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null);
}
/**
@@ -265,7 +299,8 @@ public class AlarmManager
*/
public void setRepeating(int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, operation, null, null);
+ setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation, null,
+ null);
}
/**
@@ -315,7 +350,7 @@ public class AlarmManager
*/
public void setWindow(int type, long windowStartMillis, long windowLengthMillis,
PendingIntent operation) {
- setImpl(type, windowStartMillis, windowLengthMillis, 0, operation, null, null);
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation, null, null);
}
/**
@@ -353,7 +388,16 @@ public class AlarmManager
* @see #RTC_WAKEUP
*/
public void setExact(int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, operation, null, null);
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null);
+ }
+
+ /**
+ * Schedule an idle-until alarm, which will keep the alarm manager idle until
+ * the given time.
+ * @hide
+ */
+ public void setIdleUntil(int type, long triggerAtMillis, PendingIntent operation) {
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, operation, null, null);
}
/**
@@ -381,18 +425,19 @@ public class AlarmManager
* @see android.content.Intent#filterEquals
*/
public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
- setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, operation, null, info);
+ setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation, null, info);
}
/** @hide */
@SystemApi
public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
PendingIntent operation, WorkSource workSource) {
- setImpl(type, triggerAtMillis, windowMillis, intervalMillis, operation, workSource, null);
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, workSource,
+ null);
}
private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
- PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) {
+ int flags, PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) {
if (triggerAtMillis < 0) {
/* NOTYET
if (mAlwaysExact) {
@@ -405,7 +450,7 @@ public class AlarmManager
}
try {
- mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation,
+ mService.set(type, triggerAtMillis, windowMillis, intervalMillis, flags, operation,
workSource, alarmClock);
} catch (RemoteException ex) {
}
@@ -506,7 +551,7 @@ public class AlarmManager
*/
public void setInexactRepeating(int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, operation, null, null);
+ setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null, null);
}
/**
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index 3e545f9..abe12dc 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -22,6 +22,7 @@ import android.annotation.ArrayRes;
import android.annotation.AttrRes;
import android.annotation.DrawableRes;
import android.annotation.StringRes;
+import android.annotation.StyleRes;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
@@ -134,6 +135,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
* {@code context}'s theme.
*
* @param context the parent context
+ * @see android.R.styleable#Theme_alertDialogTheme
*/
protected AlertDialog(Context context) {
this(context, 0);
@@ -155,6 +157,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
* {@code context}'s theme.
*
* @param context the parent context
+ * @see android.R.styleable#Theme_alertDialogTheme
*/
protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
this(context, 0);
@@ -187,9 +190,15 @@ public class AlertDialog extends Dialog implements DialogInterface {
* @param themeResId the resource ID of the theme against which to inflate
* this dialog, or {@code 0} to use the parent
* {@code context}'s default alert dialog theme
+ * @see android.R.styleable#Theme_alertDialogTheme
*/
- protected AlertDialog(Context context, @AttrRes int themeResId) {
- super(context, resolveDialogTheme(context, themeResId));
+ protected AlertDialog(Context context, @StyleRes int themeResId) {
+ this(context, themeResId, true);
+ }
+
+ AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
+ super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
+ createContextThemeWrapper);
mWindow.alwaysReadCloseOnTouchAttr();
mAlert = new AlertController(getContext(), this, getWindow());
@@ -428,7 +437,6 @@ public class AlertDialog extends Dialog implements DialogInterface {
public static class Builder {
private final AlertController.AlertParams P;
- private int mThemeResId;
/**
* Creates a builder for an alert dialog that uses the default alert
@@ -473,7 +481,6 @@ public class AlertDialog extends Dialog implements DialogInterface {
public Builder(Context context, int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
- mThemeResId = themeResId;
}
/**
@@ -1075,7 +1082,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
* create and display the dialog.
*/
public AlertDialog create() {
- final AlertDialog dialog = new AlertDialog(P.mContext, mThemeResId);
+ final AlertDialog dialog = new AlertDialog(P.mContext);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
diff --git a/core/java/android/app/AssistAction.java b/core/java/android/app/AssistAction.java
new file mode 100644
index 0000000..eb33542
--- /dev/null
+++ b/core/java/android/app/AssistAction.java
@@ -0,0 +1,277 @@
+/*
+ * 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.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Helper class for building a {@link Bundle} representing an action being performed by the user,
+ * to be included in the Bundle generated by {@link Activity#onProvideAssistData}.
+ *
+ * @see Activity#onProvideAssistData
+ */
+public final class AssistAction {
+
+ /**
+ * Key name for the Bundle containing the schema.org representation of
+ * an action performed, and should be stored in the Bundle generated by
+ * {@link Activity#onProvideAssistData}.
+ */
+ public static final String ASSIST_ACTION_KEY = "android:assist_action";
+
+ /** Bundle key to specify the schema.org ID of the content. */
+ public static final String KEY_ID = "@id";
+
+ /** Bundle key to specify the schema.org type of the content. */
+ public static final String KEY_TYPE = "@type";
+
+ /** Bundle key to specify the name of the content. */
+ public static final String KEY_NAME = "name";
+
+ /** Bundle key to specify the description of the content. */
+ public static final String KEY_DESCRIPTION = "description";
+
+ /** Bundle key to specify the URL of the content. */
+ public static final String KEY_URL = "url";
+
+ /** Bundle key to specify the object of an action. */
+ public static final String KEY_ACTION_OBJECT = "object";
+
+ /** Bundle key to specify the action's status. */
+ public static final String KEY_ACTION_STATUS = "actionStatus";
+
+ /** The act of editing by adding an object to a collection. */
+ public static final String TYPE_ADD_ACTION = "AddAction";
+
+ /** The act of bookmarking an object. */
+ public static final String TYPE_BOOKMARK_ACTION = "BookmarkAction";
+
+ /** The act of liking an object. */
+ public static final String TYPE_LIKE_ACTION = "LikeAction";
+
+ /** The act of consuming audio content. */
+ public static final String TYPE_LISTEN_ACTION = "ListenAction";
+
+ /** The act of consuming static visual content. */
+ public static final String TYPE_VIEW_ACTION = "ViewAction";
+
+ /** The act of expressing a desire about the object. */
+ public static final String TYPE_WANT_ACTION = "WantAction";
+
+ /** The act of watching an object. */
+ public static final String TYPE_WATCH_ACTION = "WatchAction";
+
+ /** The status of an active action. */
+ public static final String STATUS_TYPE_ACTIVE = "ActiveActionStatus";
+
+ /** The status of a completed action. */
+ public static final String STATUS_TYPE_COMPLETED = "CompletedActionStatus";
+
+ private AssistAction() {
+ }
+
+ /**
+ * Update the Bundle passed into {@link Activity#onProvideAssistData} with the action Bundle,
+ * built with {@link ActionBuilder}.
+ *
+ * @param assistDataBundle The Bundle provided to {@link Activity#onProvideAssistData}.
+ * @param actionBundle The Bundle representing an schema.org action.
+ */
+ public static void updateAssistData(Bundle assistDataBundle, Bundle actionBundle) {
+ Preconditions.checkNotNull(assistDataBundle);
+ Preconditions.checkNotNull(actionBundle);
+
+ Preconditions.checkNotNull(actionBundle.getString(KEY_TYPE),
+ "The '@type' property is required in the provided actionBundle");
+ assistDataBundle.putParcelable(ASSIST_ACTION_KEY, actionBundle);
+ }
+
+ /**
+ * Builds a {@link Bundle} representing a schema.org entity.
+ */
+ public static final class ThingBuilder {
+ private final Bundle mBundle;
+
+ public ThingBuilder() {
+ mBundle = new Bundle();
+ }
+
+ /**
+ * Sets the name of the content.
+ *
+ * @param name The name of the content.
+ */
+ public ThingBuilder setName(@Nullable String name) {
+ set(KEY_NAME, name);
+ return this;
+ }
+
+ /**
+ * Sets the app URI of the content.
+ *
+ * @param uri The app URI of the content.
+ */
+ public ThingBuilder setUrl(@Nullable Uri uri) {
+ if (uri != null) {
+ set(KEY_URL, uri.toString());
+ }
+ return this;
+ }
+
+ /**
+ * Sets the ID of the content.
+ *
+ * @param id Set the ID of the content.
+ */
+ public ThingBuilder setId(@Nullable String id) {
+ set(KEY_ID, id);
+ return this;
+ }
+
+ /**
+ * Sets the schema.org type of the content.
+ *
+ * @param type The schema.org type.
+ */
+ public ThingBuilder setType(@Nullable String type) {
+ set(KEY_TYPE, type);
+ return this;
+ }
+
+ /**
+ * Sets the optional description of the content.
+ *
+ * @param description The description of the content.
+ */
+ public ThingBuilder setDescription(@Nullable String description) {
+ set(KEY_DESCRIPTION, description);
+ return this;
+ }
+
+ /**
+ * Sets a property of the content.
+ *
+ * @param key The schema.org property. Must not be null.
+ * @param value The value of the schema.org property.
+ * If null, the value will be ignored.
+ */
+ public ThingBuilder set(@NonNull String key, @Nullable String value) {
+ if (value != null) {
+ mBundle.putString(key, value);
+ }
+ return this;
+ }
+
+ /**
+ * Sets a property of the content.
+ *
+ * @param key The schema.org property. Must not be null.
+ * @param value The value of the schema.org property represented as a bundle.
+ * If null, the value will be ignored.
+ */
+ public ThingBuilder set(@NonNull String key, @Nullable Bundle value) {
+ if (value != null) {
+ mBundle.putParcelable(key, value);
+ }
+ return this;
+ }
+
+ /**
+ * Build the {@link Bundle} object representing the schema.org entity.
+ */
+ public Bundle build() {
+ return mBundle;
+ }
+ }
+
+ /**
+ * Builds a {@link Bundle} representing a schema.org action.
+ */
+ public static final class ActionBuilder {
+ private final Bundle mBundle;
+
+ public ActionBuilder() {
+ mBundle = new Bundle();
+ }
+
+ /**
+ * Sets the schema.org type of the action.
+ *
+ * @param type The schema.org type.
+ */
+ public ActionBuilder setType(@Nullable String type) {
+ set(KEY_TYPE, type);
+ return this;
+ }
+
+ /**
+ * Sets the schema.org object of the action.
+ *
+ * @param object The schema.org object of the action.
+ */
+ public ActionBuilder setObject(@Nullable Bundle object) {
+ set(KEY_ACTION_OBJECT, object);
+ return this;
+ }
+
+ /**
+ * Sets a property of the action.
+ *
+ * @param key The schema.org property. Must not be null.
+ * @param value The value of the schema.org property.
+ * If null, the value will be ignored.
+ */
+ public ActionBuilder set(@NonNull String key, @Nullable String value) {
+ if (value != null) {
+ mBundle.putString(key, value);
+ }
+ return this;
+ }
+
+ /**
+ * Sets a property of the action.
+ *
+ * @param key The schema.org property. Must not be null.
+ * @param value The value of the schema.org property represented as a bundle.
+ * If null, the value will be ignored.
+ */
+ public ActionBuilder set(@NonNull String key, @Nullable Bundle value) {
+ if (value != null) {
+ mBundle.putParcelable(key, value);
+ }
+ return this;
+ }
+
+ /**
+ * Build the {@link Bundle} object representing the schema.org action.
+ */
+ public Bundle build() {
+ if (TextUtils.isEmpty(mBundle.getString(KEY_TYPE, null))) {
+ // Defaults to the base action type http://schema.org/Action.
+ setType("Action");
+ }
+
+ return mBundle;
+ }
+ }
+}
diff --git a/core/java/android/app/AssistContent.java b/core/java/android/app/AssistContent.java
index ace4af7..cb1a3f5 100644
--- a/core/java/android/app/AssistContent.java
+++ b/core/java/android/app/AssistContent.java
@@ -51,7 +51,7 @@ public class AssistContent implements Parcelable {
/**
* Sets the Intent associated with the content, describing the current top-level context of
* the activity. If this contains a reference to a piece of data related to the activity,
- * be sure to set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} so the accessibilty
+ * be sure to set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} so the accessibility
* service can access it.
*/
public void setIntent(Intent intent) {
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 9defcbe..786a52f 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -20,10 +20,11 @@ import android.annotation.CallSuper;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
import android.annotation.LayoutRes;
+import android.annotation.NonNull;
import android.annotation.StringRes;
-import com.android.internal.app.WindowDecorActionBar;
import android.annotation.Nullable;
+import android.annotation.StyleRes;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
@@ -56,6 +57,9 @@ import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import com.android.internal.R;
+import com.android.internal.app.WindowDecorActionBar;
+
import java.lang.ref.WeakReference;
/**
@@ -130,52 +134,58 @@ public class Dialog implements DialogInterface, Window.Callback,
};
/**
- * Create a Dialog window that uses the default dialog frame style.
- *
- * @param context The Context the Dialog is to run it. In particular, it
- * uses the window manager and theme in this context to
- * present its UI.
+ * Creates a dialog window that uses the default dialog theme.
+ * <p>
+ * The supplied {@code context} is used to obtain the window manager and
+ * base theme used to present the dialog.
+ *
+ * @param context the context in which the dialog should run
+ * @see android.R.styleable#Theme_dialogTheme
*/
- public Dialog(Context context) {
+ public Dialog(@NonNull Context context) {
this(context, 0, true);
}
/**
- * Create a Dialog window that uses a custom dialog style.
+ * Creates a dialog window that uses a custom dialog style.
+ * <p>
+ * The supplied {@code context} is used to obtain the window manager and
+ * base theme used to present the dialog.
+ * <p>
+ * The supplied {@code theme} is applied on top of the context's theme. See
+ * <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">
+ * Style and Theme Resources</a> for more information about defining and
+ * using styles.
*
- * @param context The Context in which the Dialog should run. In particular, it
- * uses the window manager and theme from this context to
- * present its UI.
- * @param theme A style resource describing the theme to use for the
- * window. See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Style
- * and Theme Resources</a> for more information about defining and using
- * styles. This theme is applied on top of the current theme in
- * <var>context</var>. If 0, the default dialog theme will be used.
+ * @param context the context in which the dialog should run
+ * @param themeResId a style resource describing the theme to use for the
+ * window, or {@code 0} to use the default dialog theme
*/
- public Dialog(Context context, int theme) {
- this(context, theme, true);
+ public Dialog(@NonNull Context context, @StyleRes int themeResId) {
+ this(context, themeResId, true);
}
- Dialog(Context context, int theme, boolean createContextThemeWrapper) {
+ Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
- if (theme == 0) {
- TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
- outValue, true);
- theme = outValue.resourceId;
+ if (themeResId == 0) {
+ final TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
+ themeResId = outValue.resourceId;
}
- mContext = new ContextThemeWrapper(context, theme);
+ mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
- mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
- Window w = new PhoneWindow(mContext);
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+
+ final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
+
mListenersHandler = new ListenersHandler(this);
}
@@ -184,14 +194,13 @@ public class Dialog implements DialogInterface, Window.Callback,
* @hide
*/
@Deprecated
- protected Dialog(Context context, boolean cancelable,
- Message cancelCallback) {
+ protected Dialog(@NonNull Context context, boolean cancelable, Message cancelCallback) {
this(context);
mCancelable = cancelable;
mCancelMessage = cancelCallback;
}
- protected Dialog(Context context, boolean cancelable,
+ protected Dialog(@NonNull Context context, boolean cancelable,
OnCancelListener cancelListener) {
this(context);
mCancelable = cancelable;
@@ -203,6 +212,7 @@ public class Dialog implements DialogInterface, Window.Callback,
*
* @return Context The Context used by the Dialog.
*/
+ @NonNull
public final Context getContext() {
return mContext;
}
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index c053c83..e84a8da 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -18,6 +18,7 @@ package android.app;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.app.SharedElementCallback.OnSharedElementsReadyListener;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.ResultReceiver;
@@ -140,13 +141,13 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
} else {
decor.getViewTreeObserver()
.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- decor.getViewTreeObserver().removeOnPreDrawListener(this);
- viewsReady(sharedElements);
- return true;
- }
- });
+ @Override
+ public boolean onPreDraw() {
+ decor.getViewTreeObserver().removeOnPreDrawListener(this);
+ viewsReady(sharedElements);
+ return true;
+ }
+ });
}
}
@@ -383,23 +384,33 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
}
final Bundle sharedElementState = mSharedElementsBundle;
mSharedElementsBundle = null;
- final View decorView = getDecor();
- if (decorView != null) {
- decorView.getViewTreeObserver()
- .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- decorView.getViewTreeObserver().removeOnPreDrawListener(this);
- startTransition(new Runnable() {
+ OnSharedElementsReadyListener listener = new OnSharedElementsReadyListener() {
+ @Override
+ public void onSharedElementsReady() {
+ final View decorView = getDecor();
+ if (decorView != null) {
+ decorView.getViewTreeObserver()
+ .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
- public void run() {
- startSharedElementTransition(sharedElementState);
+ public boolean onPreDraw() {
+ decorView.getViewTreeObserver().removeOnPreDrawListener(this);
+ startTransition(new Runnable() {
+ @Override
+ public void run() {
+ startSharedElementTransition(sharedElementState);
+ }
+ });
+ return false;
}
});
- return false;
- }
- });
- decorView.invalidate();
+ decorView.invalidate();
+ }
+ }
+ };
+ if (mListener == null) {
+ listener.onSharedElementsReady();
+ } else {
+ mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, listener);
}
}
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index dd3df47..169952a 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -18,6 +18,7 @@ package android.app;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.app.SharedElementCallback.OnSharedElementsReadyListener;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Matrix;
@@ -27,6 +28,7 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.ResultReceiver;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.view.View;
@@ -408,18 +410,38 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
if (!mSharedElementNotified) {
mSharedElementNotified = true;
delayCancel();
- mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
- }
- if (!mExitNotified && mExitComplete) {
- mExitNotified = true;
- mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
- mResultReceiver = null; // done talking
- ViewGroup decorView = getDecor();
- if (!mIsReturning && decorView != null) {
- decorView.suppressLayout(false);
+ if (mListener == null) {
+ mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
+ notifyExitComplete();
+ } else {
+ final ResultReceiver resultReceiver = mResultReceiver;
+ final Bundle sharedElementBundle = mSharedElementBundle;
+ mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements,
+ new OnSharedElementsReadyListener() {
+ @Override
+ public void onSharedElementsReady() {
+ resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS,
+ sharedElementBundle);
+ notifyExitComplete();
+ }
+ });
}
- finishIfNecessary();
+ } else {
+ notifyExitComplete();
+ }
+ }
+ }
+
+ private void notifyExitComplete() {
+ if (!mExitNotified && mExitComplete) {
+ mExitNotified = true;
+ mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
+ mResultReceiver = null; // done talking
+ ViewGroup decorView = getDecor();
+ if (!mIsReturning && decorView != null) {
+ decorView.suppressLayout(false);
}
+ finishIfNecessary();
}
}
diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl
index 194082e..d5719f5 100644
--- a/core/java/android/app/IAlarmManager.aidl
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -28,7 +28,7 @@ import android.os.WorkSource;
interface IAlarmManager {
/** windowLength == 0 means exact; windowLength < 0 means the let the OS decide */
void set(int type, long triggerAtTime, long windowLength,
- long interval, in PendingIntent operation, in WorkSource workSource,
+ long interval, int flags, in PendingIntent operation, in WorkSource workSource,
in AlarmManager.AlarmClockInfo alarmClock);
boolean setTime(long millis);
void setTimeZone(String zone);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 33262b3..e2230da 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -76,12 +76,10 @@ interface INotificationManager
boolean matchesCallFilter(in Bundle extras);
boolean isSystemConditionProviderEnabled(String path);
+ int getZenMode();
ZenModeConfig getZenModeConfig();
- boolean setZenModeConfig(in ZenModeConfig config);
- oneway void setZenMode(int mode);
+ boolean setZenModeConfig(in ZenModeConfig config, String reason);
+ oneway void setZenMode(int mode, in Uri conditionId, String reason);
oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
oneway void requestZenModeConditions(in IConditionListener callback, int relevance);
- oneway void setZenModeCondition(in Condition condition);
- oneway void setAutomaticZenModeConditions(in Uri[] conditionIds);
- Condition[] getAutomaticZenModeConditions();
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 479327d..fa61e18 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -20,6 +20,7 @@ import android.annotation.SdkConstant;
import android.app.Notification.Builder;
import android.content.ComponentName;
import android.content.Context;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -27,7 +28,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.UserHandle;
-import android.service.notification.Condition;
+import android.provider.Settings.Global;
import android.service.notification.IConditionListener;
import android.service.notification.ZenModeConfig;
import android.util.Log;
@@ -282,10 +283,10 @@ public class NotificationManager
/**
* @hide
*/
- public void setZenMode(int mode) {
+ public void setZenMode(int mode, Uri conditionId, String reason) {
INotificationManager service = getService();
try {
- service.setZenMode(mode);
+ service.setZenMode(mode, conditionId, reason);
} catch (RemoteException e) {
}
}
@@ -293,6 +294,18 @@ public class NotificationManager
/**
* @hide
*/
+ public boolean setZenModeConfig(ZenModeConfig config, String reason) {
+ INotificationManager service = getService();
+ try {
+ return service.setZenModeConfig(config, reason);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * @hide
+ */
public void requestZenModeConditions(IConditionListener listener, int relevance) {
INotificationManager service = getService();
try {
@@ -304,24 +317,22 @@ public class NotificationManager
/**
* @hide
*/
- public void setZenModeCondition(Condition exitCondition) {
+ public int getZenMode() {
INotificationManager service = getService();
try {
- service.setZenModeCondition(exitCondition);
+ return service.getZenMode();
} catch (RemoteException e) {
}
+ return Global.ZEN_MODE_OFF;
}
/**
* @hide
*/
- public Condition getZenModeCondition() {
+ public ZenModeConfig getZenModeConfig() {
INotificationManager service = getService();
try {
- final ZenModeConfig config = service.getZenModeConfig();
- if (config != null) {
- return config.exitCondition;
- }
+ return service.getZenModeConfig();
} catch (RemoteException e) {
}
return null;
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index cf14202..2cfc1fa4 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -151,6 +151,14 @@ public final class PendingIntent implements Parcelable {
public static final int FLAG_UPDATE_CURRENT = 1<<27;
/**
+ * Flag indicating that the created PendingIntent should be immutable.
+ * This means that the additional intent argument passed to the send
+ * methods to fill in unpopulated properties of this intent will be
+ * ignored.
+ */
+ public static final int FLAG_IMMUTABLE = 1<<26;
+
+ /**
* Exception thrown when trying to send through a PendingIntent that
* has been canceled or is otherwise no longer able to execute the request.
*/
@@ -618,7 +626,8 @@ public final class PendingIntent implements Parcelable {
* @param code Result code to supply back to the PendingIntent's target.
* @param intent Additional Intent data. See {@link Intent#fillIn
* Intent.fillIn()} for information on how this is applied to the
- * original Intent.
+ * original Intent. If flag {@link #FLAG_IMMUTABLE} was set when this
+ * pending intent was created, this argument will be ignored.
*
* @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler)
*
@@ -667,6 +676,8 @@ public final class PendingIntent implements Parcelable {
* @param intent Additional Intent data. See {@link Intent#fillIn
* Intent.fillIn()} for information on how this is applied to the
* original Intent. Use null to not modify the original Intent.
+ * If flag {@link #FLAG_IMMUTABLE} was set when this pending intent was
+ * created, this argument will be ignored.
* @param onFinished The object to call back on when the send has
* completed, or null for no callback.
* @param handler Handler identifying the thread on which the callback
@@ -703,6 +714,8 @@ public final class PendingIntent implements Parcelable {
* @param intent Additional Intent data. See {@link Intent#fillIn
* Intent.fillIn()} for information on how this is applied to the
* original Intent. Use null to not modify the original Intent.
+ * If flag {@link #FLAG_IMMUTABLE} was set when this pending intent was
+ * created, this argument will be ignored.
* @param onFinished The object to call back on when the send has
* completed, or null for no callback.
* @param handler Handler identifying the thread on which the callback
diff --git a/core/java/android/app/SharedElementCallback.java b/core/java/android/app/SharedElementCallback.java
index 6ac2401..e58b7fb 100644
--- a/core/java/android/app/SharedElementCallback.java
+++ b/core/java/android/app/SharedElementCallback.java
@@ -221,4 +221,42 @@ public abstract class SharedElementCallback {
}
return view;
}
+
+ /**
+ * Called during an Activity Transition when the shared elements have arrived at the
+ * final location and are ready to be transferred. This method is called for both the
+ * source and destination Activities.
+ * <p>
+ * When the shared elements are ready to be transferred,
+ * {@link OnSharedElementsReadyListener#onSharedElementsReady()}
+ * must be called to trigger the transfer.
+ * <p>
+ * The default behavior is to trigger the transfer immediately.
+ *
+ * @param sharedElementNames The names of the shared elements that are being transferred..
+ * @param sharedElements The shared elements that are part of the View hierarchy.
+ * @param listener The listener to call when the shared elements are ready to be hidden
+ * in the source Activity or shown in the destination Activity.
+ */
+ public void onSharedElementsArrived(List<String> sharedElementNames,
+ List<View> sharedElements, OnSharedElementsReadyListener listener) {
+ listener.onSharedElementsReady();
+ }
+
+ /**
+ * Listener to be called after {@link
+ * SharedElementCallback#onSharedElementsArrived(List, List, OnSharedElementsReadyListener)}
+ * when the shared elements are ready to be hidden in the source Activity and shown in the
+ * destination Activity.
+ */
+ public interface OnSharedElementsReadyListener {
+
+ /**
+ * Call this method during or after the OnSharedElementsReadyListener has been received
+ * in {@link SharedElementCallback#onSharedElementsArrived(List, List,
+ * OnSharedElementsReadyListener)} to indicate that the shared elements are ready to be
+ * hidden in the source and shown in the destination Activity.
+ */
+ void onSharedElementsReady();
+ }
}
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 7acf5f0..022a62c 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -318,7 +318,7 @@ public class VoiceInteractor {
* @param label The label that will both be matched against what the user speaks
* and displayed visually.
* @param index The location of this option within the overall set of options.
- * Can be used to help identify which the option when it is returned from the
+ * Can be used to help identify the option when it is returned from the
* voice interactor.
*/
public Option(CharSequence label, int index) {
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 9151a16..8b79305 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -339,4 +339,30 @@ public class BackupManager {
}
}
}
+
+ /**
+ * Ask the framework which dataset, if any, the given package's data would be
+ * restored from if we were to install it right now.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param packageName The name of the package whose most-suitable dataset we
+ * wish to look up
+ * @return The dataset token from which a restore should be attempted, or zero if
+ * no suitable data is available.
+ *
+ * @hide
+ */
+ @SystemApi
+ public long getAvailableRestoreToken(String packageName) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ return sService.getAvailableRestoreToken(packageName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getAvailableRestoreToken() couldn't connect");
+ }
+ }
+ return 0;
+ }
}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 8f36dc4..87e4ef1 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -313,4 +313,17 @@ interface IBackupManager {
* is being queried.
*/
boolean isBackupServiceActive(int whichUser);
+
+ /**
+ * Ask the framework which dataset, if any, the given package's data would be
+ * restored from if we were to install it right now.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param packageName The name of the package whose most-suitable dataset we
+ * wish to look up
+ * @return The dataset token from which a restore should be attempted, or zero if
+ * no suitable data is available.
+ */
+ long getAvailableRestoreToken(String packageName);
}
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index 93ea299..5dbfa6a 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -36,7 +36,7 @@ import java.util.UUID;
/**
* This class provides methods to perform scan related operations for Bluetooth LE devices. An
- * application can scan for a particular type of Bluetotoh LE devices using {@link ScanFilter}. It
+ * application can scan for a particular type of Bluetooth LE devices using {@link ScanFilter}. It
* can also request different types of callbacks for delivering the result.
* <p>
* Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6532563..0396660 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1318,6 +1318,15 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports high fidelity sensor processing
+ * capabilities.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_HIFI_SENSORS =
+ "android.hardware.sensor.hifi_sensors";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device has a telephony radio with data
* communication support.
*/
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index b5b89dd..581fe7f 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -35,34 +35,38 @@ public abstract class AbstractCursor implements CrossProcessCursor {
private static final String TAG = "Cursor";
/**
- * @deprecated This is never updated by this class and should not be used
+ * @removed This field should not be used.
*/
- @Deprecated
protected HashMap<Long, Map<String, Object>> mUpdatedRows;
- protected int mPos;
-
/**
- * This must be set to the index of the row ID column by any
- * subclass that wishes to support updates.
- *
- * @deprecated This field should not be used.
+ * @removed This field should not be used.
*/
- @Deprecated
protected int mRowIdColumnIndex;
/**
- * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of
- * the column at {@link #mRowIdColumnIndex} for the current row this cursor is
- * pointing at.
- *
- * @deprecated This field should not be used.
+ * @removed This field should not be used.
*/
- @Deprecated
protected Long mCurrentRowID;
+ /**
+ * @deprecated Use {@link #getPosition()} instead.
+ */
+ @Deprecated
+ protected int mPos;
+
+ /**
+ * @deprecated Use {@link #isClosed()} instead.
+ */
+ @Deprecated
protected boolean mClosed;
+
+ /**
+ * @deprecated Do not use.
+ */
+ @Deprecated
protected ContentResolver mContentResolver;
+
private Uri mNotifyUri;
private final Object mSelfObserverLock = new Object();
@@ -76,18 +80,28 @@ public abstract class AbstractCursor implements CrossProcessCursor {
/* -------------------------------------------------------- */
/* These need to be implemented by subclasses */
+ @Override
abstract public int getCount();
+ @Override
abstract public String[] getColumnNames();
+ @Override
abstract public String getString(int column);
+ @Override
abstract public short getShort(int column);
+ @Override
abstract public int getInt(int column);
+ @Override
abstract public long getLong(int column);
+ @Override
abstract public float getFloat(int column);
+ @Override
abstract public double getDouble(int column);
+ @Override
abstract public boolean isNull(int column);
+ @Override
public int getType(int column) {
// Reflects the assumption that all commonly used field types (meaning everything
// but blobs) are convertible to strings so it should be safe to call
@@ -96,6 +110,7 @@ public abstract class AbstractCursor implements CrossProcessCursor {
}
// TODO implement getBlob in all cursor types
+ @Override
public byte[] getBlob(int column) {
throw new UnsupportedOperationException("getBlob is not supported");
}
@@ -108,14 +123,17 @@ public abstract class AbstractCursor implements CrossProcessCursor {
*
* @return The pre-filled window that backs this cursor, or null if none.
*/
+ @Override
public CursorWindow getWindow() {
return null;
}
+ @Override
public int getColumnCount() {
return getColumnNames().length;
}
+ @Override
public void deactivate() {
onDeactivateOrClose();
}
@@ -129,6 +147,7 @@ public abstract class AbstractCursor implements CrossProcessCursor {
mDataSetObservable.notifyInvalidated();
}
+ @Override
public boolean requery() {
if (mSelfObserver != null && mSelfObserverRegistered == false) {
mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
@@ -138,10 +157,12 @@ public abstract class AbstractCursor implements CrossProcessCursor {
return true;
}
+ @Override
public boolean isClosed() {
return mClosed;
}
+ @Override
public void close() {
mClosed = true;
mContentObservable.unregisterAll();
@@ -158,11 +179,13 @@ public abstract class AbstractCursor implements CrossProcessCursor {
* @param newPosition the position that we're moving to
* @return true if the move is successful, false otherwise
*/
+ @Override
public boolean onMove(int oldPosition, int newPosition) {
return true;
}
+ @Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
// Default implementation, uses getString
String result = getString(columnIndex);
@@ -183,15 +206,14 @@ public abstract class AbstractCursor implements CrossProcessCursor {
/* Implementation */
public AbstractCursor() {
mPos = -1;
- mRowIdColumnIndex = -1;
- mCurrentRowID = null;
- mUpdatedRows = new HashMap<Long, Map<String, Object>>();
}
+ @Override
public final int getPosition() {
return mPos;
}
+ @Override
public final boolean moveToPosition(int position) {
// Make sure position isn't past the end of the cursor
final int count = getCount();
@@ -216,9 +238,6 @@ public abstract class AbstractCursor implements CrossProcessCursor {
mPos = -1;
} else {
mPos = position;
- if (mRowIdColumnIndex != -1) {
- mCurrentRowID = Long.valueOf(getLong(mRowIdColumnIndex));
- }
}
return result;
@@ -229,35 +248,43 @@ public abstract class AbstractCursor implements CrossProcessCursor {
DatabaseUtils.cursorFillWindow(this, position, window);
}
+ @Override
public final boolean move(int offset) {
return moveToPosition(mPos + offset);
}
+ @Override
public final boolean moveToFirst() {
return moveToPosition(0);
}
+ @Override
public final boolean moveToLast() {
return moveToPosition(getCount() - 1);
}
+ @Override
public final boolean moveToNext() {
return moveToPosition(mPos + 1);
}
+ @Override
public final boolean moveToPrevious() {
return moveToPosition(mPos - 1);
}
+ @Override
public final boolean isFirst() {
return mPos == 0 && getCount() != 0;
}
+ @Override
public final boolean isLast() {
int cnt = getCount();
return mPos == (cnt - 1) && cnt != 0;
}
+ @Override
public final boolean isBeforeFirst() {
if (getCount() == 0) {
return true;
@@ -265,6 +292,7 @@ public abstract class AbstractCursor implements CrossProcessCursor {
return mPos == -1;
}
+ @Override
public final boolean isAfterLast() {
if (getCount() == 0) {
return true;
@@ -272,6 +300,7 @@ public abstract class AbstractCursor implements CrossProcessCursor {
return mPos == getCount();
}
+ @Override
public int getColumnIndex(String columnName) {
// Hack according to bug 903852
final int periodIndex = columnName.lastIndexOf('.');
@@ -297,6 +326,7 @@ public abstract class AbstractCursor implements CrossProcessCursor {
return -1;
}
+ @Override
public int getColumnIndexOrThrow(String columnName) {
final int index = getColumnIndex(columnName);
if (index < 0) {
@@ -305,14 +335,17 @@ public abstract class AbstractCursor implements CrossProcessCursor {
return index;
}
+ @Override
public String getColumnName(int columnIndex) {
return getColumnNames()[columnIndex];
}
+ @Override
public void registerContentObserver(ContentObserver observer) {
mContentObservable.registerObserver(observer);
}
+ @Override
public void unregisterContentObserver(ContentObserver observer) {
// cursor will unregister all observers when it close
if (!mClosed) {
@@ -320,10 +353,12 @@ public abstract class AbstractCursor implements CrossProcessCursor {
}
}
+ @Override
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
+ @Override
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
@@ -350,6 +385,7 @@ public abstract class AbstractCursor implements CrossProcessCursor {
* @param notifyUri The URI to watch for changes. This can be a
* specific row URI, or a base URI for a whole class of content.
*/
+ @Override
public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
setNotificationUri(cr, notifyUri, UserHandle.myUserId());
}
@@ -368,31 +404,29 @@ public abstract class AbstractCursor implements CrossProcessCursor {
}
}
+ @Override
public Uri getNotificationUri() {
synchronized (mSelfObserverLock) {
return mNotifyUri;
}
}
+ @Override
public boolean getWantsAllOnMoveCalls() {
return false;
}
- /**
- * Sets a {@link Bundle} that will be returned by {@link #getExtras()}. <code>null</code> will
- * be converted into {@link Bundle#EMPTY}.
- *
- * @param extras {@link Bundle} to set.
- * @hide
- */
+ @Override
public void setExtras(Bundle extras) {
mExtras = (extras == null) ? Bundle.EMPTY : extras;
}
+ @Override
public Bundle getExtras() {
return mExtras;
}
+ @Override
public Bundle respond(Bundle extras) {
return Bundle.EMPTY;
}
diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java
index 98c7043..8576715 100644
--- a/core/java/android/database/BulkCursorToCursorAdaptor.java
+++ b/core/java/android/database/BulkCursorToCursorAdaptor.java
@@ -41,7 +41,6 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
public void initialize(BulkCursorDescriptor d) {
mBulkCursor = d.cursor;
mColumns = d.columnNames;
- mRowIdColumnIndex = DatabaseUtils.findRowIdColumnIndex(mColumns);
mWantsAllOnMoveCalls = d.wantsAllOnMoveCalls;
mCount = d.count;
if (d.window != null) {
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index fc2a885..d10c9b8 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -444,6 +444,13 @@ public interface Cursor extends Closeable {
boolean getWantsAllOnMoveCalls();
/**
+ * Sets a {@link Bundle} that will be returned by {@link #getExtras()}.
+ *
+ * @param extras {@link Bundle} to set, or null to set an empty bundle.
+ */
+ void setExtras(Bundle extras);
+
+ /**
* Returns a bundle of extra values. This is an optional way for cursors to provide out-of-band
* metadata to their users. One use of this is for reporting on the progress of network requests
* that are required to fetch data for the cursor.
diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java
index d8fcb17..63a2792 100644
--- a/core/java/android/database/CursorWrapper.java
+++ b/core/java/android/database/CursorWrapper.java
@@ -45,163 +45,210 @@ public class CursorWrapper implements Cursor {
return mCursor;
}
+ @Override
public void close() {
mCursor.close();
}
+ @Override
public boolean isClosed() {
return mCursor.isClosed();
}
+ @Override
public int getCount() {
return mCursor.getCount();
}
+ @Override
+ @Deprecated
public void deactivate() {
mCursor.deactivate();
}
+ @Override
public boolean moveToFirst() {
return mCursor.moveToFirst();
}
+ @Override
public int getColumnCount() {
return mCursor.getColumnCount();
}
+ @Override
public int getColumnIndex(String columnName) {
return mCursor.getColumnIndex(columnName);
}
+ @Override
public int getColumnIndexOrThrow(String columnName)
throws IllegalArgumentException {
return mCursor.getColumnIndexOrThrow(columnName);
}
+ @Override
public String getColumnName(int columnIndex) {
return mCursor.getColumnName(columnIndex);
}
+ @Override
public String[] getColumnNames() {
return mCursor.getColumnNames();
}
+ @Override
public double getDouble(int columnIndex) {
return mCursor.getDouble(columnIndex);
}
+ @Override
+ public void setExtras(Bundle extras) {
+ mCursor.setExtras(extras);
+ }
+
+ @Override
public Bundle getExtras() {
return mCursor.getExtras();
}
+ @Override
public float getFloat(int columnIndex) {
return mCursor.getFloat(columnIndex);
}
+ @Override
public int getInt(int columnIndex) {
return mCursor.getInt(columnIndex);
}
+ @Override
public long getLong(int columnIndex) {
return mCursor.getLong(columnIndex);
}
+ @Override
public short getShort(int columnIndex) {
return mCursor.getShort(columnIndex);
}
+ @Override
public String getString(int columnIndex) {
return mCursor.getString(columnIndex);
}
+ @Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
mCursor.copyStringToBuffer(columnIndex, buffer);
}
+ @Override
public byte[] getBlob(int columnIndex) {
return mCursor.getBlob(columnIndex);
}
+ @Override
public boolean getWantsAllOnMoveCalls() {
return mCursor.getWantsAllOnMoveCalls();
}
+ @Override
public boolean isAfterLast() {
return mCursor.isAfterLast();
}
+ @Override
public boolean isBeforeFirst() {
return mCursor.isBeforeFirst();
}
+ @Override
public boolean isFirst() {
return mCursor.isFirst();
}
+ @Override
public boolean isLast() {
return mCursor.isLast();
}
+ @Override
public int getType(int columnIndex) {
return mCursor.getType(columnIndex);
}
+ @Override
public boolean isNull(int columnIndex) {
return mCursor.isNull(columnIndex);
}
+ @Override
public boolean moveToLast() {
return mCursor.moveToLast();
}
+ @Override
public boolean move(int offset) {
return mCursor.move(offset);
}
+ @Override
public boolean moveToPosition(int position) {
return mCursor.moveToPosition(position);
}
+ @Override
public boolean moveToNext() {
return mCursor.moveToNext();
}
+ @Override
public int getPosition() {
return mCursor.getPosition();
}
+ @Override
public boolean moveToPrevious() {
return mCursor.moveToPrevious();
}
+ @Override
public void registerContentObserver(ContentObserver observer) {
- mCursor.registerContentObserver(observer);
+ mCursor.registerContentObserver(observer);
}
+ @Override
public void registerDataSetObserver(DataSetObserver observer) {
- mCursor.registerDataSetObserver(observer);
+ mCursor.registerDataSetObserver(observer);
}
+ @Override
+ @Deprecated
public boolean requery() {
return mCursor.requery();
}
+ @Override
public Bundle respond(Bundle extras) {
return mCursor.respond(extras);
}
+ @Override
public void setNotificationUri(ContentResolver cr, Uri uri) {
- mCursor.setNotificationUri(cr, uri);
+ mCursor.setNotificationUri(cr, uri);
}
+ @Override
public Uri getNotificationUri() {
return mCursor.getNotificationUri();
}
+ @Override
public void unregisterContentObserver(ContentObserver observer) {
- mCursor.unregisterContentObserver(observer);
+ mCursor.unregisterContentObserver(observer);
}
+ @Override
public void unregisterDataSetObserver(DataSetObserver observer) {
mCursor.unregisterDataSetObserver(observer);
}
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 5a1a8e2..2dc5ca4 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -105,7 +105,6 @@ public class SQLiteCursor extends AbstractWindowedCursor {
mQuery = query;
mColumns = query.getColumnNames();
- mRowIdColumnIndex = DatabaseUtils.findRowIdColumnIndex(mColumns);
}
/**
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index bfe4aa2..f01c540 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -939,6 +939,129 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<Integer>("android.lens.facing", int.class);
/**
+ * <p>The orientation of the camera relative to the sensor
+ * coordinate system.</p>
+ * <p>The four coefficients that describe the quarternion
+ * rotation from the Android sensor coordinate system to a
+ * camera-aligned coordinate system where the X-axis is
+ * aligned with the long side of the image sensor, the Y-axis
+ * is aligned with the short side of the image sensor, and
+ * the Z-axis is aligned with the optical axis of the sensor.</p>
+ * <p>To convert from the quarternion coefficients <code>(x,y,z,w)</code>
+ * to the axis of rotation <code>(a_x, a_y, a_z)</code> and rotation
+ * amount <code>theta</code>, the following formulas can be used:</p>
+ * <pre><code> theta = 2 * acos(w)
+ * a_x = x / sin(theta/2)
+ * a_y = y / sin(theta/2)
+ * a_z = z / sin(theta/2)
+ * </code></pre>
+ * <p>To create a 3x3 rotation matrix that applies the rotation
+ * defined by this quarternion, the following matrix can be
+ * used:</p>
+ * <pre><code>R = [ 1 - 2y^2 - 2z^2, 2xy - 2zw, 2xz + 2yw,
+ * 2xy + 2zw, 1 - 2x^2 - 2z^2, 2yz - 2xw,
+ * 2xz - 2yw, 2yz + 2xw, 1 - 2x^2 - 2y^2 ]
+ * </code></pre>
+ * <p>This matrix can then be used to apply the rotation to a
+ * column vector point with</p>
+ * <p><code>p' = Rp</code></p>
+ * <p>where <code>p</code> is in the device sensor coordinate system, and
+ * <code>p'</code> is in the camera-oriented coordinate system.</p>
+ * <p><b>Units</b>:
+ * Quarternion coefficients</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_POSE_ROTATION =
+ new Key<float[]>("android.lens.poseRotation", float[].class);
+
+ /**
+ * <p>Position of the camera optical center.</p>
+ * <p>As measured in the device sensor coordinate system, the
+ * position of the camera device's optical center, as a
+ * three-dimensional vector <code>(x,y,z)</code>.</p>
+ * <p>To transform a world position to a camera-device centered
+ * coordinate system, the position must be translated by this
+ * vector and then rotated by {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p>
+ * <p><b>Units</b>: Meters</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#LENS_POSE_ROTATION
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_POSE_TRANSLATION =
+ new Key<float[]>("android.lens.poseTranslation", float[].class);
+
+ /**
+ * <p>The parameters for this camera device's intrinsic
+ * calibration.</p>
+ * <p>The five calibration parameters that describe the
+ * transform from camera-centric 3D coordinates to sensor
+ * pixel coordinates:</p>
+ * <pre><code>[f_x, f_y, c_x, c_y, s]
+ * </code></pre>
+ * <p>Where <code>f_x</code> and <code>f_y</code> are the horizontal and vertical
+ * focal lengths, <code>[c_x, c_y]</code> is the position of the optical
+ * axis, and <code>s</code> is a skew parameter for the sensor plane not
+ * being aligned with the lens plane.</p>
+ * <p>These are typically used within a transformation matrix K:</p>
+ * <pre><code>K = [ f_x, s, c_x,
+ * 0, f_y, c_y,
+ * 0 0, 1 ]
+ * </code></pre>
+ * <p>which can then be combined with the camera pose rotation
+ * <code>R</code> and translation <code>t</code> ({@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} and
+ * {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}, respective) to calculate the
+ * complete transform from world coordinates to pixel
+ * coordinates:</p>
+ * <pre><code>P = [ K 0 * [ R t
+ * 0 1 ] 0 1 ]
+ * </code></pre>
+ * <p>and with <code>p_w</code> being a point in the world coordinate system
+ * and <code>p_s</code> being a point in the camera active pixel array
+ * coordinate system, and with the mapping including the
+ * homogeneous division by z:</p>
+ * <pre><code> p_h = (x_h, y_h, z_h) = P p_w
+ * p_s = p_h / z_h
+ * </code></pre>
+ * <p>so <code>[x_s, y_s]</code> is the pixel coordinates of the world
+ * point, <code>z_s = 1</code>, and <code>w_s</code> is a measurement of disparity
+ * (depth) in pixel coordinates.</p>
+ * <p><b>Units</b>:
+ * Pixels in the android.sensor.activeArraySize coordinate
+ * system.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#LENS_POSE_ROTATION
+ * @see CameraCharacteristics#LENS_POSE_TRANSLATION
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_INTRINSIC_CALIBRATION =
+ new Key<float[]>("android.lens.intrinsicCalibration", float[].class);
+
+ /**
+ * <p>The correction coefficients to correct for this camera device's
+ * radial lens distortion.</p>
+ * <p>Three cofficients <code>[kappa_1, kappa_2, kappa_3]</code> that
+ * can be used to correct the lens's radial geometric
+ * distortion with the mapping equations:</p>
+ * <pre><code> x_c = x_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 )
+ * y_c = y_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 )
+ * </code></pre>
+ * <p>where <code>[x_i, y_i]</code> are normalized coordinates with <code>(0,0)</code>
+ * at the lens optical center, and <code>[-1, 1]</code> are the edges of
+ * the active pixel array; and where <code>[x_c, y_c]</code> are the
+ * corrected normalized coordinates with radial distortion
+ * removed; and <code>r^2 = x_i^2 + y_i^2</code>.</p>
+ * <p><b>Units</b>:
+ * Coefficients for a 6th-degree even radial polynomial.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_RADIAL_DISTORTION =
+ new Key<float[]>("android.lens.radialDistortion", float[].class);
+
+ /**
* <p>List of noise reduction modes for {@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode} that are supported
* by this camera device.</p>
* <p>Full-capability camera devices will always support OFF and FAST.</p>
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index e346dc2..d8f92e5 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2532,6 +2532,129 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
new Key<Integer>("android.lens.state", int.class);
/**
+ * <p>The orientation of the camera relative to the sensor
+ * coordinate system.</p>
+ * <p>The four coefficients that describe the quarternion
+ * rotation from the Android sensor coordinate system to a
+ * camera-aligned coordinate system where the X-axis is
+ * aligned with the long side of the image sensor, the Y-axis
+ * is aligned with the short side of the image sensor, and
+ * the Z-axis is aligned with the optical axis of the sensor.</p>
+ * <p>To convert from the quarternion coefficients <code>(x,y,z,w)</code>
+ * to the axis of rotation <code>(a_x, a_y, a_z)</code> and rotation
+ * amount <code>theta</code>, the following formulas can be used:</p>
+ * <pre><code> theta = 2 * acos(w)
+ * a_x = x / sin(theta/2)
+ * a_y = y / sin(theta/2)
+ * a_z = z / sin(theta/2)
+ * </code></pre>
+ * <p>To create a 3x3 rotation matrix that applies the rotation
+ * defined by this quarternion, the following matrix can be
+ * used:</p>
+ * <pre><code>R = [ 1 - 2y^2 - 2z^2, 2xy - 2zw, 2xz + 2yw,
+ * 2xy + 2zw, 1 - 2x^2 - 2z^2, 2yz - 2xw,
+ * 2xz - 2yw, 2yz + 2xw, 1 - 2x^2 - 2y^2 ]
+ * </code></pre>
+ * <p>This matrix can then be used to apply the rotation to a
+ * column vector point with</p>
+ * <p><code>p' = Rp</code></p>
+ * <p>where <code>p</code> is in the device sensor coordinate system, and
+ * <code>p'</code> is in the camera-oriented coordinate system.</p>
+ * <p><b>Units</b>:
+ * Quarternion coefficients</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_POSE_ROTATION =
+ new Key<float[]>("android.lens.poseRotation", float[].class);
+
+ /**
+ * <p>Position of the camera optical center.</p>
+ * <p>As measured in the device sensor coordinate system, the
+ * position of the camera device's optical center, as a
+ * three-dimensional vector <code>(x,y,z)</code>.</p>
+ * <p>To transform a world position to a camera-device centered
+ * coordinate system, the position must be translated by this
+ * vector and then rotated by {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p>
+ * <p><b>Units</b>: Meters</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#LENS_POSE_ROTATION
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_POSE_TRANSLATION =
+ new Key<float[]>("android.lens.poseTranslation", float[].class);
+
+ /**
+ * <p>The parameters for this camera device's intrinsic
+ * calibration.</p>
+ * <p>The five calibration parameters that describe the
+ * transform from camera-centric 3D coordinates to sensor
+ * pixel coordinates:</p>
+ * <pre><code>[f_x, f_y, c_x, c_y, s]
+ * </code></pre>
+ * <p>Where <code>f_x</code> and <code>f_y</code> are the horizontal and vertical
+ * focal lengths, <code>[c_x, c_y]</code> is the position of the optical
+ * axis, and <code>s</code> is a skew parameter for the sensor plane not
+ * being aligned with the lens plane.</p>
+ * <p>These are typically used within a transformation matrix K:</p>
+ * <pre><code>K = [ f_x, s, c_x,
+ * 0, f_y, c_y,
+ * 0 0, 1 ]
+ * </code></pre>
+ * <p>which can then be combined with the camera pose rotation
+ * <code>R</code> and translation <code>t</code> ({@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} and
+ * {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}, respective) to calculate the
+ * complete transform from world coordinates to pixel
+ * coordinates:</p>
+ * <pre><code>P = [ K 0 * [ R t
+ * 0 1 ] 0 1 ]
+ * </code></pre>
+ * <p>and with <code>p_w</code> being a point in the world coordinate system
+ * and <code>p_s</code> being a point in the camera active pixel array
+ * coordinate system, and with the mapping including the
+ * homogeneous division by z:</p>
+ * <pre><code> p_h = (x_h, y_h, z_h) = P p_w
+ * p_s = p_h / z_h
+ * </code></pre>
+ * <p>so <code>[x_s, y_s]</code> is the pixel coordinates of the world
+ * point, <code>z_s = 1</code>, and <code>w_s</code> is a measurement of disparity
+ * (depth) in pixel coordinates.</p>
+ * <p><b>Units</b>:
+ * Pixels in the android.sensor.activeArraySize coordinate
+ * system.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#LENS_POSE_ROTATION
+ * @see CameraCharacteristics#LENS_POSE_TRANSLATION
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_INTRINSIC_CALIBRATION =
+ new Key<float[]>("android.lens.intrinsicCalibration", float[].class);
+
+ /**
+ * <p>The correction coefficients to correct for this camera device's
+ * radial lens distortion.</p>
+ * <p>Three cofficients <code>[kappa_1, kappa_2, kappa_3]</code> that
+ * can be used to correct the lens's radial geometric
+ * distortion with the mapping equations:</p>
+ * <pre><code> x_c = x_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 )
+ * y_c = y_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 )
+ * </code></pre>
+ * <p>where <code>[x_i, y_i]</code> are normalized coordinates with <code>(0,0)</code>
+ * at the lens optical center, and <code>[-1, 1]</code> are the edges of
+ * the active pixel array; and where <code>[x_c, y_c]</code> are the
+ * corrected normalized coordinates with radial distortion
+ * removed; and <code>r^2 = x_i^2 + y_i^2</code>.</p>
+ * <p><b>Units</b>:
+ * Coefficients for a 6th-degree even radial polynomial.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_RADIAL_DISTORTION =
+ new Key<float[]>("android.lens.radialDistortion", float[].class);
+
+ /**
* <p>Mode of operation for the noise reduction algorithm.</p>
* <p>The noise reduction algorithm attempts to improve image quality by removing
* excessive noise added by the capture process, especially in dark conditions.</p>
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index bd5a392..cccc4be 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -16,10 +16,12 @@
package android.os;
+import android.content.Context;
import android.os.BatteryProperty;
import android.os.IBatteryPropertiesRegistrar;
import android.os.RemoteException;
import android.os.ServiceManager;
+import com.android.internal.app.IBatteryStats;
/**
* The BatteryManager class contains strings and constants used for values
@@ -128,6 +130,26 @@ public class BatteryManager {
public static final int BATTERY_PLUGGED_ANY =
BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS;
+ /**
+ * Sent when the device's battery has started charging (or has reached full charge
+ * and the device is on power). This is a good time to do work that you would like to
+ * avoid doing while on battery (that is to avoid draining the user's battery due to
+ * things they don't care enough about).
+ *
+ * This is paired with {@link #ACTION_DISCHARGING}. The current state can always
+ * be retrieved with {@link #isCharging()}.
+ */
+ public static final String ACTION_CHARGING = "android.os.action.CHARGING";
+
+ /**
+ * Sent when the device's battery may be discharging, so apps should avoid doing
+ * extraneous work that would cause it to discharge faster.
+ *
+ * This is paired with {@link #ACTION_CHARGING}. The current state can always
+ * be retrieved with {@link #isCharging()}.
+ */
+ public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
+
/*
* Battery property identifiers. These must match the values in
* frameworks/native/include/batteryservice/BatteryService.h
@@ -162,17 +184,34 @@ public class BatteryManager {
*/
public static final int BATTERY_PROPERTY_ENERGY_COUNTER = 5;
+ private final IBatteryStats mBatteryStats;
private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
/**
* @removed Was previously made visible by accident.
*/
public BatteryManager() {
+ mBatteryStats = IBatteryStats.Stub.asInterface(
+ ServiceManager.getService(BatteryStats.SERVICE_NAME));
mBatteryPropertiesRegistrar = IBatteryPropertiesRegistrar.Stub.asInterface(
ServiceManager.getService("batteryproperties"));
}
/**
+ * Return true if the battery is currently considered to be charging. This means that
+ * the device is plugged in and is supplying sufficient power that the battery level is
+ * going up (or the battery is fully charged). Changes in this state are matched by
+ * broadcasts of {@link #ACTION_CHARGING} and {@link #ACTION_DISCHARGING}.
+ */
+ public boolean isCharging() {
+ try {
+ return mBatteryStats.isCharging();
+ } catch (RemoteException e) {
+ return true;
+ }
+ }
+
+ /**
* Query a battery property from the batteryproperties service.
*
* Returns the requested value, or Long.MIN_VALUE if property not
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 3051926..c7edb1a 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1065,6 +1065,7 @@ public abstract class BatteryStats implements Parcelable {
public static final int STATE_SCREEN_ON_FLAG = 1<<20;
public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19;
public static final int STATE_PHONE_IN_CALL_FLAG = 1<<18;
+ public static final int STATE_CHARGING_FLAG = 1<<17;
public static final int STATE_BLUETOOTH_ON_FLAG = 1<<16;
public static final int MOST_INTERESTING_STATES =
@@ -1751,6 +1752,7 @@ public abstract class BatteryStats implements Parcelable {
new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen", "S"),
new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged", "BP"),
new BitDescription(HistoryItem.STATE_PHONE_IN_CALL_FLAG, "phone_in_call", "Pcl"),
+ new BitDescription(HistoryItem.STATE_CHARGING_FLAG, "charging", "ch"),
new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth", "b"),
new BitDescription(HistoryItem.STATE_DATA_CONNECTION_MASK,
HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn", "Pcn",
@@ -1945,6 +1947,13 @@ public abstract class BatteryStats implements Parcelable {
public static final int NUM_CONTROLLER_ACTIVITY_TYPES = CONTROLLER_POWER_DRAIN + 1;
/**
+ * Returns true if the BatteryStats object has detailed bluetooth power reports.
+ * When true, calling {@link #getBluetoothControllerActivity(int, int)} will yield the
+ * actual power data.
+ */
+ public abstract boolean hasBluetoothActivityReporting();
+
+ /**
* For {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, and
* {@link #CONTROLLER_TX_TIME}, returns the time spent (in milliseconds) in the
* respective state.
@@ -1954,6 +1963,13 @@ public abstract class BatteryStats implements Parcelable {
public abstract long getBluetoothControllerActivity(int type, int which);
/**
+ * Returns true if the BatteryStats object has detailed WiFi power reports.
+ * When true, calling {@link #getWifiControllerActivity(int, int)} will yield the
+ * actual power data.
+ */
+ public abstract boolean hasWifiActivityReporting();
+
+ /**
* For {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, and
* {@link #CONTROLLER_TX_TIME}, returns the time spent (in milliseconds) in the
* respective state.
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 236003b..c2fd3c3 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -38,6 +38,7 @@ interface IUserManager {
List<UserInfo> getProfiles(int userHandle, boolean enabledOnly);
UserInfo getProfileParent(int userHandle);
UserInfo getUserInfo(int userHandle);
+ long getUserCreationTime(int userHandle);
boolean isRestricted();
int getUserSerialNumber(int userHandle);
int getUserHandle(int userSerialNumber);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3601a1c..afd9950 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1320,4 +1320,19 @@ public class UserManager {
}
return new Bundle();
}
+
+ /**
+ * Returns creation time of the user or of a managed profile associated with the calling user.
+ * @param userHandle user handle of the user or a managed profile associated with the
+ * calling user.
+ * @return creation time in milliseconds since Epoch time.
+ */
+ public long getUserCreationTime(int userHandle) {
+ try {
+ return mService.getUserCreationTime(userHandle);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get user creation time", re);
+ return 0;
+ }
+ }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5ee8fb3..3087e1d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -880,6 +880,15 @@ public final class Settings {
"android.settings.VOICE_CONTROL_DO_NOT_DISTURB_MODE";
/**
+ * Activity Action: Show Zen Mode schedule rule configuration settings.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_ZEN_MODE_SCHEDULE_RULE_SETTINGS
+ = "android.settings.ZEN_MODE_SCHEDULE_RULE_SETTINGS";
+
+ /**
* Activity Action: Show the regulatory information screen for the device.
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard
@@ -7218,6 +7227,18 @@ public final class Settings {
return "ZEN_MODE_OFF";
}
+ /** @hide */ public static boolean isValidZenMode(int value) {
+ switch (value) {
+ case Global.ZEN_MODE_OFF:
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ case Global.ZEN_MODE_ALARMS:
+ case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ return true;
+ default:
+ return false;
+ }
+ }
+
/**
* Opaque value, changes when persisted zen mode configuration changes.
*
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 5e2accd..ea53c0d 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -47,21 +47,17 @@ public final class KeymasterDefs {
public static final int KM_TAG_PURPOSE = KM_ENUM_REP | 1;
public static final int KM_TAG_ALGORITHM = KM_ENUM | 2;
public static final int KM_TAG_KEY_SIZE = KM_INT | 3;
- public static final int KM_TAG_BLOCK_MODE = KM_ENUM | 4;
- public static final int KM_TAG_DIGEST = KM_ENUM | 5;
- public static final int KM_TAG_MAC_LENGTH = KM_INT | 6;
- public static final int KM_TAG_PADDING = KM_ENUM | 7;
- public static final int KM_TAG_RETURN_UNAUTHED = KM_BOOL | 8;
- public static final int KM_TAG_CALLER_NONCE = KM_BOOL | 9;
+ public static final int KM_TAG_BLOCK_MODE = KM_ENUM_REP | 4;
+ public static final int KM_TAG_DIGEST = KM_ENUM_REP | 5;
+ public static final int KM_TAG_PADDING = KM_ENUM_REP | 6;
+ public static final int KM_TAG_RETURN_UNAUTHED = KM_BOOL | 7;
+ public static final int KM_TAG_CALLER_NONCE = KM_BOOL | 8;
public static final int KM_TAG_RESCOPING_ADD = KM_ENUM_REP | 101;
public static final int KM_TAG_RESCOPING_DEL = KM_ENUM_REP | 102;
public static final int KM_TAG_BLOB_USAGE_REQUIREMENTS = KM_ENUM | 705;
public static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_LONG | 200;
- public static final int KM_TAG_DSA_GENERATOR = KM_BIGNUM | 201;
- public static final int KM_TAG_DSA_P = KM_BIGNUM | 202;
- public static final int KM_TAG_DSA_Q = KM_BIGNUM | 203;
public static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
public static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
@@ -88,43 +84,21 @@ public final class KeymasterDefs {
public static final int KM_TAG_NONCE = KM_BYTES | 1001;
public static final int KM_TAG_CHUNK_LENGTH = KM_INT | 1002;
public static final int KM_TAG_AUTH_TOKEN = KM_BYTES | 1003;
+ public static final int KM_TAG_MAC_LENGTH = KM_INT | 1004;
// Algorithm values.
public static final int KM_ALGORITHM_RSA = 1;
- public static final int KM_ALGORITHM_DSA = 2;
- public static final int KM_ALGORITHM_ECDSA = 3;
- public static final int KM_ALGORITHM_ECIES = 4;
+ public static final int KM_ALGORITHM_EC = 3;
public static final int KM_ALGORITHM_AES = 32;
- public static final int KM_ALGORITHM_3DES = 33;
- public static final int KM_ALGORITHM_SKIPJACK = 34;
- public static final int KM_ALGORITHM_MARS = 48;
- public static final int KM_ALGORITHM_RC6 = 49;
- public static final int KM_ALGORITHM_SERPENT = 50;
- public static final int KM_ALGORITHM_TWOFISH = 51;
- public static final int KM_ALGORITHM_IDEA = 52;
- public static final int KM_ALGORITHM_RC5 = 53;
- public static final int KM_ALGORITHM_CAST5 = 54;
- public static final int KM_ALGORITHM_BLOWFISH = 55;
- public static final int KM_ALGORITHM_RC4 = 64;
- public static final int KM_ALGORITHM_CHACHA20 = 65;
public static final int KM_ALGORITHM_HMAC = 128;
// Block modes.
public static final int KM_MODE_FIRST_UNAUTHENTICATED = 1;
public static final int KM_MODE_ECB = KM_MODE_FIRST_UNAUTHENTICATED;
public static final int KM_MODE_CBC = 2;
- public static final int KM_MODE_CBC_CTS = 3;
public static final int KM_MODE_CTR = 4;
- public static final int KM_MODE_OFB = 5;
- public static final int KM_MODE_CFB = 6;
- public static final int KM_MODE_XTS = 7;
public static final int KM_MODE_FIRST_AUTHENTICATED = 32;
public static final int KM_MODE_GCM = KM_MODE_FIRST_AUTHENTICATED;
- public static final int KM_MODE_OCB = 33;
- public static final int KM_MODE_CCM = 34;
- public static final int KM_MODE_FIRST_MAC = 128;
- public static final int KM_MODE_CMAC = KM_MODE_FIRST_MAC;
- public static final int KM_MODE_POLY1305 = 129;
// Padding modes.
public static final int KM_PAD_NONE = 1;
@@ -132,11 +106,7 @@ public final class KeymasterDefs {
public static final int KM_PAD_RSA_PSS = 3;
public static final int KM_PAD_RSA_PKCS1_1_5_ENCRYPT = 4;
public static final int KM_PAD_RSA_PKCS1_1_5_SIGN = 5;
- public static final int KM_PAD_ANSI_X923 = 32;
- public static final int KM_PAD_ISO_10126 = 33;
- public static final int KM_PAD_ZERO = 64;
- public static final int KM_PAD_PKCS7 = 65;
- public static final int KM_PAD_ISO_7816_4 = 66;
+ public static final int KM_PAD_PKCS7 = 64;
// Digest modes.
public static final int KM_DIGEST_NONE = 0;
@@ -146,9 +116,6 @@ public final class KeymasterDefs {
public static final int KM_DIGEST_SHA_2_256 = 4;
public static final int KM_DIGEST_SHA_2_384 = 5;
public static final int KM_DIGEST_SHA_2_512 = 6;
- public static final int KM_DIGEST_SHA_3_256 = 7;
- public static final int KM_DIGEST_SHA_3_384 = 8;
- public static final int KM_DIGEST_SHA_3_512 = 9;
// Key origins.
public static final int KM_ORIGIN_HARDWARE = 0;
@@ -168,7 +135,6 @@ public final class KeymasterDefs {
// Key formats.
public static final int KM_KEY_FORMAT_X509 = 0;
public static final int KM_KEY_FORMAT_PKCS8 = 1;
- public static final int KM_KEY_FORMAT_PKCS12 = 2;
public static final int KM_KEY_FORMAT_RAW = 3;
// User authenticators.
@@ -219,7 +185,6 @@ public final class KeymasterDefs {
public static final int KM_ERROR_INVALID_TAG = -40;
public static final int KM_ERROR_MEMORY_ALLOCATION_FAILED = -41;
public static final int KM_ERROR_INVALID_RESCOPING = -42;
- public static final int KM_ERROR_INVALID_DSA_PARAMS = -43;
public static final int KM_ERROR_IMPORT_PARAMETER_MISMATCH = -44;
public static final int KM_ERROR_SECURE_HW_ACCESS_DENIED = -45;
public static final int KM_ERROR_OPERATION_CANCELLED = -46;
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 2702457..56eb510 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -22,22 +22,25 @@ import android.content.res.Resources;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.provider.Settings.Global;
import android.text.TextUtils;
import android.text.format.DateFormat;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.R;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Calendar;
import java.util.Locale;
import java.util.Objects;
-
-import com.android.internal.R;
+import java.util.UUID;
/**
* Persisted configuration for zen mode.
@@ -47,10 +50,6 @@ import com.android.internal.R;
public class ZenModeConfig implements Parcelable {
private static String TAG = "ZenModeConfig";
- public static final String SLEEP_MODE_NIGHTS = "nights";
- public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights";
- public static final String SLEEP_MODE_DAYS_PREFIX = "days:";
-
public static final int SOURCE_ANYONE = 0;
public static final int SOURCE_CONTACT = 1;
public static final int SOURCE_STAR = 2;
@@ -60,6 +59,7 @@ public class ZenModeConfig implements Parcelable {
Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY };
public static final int[] WEEKNIGHT_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
Calendar.WEDNESDAY, Calendar.THURSDAY };
+ public static final int[] WEEKEND_DAYS = { Calendar.FRIDAY, Calendar.SATURDAY };
public static final int[] MINUTE_BUCKETS = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 };
private static final int SECONDS_MS = 1000;
@@ -68,24 +68,18 @@ public class ZenModeConfig implements Parcelable {
private static final boolean DEFAULT_ALLOW_REMINDERS = true;
private static final boolean DEFAULT_ALLOW_EVENTS = true;
+ private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false;
- private static final int XML_VERSION = 1;
+ private static final int XML_VERSION = 2;
private static final String ZEN_TAG = "zen";
private static final String ZEN_ATT_VERSION = "version";
private static final String ALLOW_TAG = "allow";
private static final String ALLOW_ATT_CALLS = "calls";
+ private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers";
private static final String ALLOW_ATT_MESSAGES = "messages";
private static final String ALLOW_ATT_FROM = "from";
private static final String ALLOW_ATT_REMINDERS = "reminders";
private static final String ALLOW_ATT_EVENTS = "events";
- private static final String SLEEP_TAG = "sleep";
- private static final String SLEEP_ATT_MODE = "mode";
- private static final String SLEEP_ATT_NONE = "none";
-
- private static final String SLEEP_ATT_START_HR = "startHour";
- private static final String SLEEP_ATT_START_MIN = "startMin";
- private static final String SLEEP_ATT_END_HR = "endHour";
- private static final String SLEEP_ATT_END_MIN = "endMin";
private static final String CONDITION_TAG = "condition";
private static final String CONDITION_ATT_COMPONENT = "component";
@@ -97,111 +91,115 @@ public class ZenModeConfig implements Parcelable {
private static final String CONDITION_ATT_STATE = "state";
private static final String CONDITION_ATT_FLAGS = "flags";
- private static final String EXIT_CONDITION_TAG = "exitCondition";
- private static final String EXIT_CONDITION_ATT_COMPONENT = "component";
+ private static final String MANUAL_TAG = "manual";
+ private static final String AUTOMATIC_TAG = "automatic";
+
+ private static final String RULE_ATT_ID = "id";
+ private static final String RULE_ATT_ENABLED = "enabled";
+ private static final String RULE_ATT_SNOOZING = "snoozing";
+ private static final String RULE_ATT_NAME = "name";
+ private static final String RULE_ATT_COMPONENT = "component";
+ private static final String RULE_ATT_ZEN = "zen";
+ private static final String RULE_ATT_CONDITION_ID = "conditionId";
public boolean allowCalls;
+ public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS;
public boolean allowMessages;
public boolean allowReminders = DEFAULT_ALLOW_REMINDERS;
public boolean allowEvents = DEFAULT_ALLOW_EVENTS;
public int allowFrom = SOURCE_ANYONE;
- public String sleepMode;
- public int sleepStartHour; // 0-23
- public int sleepStartMinute; // 0-59
- public int sleepEndHour;
- public int sleepEndMinute;
- public boolean sleepNone; // false = priority, true = none
- public ComponentName[] conditionComponents;
- public Uri[] conditionIds;
- public Condition exitCondition;
- public ComponentName exitConditionComponent;
+ public ZenRule manualRule;
+ public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();
public ZenModeConfig() { }
public ZenModeConfig(Parcel source) {
allowCalls = source.readInt() == 1;
+ allowRepeatCallers = source.readInt() == 1;
allowMessages = source.readInt() == 1;
allowReminders = source.readInt() == 1;
allowEvents = source.readInt() == 1;
- if (source.readInt() == 1) {
- sleepMode = source.readString();
- }
- sleepStartHour = source.readInt();
- sleepStartMinute = source.readInt();
- sleepEndHour = source.readInt();
- sleepEndMinute = source.readInt();
- sleepNone = source.readInt() == 1;
- int len = source.readInt();
- if (len > 0) {
- conditionComponents = new ComponentName[len];
- source.readTypedArray(conditionComponents, ComponentName.CREATOR);
- }
- len = source.readInt();
+ allowFrom = source.readInt();
+ manualRule = source.readParcelable(null);
+ final int len = source.readInt();
if (len > 0) {
- conditionIds = new Uri[len];
- source.readTypedArray(conditionIds, Uri.CREATOR);
+ final String[] ids = new String[len];
+ final ZenRule[] rules = new ZenRule[len];
+ source.readStringArray(ids);
+ source.readTypedArray(rules, ZenRule.CREATOR);
+ for (int i = 0; i < len; i++) {
+ automaticRules.put(ids[i], rules[i]);
+ }
}
- allowFrom = source.readInt();
- exitCondition = source.readParcelable(null);
- exitConditionComponent = source.readParcelable(null);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(allowCalls ? 1 : 0);
+ dest.writeInt(allowRepeatCallers ? 1 : 0);
dest.writeInt(allowMessages ? 1 : 0);
dest.writeInt(allowReminders ? 1 : 0);
dest.writeInt(allowEvents ? 1 : 0);
- if (sleepMode != null) {
- dest.writeInt(1);
- dest.writeString(sleepMode);
- } else {
- dest.writeInt(0);
- }
- dest.writeInt(sleepStartHour);
- dest.writeInt(sleepStartMinute);
- dest.writeInt(sleepEndHour);
- dest.writeInt(sleepEndMinute);
- dest.writeInt(sleepNone ? 1 : 0);
- if (conditionComponents != null && conditionComponents.length > 0) {
- dest.writeInt(conditionComponents.length);
- dest.writeTypedArray(conditionComponents, 0);
- } else {
- dest.writeInt(0);
- }
- if (conditionIds != null && conditionIds.length > 0) {
- dest.writeInt(conditionIds.length);
- dest.writeTypedArray(conditionIds, 0);
+ dest.writeInt(allowFrom);
+ dest.writeParcelable(manualRule, 0);
+ if (!automaticRules.isEmpty()) {
+ final int len = automaticRules.size();
+ final String[] ids = new String[len];
+ final ZenRule[] rules = new ZenRule[len];
+ for (int i = 0; i < len; i++) {
+ ids[i] = automaticRules.keyAt(i);
+ rules[i] = automaticRules.valueAt(i);
+ }
+ dest.writeInt(len);
+ dest.writeStringArray(ids);
+ dest.writeTypedArray(rules, 0);
} else {
dest.writeInt(0);
}
- dest.writeInt(allowFrom);
- dest.writeParcelable(exitCondition, 0);
- dest.writeParcelable(exitConditionComponent, 0);
}
@Override
public String toString() {
return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
.append("allowCalls=").append(allowCalls)
+ .append(",allowRepeatCallers=").append(allowRepeatCallers)
.append(",allowMessages=").append(allowMessages)
.append(",allowFrom=").append(sourceToString(allowFrom))
.append(",allowReminders=").append(allowReminders)
.append(",allowEvents=").append(allowEvents)
- .append(",sleepMode=").append(sleepMode)
- .append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute)
- .append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute)
- .append(",sleepNone=").append(sleepNone)
- .append(",conditionComponents=")
- .append(conditionComponents == null ? null : TextUtils.join(",", conditionComponents))
- .append(",conditionIds=")
- .append(conditionIds == null ? null : TextUtils.join(",", conditionIds))
- .append(",exitCondition=").append(exitCondition)
- .append(",exitConditionComponent=").append(exitConditionComponent)
+ .append(",automaticRules=").append(automaticRules)
+ .append(",manualRule=").append(manualRule)
.append(']').toString();
}
+ public boolean isValid() {
+ if (!isValidManualRule(manualRule)) return false;
+ final int N = automaticRules.size();
+ for (int i = 0; i < N; i++) {
+ if (!isValidAutomaticRule(automaticRules.valueAt(i))) return false;
+ }
+ return true;
+ }
+
+ private static boolean isValidManualRule(ZenRule rule) {
+ return rule == null || Global.isValidZenMode(rule.zenMode) && sameCondition(rule);
+ }
+
+ private static boolean isValidAutomaticRule(ZenRule rule) {
+ return rule != null && !TextUtils.isEmpty(rule.name) && Global.isValidZenMode(rule.zenMode)
+ && rule.conditionId != null && sameCondition(rule);
+ }
+
+ private static boolean sameCondition(ZenRule rule) {
+ if (rule == null) return false;
+ if (rule.conditionId == null) {
+ return rule.condition == null;
+ } else {
+ return rule.condition == null || rule.conditionId.equals(rule.condition.id);
+ }
+ }
+
public static String sourceToString(int source) {
switch (source) {
case SOURCE_ANYONE:
@@ -221,49 +219,34 @@ public class ZenModeConfig implements Parcelable {
if (o == this) return true;
final ZenModeConfig other = (ZenModeConfig) o;
return other.allowCalls == allowCalls
+ && other.allowRepeatCallers == allowRepeatCallers
&& other.allowMessages == allowMessages
&& other.allowFrom == allowFrom
&& other.allowReminders == allowReminders
&& other.allowEvents == allowEvents
- && Objects.equals(other.sleepMode, sleepMode)
- && other.sleepNone == sleepNone
- && other.sleepStartHour == sleepStartHour
- && other.sleepStartMinute == sleepStartMinute
- && other.sleepEndHour == sleepEndHour
- && other.sleepEndMinute == sleepEndMinute
- && Objects.deepEquals(other.conditionComponents, conditionComponents)
- && Objects.deepEquals(other.conditionIds, conditionIds)
- && Objects.equals(other.exitCondition, exitCondition)
- && Objects.equals(other.exitConditionComponent, exitConditionComponent);
+ && Objects.equals(other.automaticRules, automaticRules)
+ && Objects.equals(other.manualRule, manualRule);
}
@Override
public int hashCode() {
- return Objects.hash(allowCalls, allowMessages, allowFrom, allowReminders, allowEvents,
- sleepMode, sleepNone, sleepStartHour, sleepStartMinute, sleepEndHour,
- sleepEndMinute, Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds),
- exitCondition, exitConditionComponent);
+ return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowFrom,
+ allowReminders, allowEvents, automaticRules, manualRule);
}
- public boolean isValid() {
- return isValidHour(sleepStartHour) && isValidMinute(sleepStartMinute)
- && isValidHour(sleepEndHour) && isValidMinute(sleepEndMinute)
- && isValidSleepMode(sleepMode);
- }
-
- public static boolean isValidSleepMode(String sleepMode) {
- return sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS)
- || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS) || tryParseDays(sleepMode) != null;
+ private static String toDayList(int[] days) {
+ if (days == null || days.length == 0) return "";
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < days.length; i++) {
+ if (i > 0) sb.append('.');
+ sb.append(days[i]);
+ }
+ return sb.toString();
}
- public static int[] tryParseDays(String sleepMode) {
- if (sleepMode == null) return null;
- sleepMode = sleepMode.trim();
- if (SLEEP_MODE_NIGHTS.equals(sleepMode)) return ALL_DAYS;
- if (SLEEP_MODE_WEEKNIGHTS.equals(sleepMode)) return WEEKNIGHT_DAYS;
- if (!sleepMode.startsWith(SLEEP_MODE_DAYS_PREFIX)) return null;
- if (sleepMode.equals(SLEEP_MODE_DAYS_PREFIX)) return null;
- final String[] tokens = sleepMode.substring(SLEEP_MODE_DAYS_PREFIX.length()).split(",");
+ private static int[] tryParseDayList(String dayList, String sep) {
+ if (dayList == null) return null;
+ final String[] tokens = dayList.split(sep);
if (tokens.length == 0) return null;
final int[] rt = new int[tokens.length];
for (int i = 0; i < tokens.length; i++) {
@@ -283,7 +266,7 @@ public class ZenModeConfig implements Parcelable {
}
}
- public static ZenModeConfig readXml(XmlPullParser parser)
+ public static ZenModeConfig readXml(XmlPullParser parser, Migration migration)
throws XmlPullParserException, IOException {
int type = parser.getEventType();
if (type != XmlPullParser.START_TAG) return null;
@@ -291,21 +274,20 @@ public class ZenModeConfig implements Parcelable {
if (!ZEN_TAG.equals(tag)) return null;
final ZenModeConfig rt = new ZenModeConfig();
final int version = safeInt(parser, ZEN_ATT_VERSION, XML_VERSION);
- final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>();
- final ArrayList<Uri> conditionIds = new ArrayList<Uri>();
+ if (version == 1) {
+ final XmlV1 v1 = XmlV1.readXml(parser);
+ return migration.migrate(v1);
+ }
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
tag = parser.getName();
if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
- if (!conditionComponents.isEmpty()) {
- rt.conditionComponents = conditionComponents
- .toArray(new ComponentName[conditionComponents.size()]);
- rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]);
- }
return rt;
}
if (type == XmlPullParser.START_TAG) {
if (ALLOW_TAG.equals(tag)) {
rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
+ rt.allowRepeatCallers = safeBoolean(parser, ALLOW_ATT_REPEAT_CALLERS,
+ DEFAULT_ALLOW_REPEAT_CALLERS);
rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
DEFAULT_ALLOW_REMINDERS);
@@ -314,31 +296,13 @@ public class ZenModeConfig implements Parcelable {
if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) {
throw new IndexOutOfBoundsException("bad source in config:" + rt.allowFrom);
}
- } else if (SLEEP_TAG.equals(tag)) {
- final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
- rt.sleepMode = isValidSleepMode(mode)? mode : null;
- rt.sleepNone = safeBoolean(parser, SLEEP_ATT_NONE, false);
- final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0);
- final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0);
- final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0);
- final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0);
- rt.sleepStartHour = isValidHour(startHour) ? startHour : 0;
- rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0;
- rt.sleepEndHour = isValidHour(endHour) ? endHour : 0;
- rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0;
- } else if (CONDITION_TAG.equals(tag)) {
- final ComponentName component =
- safeComponentName(parser, CONDITION_ATT_COMPONENT);
- final Uri conditionId = safeUri(parser, CONDITION_ATT_ID);
- if (component != null && conditionId != null) {
- conditionComponents.add(component);
- conditionIds.add(conditionId);
- }
- } else if (EXIT_CONDITION_TAG.equals(tag)) {
- rt.exitCondition = readConditionXml(parser);
- if (rt.exitCondition != null) {
- rt.exitConditionComponent =
- safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT);
+ } else if (MANUAL_TAG.equals(tag)) {
+ rt.manualRule = readRuleXml(parser);
+ } else if (AUTOMATIC_TAG.equals(tag)) {
+ final String id = parser.getAttributeValue(null, RULE_ATT_ID);
+ final ZenRule automaticRule = readRuleXml(parser);
+ if (id != null && automaticRule != null) {
+ rt.automaticRules.put(id, automaticRule);
}
}
}
@@ -352,45 +316,68 @@ public class ZenModeConfig implements Parcelable {
out.startTag(null, ALLOW_TAG);
out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls));
+ out.attribute(null, ALLOW_ATT_REPEAT_CALLERS, Boolean.toString(allowRepeatCallers));
out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages));
out.attribute(null, ALLOW_ATT_REMINDERS, Boolean.toString(allowReminders));
out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents));
out.attribute(null, ALLOW_ATT_FROM, Integer.toString(allowFrom));
out.endTag(null, ALLOW_TAG);
- out.startTag(null, SLEEP_TAG);
- if (sleepMode != null) {
- out.attribute(null, SLEEP_ATT_MODE, sleepMode);
- }
- out.attribute(null, SLEEP_ATT_NONE, Boolean.toString(sleepNone));
- out.attribute(null, SLEEP_ATT_START_HR, Integer.toString(sleepStartHour));
- out.attribute(null, SLEEP_ATT_START_MIN, Integer.toString(sleepStartMinute));
- out.attribute(null, SLEEP_ATT_END_HR, Integer.toString(sleepEndHour));
- out.attribute(null, SLEEP_ATT_END_MIN, Integer.toString(sleepEndMinute));
- out.endTag(null, SLEEP_TAG);
-
- if (conditionComponents != null && conditionIds != null
- && conditionComponents.length == conditionIds.length) {
- for (int i = 0; i < conditionComponents.length; i++) {
- out.startTag(null, CONDITION_TAG);
- out.attribute(null, CONDITION_ATT_COMPONENT,
- conditionComponents[i].flattenToString());
- out.attribute(null, CONDITION_ATT_ID, conditionIds[i].toString());
- out.endTag(null, CONDITION_TAG);
- }
+ if (manualRule != null) {
+ out.startTag(null, MANUAL_TAG);
+ writeRuleXml(manualRule, out);
+ out.endTag(null, MANUAL_TAG);
}
- if (exitCondition != null && exitConditionComponent != null) {
- out.startTag(null, EXIT_CONDITION_TAG);
- out.attribute(null, EXIT_CONDITION_ATT_COMPONENT,
- exitConditionComponent.flattenToString());
- writeConditionXml(exitCondition, out);
- out.endTag(null, EXIT_CONDITION_TAG);
+ final int N = automaticRules.size();
+ for (int i = 0; i < N; i++) {
+ final String id = automaticRules.keyAt(i);
+ final ZenRule automaticRule = automaticRules.valueAt(i);
+ out.startTag(null, AUTOMATIC_TAG);
+ out.attribute(null, RULE_ATT_ID, id);
+ writeRuleXml(automaticRule, out);
+ out.endTag(null, AUTOMATIC_TAG);
}
out.endTag(null, ZEN_TAG);
}
+ public static ZenRule readRuleXml(XmlPullParser parser) {
+ final ZenRule rt = new ZenRule();
+ rt.enabled = safeBoolean(parser, RULE_ATT_ENABLED, true);
+ rt.snoozing = safeBoolean(parser, RULE_ATT_SNOOZING, false);
+ rt.name = parser.getAttributeValue(null, RULE_ATT_NAME);
+ final String zen = parser.getAttributeValue(null, RULE_ATT_ZEN);
+ rt.zenMode = tryParseZenMode(zen, -1);
+ if (rt.zenMode == -1) {
+ Slog.w(TAG, "Bad zen mode in rule xml:" + zen);
+ return null;
+ }
+ rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID);
+ rt.component = safeComponentName(parser, RULE_ATT_COMPONENT);
+ rt.condition = readConditionXml(parser);
+ return rt.condition != null ? rt : null;
+ }
+
+ public static void writeRuleXml(ZenRule rule, XmlSerializer out) throws IOException {
+ out.attribute(null, RULE_ATT_ENABLED, Boolean.toString(rule.enabled));
+ out.attribute(null, RULE_ATT_SNOOZING, Boolean.toString(rule.snoozing));
+ if (rule.name != null) {
+ out.attribute(null, RULE_ATT_NAME, rule.name);
+ }
+ out.attribute(null, RULE_ATT_ZEN, Integer.toString(rule.zenMode));
+ if (rule.component != null) {
+ out.attribute(null, RULE_ATT_COMPONENT, rule.component.flattenToString());
+ }
+ if (rule.conditionId != null) {
+ out.attribute(null, RULE_ATT_CONDITION_ID, rule.conditionId.toString());
+ }
+ if (rule.condition != null) {
+ writeConditionXml(rule.condition, out);
+ }
+ }
+
public static Condition readConditionXml(XmlPullParser parser) {
final Uri id = safeUri(parser, CONDITION_ATT_ID);
+ if (id == null) return null;
final String summary = parser.getAttributeValue(null, CONDITION_ATT_SUMMARY);
final String line1 = parser.getAttributeValue(null, CONDITION_ATT_LINE1);
final String line2 = parser.getAttributeValue(null, CONDITION_ATT_LINE2);
@@ -446,6 +433,14 @@ public class ZenModeConfig implements Parcelable {
return Uri.parse(val);
}
+ public ArraySet<String> getAutomaticRuleNames() {
+ final ArraySet<String> rt = new ArraySet<String>();
+ for (int i = 0; i < automaticRules.size(); i++) {
+ rt.add(automaticRules.valueAt(i).name);
+ }
+ return rt;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -475,17 +470,6 @@ public class ZenModeConfig implements Parcelable {
}
};
- public DowntimeInfo toDowntimeInfo() {
- final DowntimeInfo downtime = new DowntimeInfo();
- downtime.startHour = sleepStartHour;
- downtime.startMinute = sleepStartMinute;
- downtime.endHour = sleepEndHour;
- downtime.endMinute = sleepEndMinute;
- downtime.mode = sleepMode;
- downtime.none = sleepNone;
- return downtime;
- }
-
public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) {
final long now = System.currentTimeMillis();
final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS;
@@ -548,38 +532,77 @@ public class ZenModeConfig implements Parcelable {
return tryParseCountdownConditionId(conditionId) != 0;
}
- // Built-in downtime conditions
- // e.g. condition://android/downtime?start=10.00&end=7.00&mode=days%3A5%2C6&none=false
- public static final String DOWNTIME_PATH = "downtime";
+ // built-in schedule conditions
+ public static final String SCHEDULE_PATH = "schedule";
+
+ public static class ScheduleInfo {
+ public int[] days;
+ public int startHour;
+ public int startMinute;
+ public int endHour;
+ public int endMinute;
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ScheduleInfo)) return false;
+ final ScheduleInfo other = (ScheduleInfo) o;
+ return toDayList(days).equals(toDayList(other.days))
+ && startHour == other.startHour
+ && startMinute == other.startMinute
+ && endHour == other.endHour
+ && endMinute == other.endMinute;
+ }
+
+ public ScheduleInfo copy() {
+ final ScheduleInfo rt = new ScheduleInfo();
+ if (days != null) {
+ rt.days = new int[days.length];
+ System.arraycopy(days, 0, rt.days, 0, days.length);
+ }
+ rt.startHour = startHour;
+ rt.startMinute = startMinute;
+ rt.endHour = endHour;
+ rt.endMinute = endMinute;
+ return rt;
+ }
+ }
- public static Uri toDowntimeConditionId(DowntimeInfo downtime) {
+ public static Uri toScheduleConditionId(ScheduleInfo schedule) {
return new Uri.Builder().scheme(Condition.SCHEME)
.authority(SYSTEM_AUTHORITY)
- .appendPath(DOWNTIME_PATH)
- .appendQueryParameter("start", downtime.startHour + "." + downtime.startMinute)
- .appendQueryParameter("end", downtime.endHour + "." + downtime.endMinute)
- .appendQueryParameter("mode", downtime.mode)
- .appendQueryParameter("none", Boolean.toString(downtime.none))
+ .appendPath(SCHEDULE_PATH)
+ .appendQueryParameter("days", toDayList(schedule.days))
+ .appendQueryParameter("start", schedule.startHour + "." + schedule.startMinute)
+ .appendQueryParameter("end", schedule.endHour + "." + schedule.endMinute)
.build();
}
- public static DowntimeInfo tryParseDowntimeConditionId(Uri conditionId) {
- if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY)
- || conditionId.getPathSegments().size() != 1
- || !DOWNTIME_PATH.equals(conditionId.getPathSegments().get(0))) {
- return null;
- }
+ public static boolean isValidScheduleConditionId(Uri conditionId) {
+ return tryParseScheduleConditionId(conditionId) != null;
+ }
+
+ public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) {
+ final boolean isSchedule = conditionId != null
+ && conditionId.getScheme().equals(Condition.SCHEME)
+ && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
+ && conditionId.getPathSegments().size() == 1
+ && conditionId.getPathSegments().get(0).equals(ZenModeConfig.SCHEDULE_PATH);
+ if (!isSchedule) return null;
final int[] start = tryParseHourAndMinute(conditionId.getQueryParameter("start"));
final int[] end = tryParseHourAndMinute(conditionId.getQueryParameter("end"));
if (start == null || end == null) return null;
- final DowntimeInfo downtime = new DowntimeInfo();
- downtime.startHour = start[0];
- downtime.startMinute = start[1];
- downtime.endHour = end[0];
- downtime.endMinute = end[1];
- downtime.mode = conditionId.getQueryParameter("mode");
- downtime.none = Boolean.toString(true).equals(conditionId.getQueryParameter("none"));
- return downtime;
+ final ScheduleInfo rt = new ScheduleInfo();
+ rt.days = tryParseDayList(conditionId.getQueryParameter("days"), "\\.");
+ rt.startHour = start[0];
+ rt.startMinute = start[1];
+ rt.endHour = end[0];
+ rt.endMinute = end[1];
+ return rt;
}
private static int[] tryParseHourAndMinute(String value) {
@@ -591,36 +614,268 @@ public class ZenModeConfig implements Parcelable {
return isValidHour(hour) && isValidMinute(minute) ? new int[] { hour, minute } : null;
}
- public static boolean isValidDowntimeConditionId(Uri conditionId) {
- return tryParseDowntimeConditionId(conditionId) != null;
+ private static int tryParseZenMode(String value, int defValue) {
+ final int rt = tryParseInt(value, defValue);
+ return Global.isValidZenMode(rt) ? rt : defValue;
}
- public static class DowntimeInfo {
- public int startHour; // 0-23
- public int startMinute; // 0-59
- public int endHour;
- public int endMinute;
- public String mode;
- public boolean none;
+ public String newRuleId() {
+ return UUID.randomUUID().toString().replace("-", "");
+ }
+
+ public static String getConditionLine1(Context context, ZenModeConfig config,
+ int userHandle) {
+ return getConditionLine(context, config, userHandle, true /*useLine1*/);
+ }
+
+ public static String getConditionSummary(Context context, ZenModeConfig config,
+ int userHandle) {
+ return getConditionLine(context, config, userHandle, false /*useLine1*/);
+ }
+
+ private static String getConditionLine(Context context, ZenModeConfig config,
+ int userHandle, boolean useLine1) {
+ if (config == null) return "";
+ if (config.manualRule != null) {
+ final Uri id = config.manualRule.conditionId;
+ if (id == null) {
+ return context.getString(com.android.internal.R.string.zen_mode_forever);
+ }
+ final long time = tryParseCountdownConditionId(id);
+ Condition c = config.manualRule.condition;
+ if (time > 0) {
+ final long now = System.currentTimeMillis();
+ final long span = time - now;
+ c = toTimeCondition(context,
+ time, Math.round(span / (float) MINUTES_MS), now, userHandle);
+ }
+ final String rt = c == null ? "" : useLine1 ? c.line1 : c.summary;
+ return TextUtils.isEmpty(rt) ? "" : rt;
+ }
+ String summary = "";
+ for (ZenRule automaticRule : config.automaticRules.values()) {
+ if (automaticRule.enabled && !automaticRule.snoozing
+ && automaticRule.isTrueOrUnknown()) {
+ if (summary.isEmpty()) {
+ summary = automaticRule.name;
+ } else {
+ summary = context.getResources()
+ .getString(R.string.zen_mode_rule_name_combination, summary,
+ automaticRule.name);
+ }
+ }
+ }
+ return summary;
+ }
+
+ public static class ZenRule implements Parcelable {
+ public boolean enabled;
+ public boolean snoozing; // user manually disabled this instance
+ public String name; // required for automatic (unique)
+ public int zenMode;
+ public Uri conditionId; // required for automatic
+ public Condition condition; // optional
+ public ComponentName component; // optional
+
+ public ZenRule() { }
+
+ public ZenRule(Parcel source) {
+ enabled = source.readInt() == 1;
+ snoozing = source.readInt() == 1;
+ if (source.readInt() == 1) {
+ name = source.readString();
+ }
+ zenMode = source.readInt();
+ conditionId = source.readParcelable(null);
+ condition = source.readParcelable(null);
+ component = source.readParcelable(null);
+ }
@Override
- public int hashCode() {
+ public int describeContents() {
return 0;
}
@Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(enabled ? 1 : 0);
+ dest.writeInt(snoozing ? 1 : 0);
+ if (name != null) {
+ dest.writeInt(1);
+ dest.writeString(name);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(zenMode);
+ dest.writeParcelable(conditionId, 0);
+ dest.writeParcelable(condition, 0);
+ dest.writeParcelable(component, 0);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder(ZenRule.class.getSimpleName()).append('[')
+ .append("enabled=").append(enabled)
+ .append(",snoozing=").append(snoozing)
+ .append(",name=").append(name)
+ .append(",zenMode=").append(Global.zenModeToString(zenMode))
+ .append(",conditionId=").append(conditionId)
+ .append(",condition=").append(condition)
+ .append(",component=").append(component)
+ .append(']').toString();
+ }
+
+ @Override
public boolean equals(Object o) {
- if (!(o instanceof DowntimeInfo)) return false;
- final DowntimeInfo other = (DowntimeInfo) o;
- return startHour == other.startHour
- && startMinute == other.startMinute
- && endHour == other.endHour
- && endMinute == other.endMinute
- && Objects.equals(mode, other.mode)
- && none == other.none;
+ if (!(o instanceof ZenRule)) return false;
+ if (o == this) return true;
+ final ZenRule other = (ZenRule) o;
+ return other.enabled == enabled
+ && other.snoozing == snoozing
+ && Objects.equals(other.name, name)
+ && other.zenMode == zenMode
+ && Objects.equals(other.conditionId, conditionId)
+ && Objects.equals(other.condition, condition)
+ && Objects.equals(other.component, component);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
+ component);
+ }
+
+ public boolean isTrueOrUnknown() {
+ return condition == null || condition.state == Condition.STATE_TRUE
+ || condition.state == Condition.STATE_UNKNOWN;
+ }
+
+ public static final Parcelable.Creator<ZenRule> CREATOR
+ = new Parcelable.Creator<ZenRule>() {
+ @Override
+ public ZenRule createFromParcel(Parcel source) {
+ return new ZenRule(source);
+ }
+ @Override
+ public ZenRule[] newArray(int size) {
+ return new ZenRule[size];
+ }
+ };
+ }
+
+ // Legacy config
+ public static final class XmlV1 {
+ public static final String SLEEP_MODE_NIGHTS = "nights";
+ public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights";
+ public static final String SLEEP_MODE_DAYS_PREFIX = "days:";
+
+ private static final String EXIT_CONDITION_TAG = "exitCondition";
+ private static final String EXIT_CONDITION_ATT_COMPONENT = "component";
+ private static final String SLEEP_TAG = "sleep";
+ private static final String SLEEP_ATT_MODE = "mode";
+ private static final String SLEEP_ATT_NONE = "none";
+
+ private static final String SLEEP_ATT_START_HR = "startHour";
+ private static final String SLEEP_ATT_START_MIN = "startMin";
+ private static final String SLEEP_ATT_END_HR = "endHour";
+ private static final String SLEEP_ATT_END_MIN = "endMin";
+
+ public boolean allowCalls;
+ public boolean allowMessages;
+ public boolean allowReminders = DEFAULT_ALLOW_REMINDERS;
+ public boolean allowEvents = DEFAULT_ALLOW_EVENTS;
+ public int allowFrom = SOURCE_ANYONE;
+
+ public String sleepMode; // nights, weeknights, days:1,2,3 Calendar.days
+ public int sleepStartHour; // 0-23
+ public int sleepStartMinute; // 0-59
+ public int sleepEndHour;
+ public int sleepEndMinute;
+ public boolean sleepNone; // false = priority, true = none
+ public ComponentName[] conditionComponents;
+ public Uri[] conditionIds;
+ public Condition exitCondition; // manual exit condition
+ public ComponentName exitConditionComponent; // manual exit condition component
+
+ private static boolean isValidSleepMode(String sleepMode) {
+ return sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS)
+ || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS) || tryParseDays(sleepMode) != null;
+ }
+
+ public static int[] tryParseDays(String sleepMode) {
+ if (sleepMode == null) return null;
+ sleepMode = sleepMode.trim();
+ if (SLEEP_MODE_NIGHTS.equals(sleepMode)) return ALL_DAYS;
+ if (SLEEP_MODE_WEEKNIGHTS.equals(sleepMode)) return WEEKNIGHT_DAYS;
+ if (!sleepMode.startsWith(SLEEP_MODE_DAYS_PREFIX)) return null;
+ if (sleepMode.equals(SLEEP_MODE_DAYS_PREFIX)) return null;
+ return tryParseDayList(sleepMode.substring(SLEEP_MODE_DAYS_PREFIX.length()), ",");
+ }
+
+ public static XmlV1 readXml(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int type;
+ String tag;
+ XmlV1 rt = new XmlV1();
+ final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>();
+ final ArrayList<Uri> conditionIds = new ArrayList<Uri>();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ tag = parser.getName();
+ if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
+ if (!conditionComponents.isEmpty()) {
+ rt.conditionComponents = conditionComponents
+ .toArray(new ComponentName[conditionComponents.size()]);
+ rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]);
+ }
+ return rt;
+ }
+ if (type == XmlPullParser.START_TAG) {
+ if (ALLOW_TAG.equals(tag)) {
+ rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
+ rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
+ rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
+ DEFAULT_ALLOW_REMINDERS);
+ rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS,
+ DEFAULT_ALLOW_EVENTS);
+ rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE);
+ if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) {
+ throw new IndexOutOfBoundsException("bad source in config:"
+ + rt.allowFrom);
+ }
+ } else if (SLEEP_TAG.equals(tag)) {
+ final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
+ rt.sleepMode = isValidSleepMode(mode)? mode : null;
+ rt.sleepNone = safeBoolean(parser, SLEEP_ATT_NONE, false);
+ final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0);
+ final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0);
+ final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0);
+ final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0);
+ rt.sleepStartHour = isValidHour(startHour) ? startHour : 0;
+ rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0;
+ rt.sleepEndHour = isValidHour(endHour) ? endHour : 0;
+ rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0;
+ } else if (CONDITION_TAG.equals(tag)) {
+ final ComponentName component =
+ safeComponentName(parser, CONDITION_ATT_COMPONENT);
+ final Uri conditionId = safeUri(parser, CONDITION_ATT_ID);
+ if (component != null && conditionId != null) {
+ conditionComponents.add(component);
+ conditionIds.add(conditionId);
+ }
+ } else if (EXIT_CONDITION_TAG.equals(tag)) {
+ rt.exitCondition = readConditionXml(parser);
+ if (rt.exitCondition != null) {
+ rt.exitConditionComponent =
+ safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT);
+ }
+ }
+ }
+ }
+ throw new IllegalStateException("Failed to reach END_DOCUMENT");
}
}
- // built-in next alarm conditions
- public static final String NEXT_ALARM_PATH = "next_alarm";
+ public interface Migration {
+ ZenModeConfig migrate(XmlV1 v1);
+ }
}
diff --git a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
index 52db223..0071a33 100644
--- a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
+++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
@@ -16,6 +16,8 @@
package android.service.persistentdata;
+import android.app.PendingIntent;
+import android.os.Bundle;
import android.os.ParcelFileDescriptor;
/**
@@ -30,6 +32,7 @@ interface IPersistentDataBlockService {
int write(in byte[] data);
byte[] read();
void wipe();
+ void wipeIfAllowed(in Bundle bundle, in PendingIntent pi);
int getDataBlockSize();
long getMaximumDataBlockSize();
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
index 0ffdf68..31570c6 100644
--- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -17,6 +17,8 @@
package android.service.persistentdata;
import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
@@ -41,6 +43,56 @@ import android.util.Slog;
@SystemApi
public class PersistentDataBlockManager {
private static final String TAG = PersistentDataBlockManager.class.getSimpleName();
+
+ /**
+ * Broadcast action that will be called when the {@link #wipeIfAllowed(Bundle,PendingIntent)}
+ * method is called. A broadcast with this action will be sent to the package allowed to write
+ * to the persistent data block. Packages receiving this broadcasts should respond by using the
+ * {@link android.app.PendingIntent} sent in the {@link #EXTRA_WIPE_IF_ALLOWED_CALLBACK} extra.
+ */
+ public static final String ACTION_WIPE_IF_ALLOWED
+ = "android.service.persistentdata.action.WIPE_IF_ALLOWED";
+
+ /**
+ * A {@link android.os.Parcelable} extra of type {@link android.app.PendingIntent} used to
+ * response to {@link #wipeIfAllowed(Bundle,PendingIntent)}. This extra will set in broadcasts
+ * with an action of {@link #ACTION_WIPE_IF_ALLOWED}.
+ */
+ public static final String EXTRA_WIPE_IF_ALLOWED_CALLBACK
+ = "android.service.persistentdata.extra.WIPE_IF_ALLOWED_CALLBACK";
+
+ /**
+ * Result code indicating that the data block was wiped.
+ *
+ * <p>This value is set as result code of the {@link android.app.PendingIntent} argument to
+ * {@link #wipeIfAllowed(Bundle,PendingIntent)}
+ */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * Result code indicating that a remote exception was received while processing the request.
+ *
+ * <p>This value is set as result code of the {@link android.app.PendingIntent} argument to
+ * {@link #wipeIfAllowed(Bundle,PendingIntent)}
+ */
+ public static final int STATUS_ERROR_REMOTE_EXCEPTION = 1;
+
+ /**
+ * Result code indicating that a network error occurred while processing the request.
+ *
+ * <p>This value is set as result code of the {@link android.app.PendingIntent} argument to
+ * {@link #wipeIfAllowed(Bundle,PendingIntent)}
+ */
+ public static final int STATUS_ERROR_NETWORK_ERROR = 2;
+
+ /**
+ * Result code indicating that the data block could not be cleared with the provided data.
+ *
+ * <p>This value is set as result code of the {@link android.app.PendingIntent} argument to
+ * {@link #wipeIfAllowed(Bundle,PendingIntent)}
+ */
+ public static final int STATUS_ERROR_NOT_COMPLIANT = 3;
+
private IPersistentDataBlockService sService;
public PersistentDataBlockManager(IPersistentDataBlockService service) {
@@ -118,6 +170,28 @@ public class PersistentDataBlockManager {
}
/**
+ * Attempt to wipe the data block by sending a broadcast to the package allowed to modify the
+ * datablock. The allowed package can refuse to wipe the data block based on the contents of
+ * the specified bundle. This bundle may contain data used by the allowed package to wipe the
+ * partition such as account credentials or an authorization token.
+ * @param bundle data used to wipe the data block. The contents of this bundle depend on the
+ * allowed package receiving the data.
+ * @param pi intent called when attempt finished. The result code of this intent will be set
+ * to one of {@link #STATUS_SUCCESS}, {@link #STATUS_ERROR_REMOTE_EXCEPTION},
+ * {@link #STATUS_ERROR_NETWORK_ERROR}, or {@link #STATUS_ERROR_NOT_COMPLIANT}.
+ */
+ public void wipeIfAllowed(Bundle bundle, PendingIntent pi) {
+ if (pi == null) {
+ throw new NullPointerException();
+ }
+ try {
+ sService.wipeIfAllowed(bundle, pi);
+ } catch (RemoteException e) {
+ onError("wiping persistent partition");
+ }
+ }
+
+ /**
* Writes a byte enabling or disabling the ability to "OEM unlock" the device.
*/
public void setOemUnlockEnabled(boolean enabled) {
diff --git a/core/java/android/text/method/AllCapsTransformationMethod.java b/core/java/android/text/method/AllCapsTransformationMethod.java
index f9920dd..0cea821 100644
--- a/core/java/android/text/method/AllCapsTransformationMethod.java
+++ b/core/java/android/text/method/AllCapsTransformationMethod.java
@@ -19,6 +19,7 @@ import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
+import android.widget.TextView;
import java.util.Locale;
@@ -39,11 +40,23 @@ public class AllCapsTransformationMethod implements TransformationMethod2 {
@Override
public CharSequence getTransformation(CharSequence source, View view) {
- if (mEnabled) {
- return source != null ? source.toString().toUpperCase(mLocale) : null;
+ if (!mEnabled) {
+ Log.w(TAG, "Caller did not enable length changes; not transforming text");
+ return source;
}
- Log.w(TAG, "Caller did not enable length changes; not transforming text");
- return source;
+
+ if (source == null) {
+ return null;
+ }
+
+ Locale locale = null;
+ if (view instanceof TextView) {
+ locale = ((TextView)view).getTextLocale();
+ }
+ if (locale == null) {
+ locale = mLocale;
+ }
+ return source.toString().toUpperCase(locale);
}
@Override
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index 63607fa..07c1ec3 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -22,6 +22,8 @@ import android.text.*;
import android.text.method.TextKeyListener.Capitalize;
import android.widget.TextView;
+import java.text.BreakIterator;
+
/**
* Abstract base class for key listeners.
*
@@ -63,9 +65,9 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener
private boolean backspaceOrForwardDelete(View view, Editable content, int keyCode,
KeyEvent event, boolean isForwardDelete) {
- // Ensure the key event does not have modifiers except ALT or SHIFT.
+ // Ensure the key event does not have modifiers except ALT or SHIFT or CTRL.
if (!KeyEvent.metaStateHasNoModifiers(event.getMetaState()
- & ~(KeyEvent.META_SHIFT_MASK | KeyEvent.META_ALT_MASK))) {
+ & ~(KeyEvent.META_SHIFT_MASK | KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK))) {
return false;
}
@@ -74,18 +76,28 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener
return true;
}
- // Alt+Backspace or Alt+ForwardDelete deletes the current line, if possible.
- if (getMetaState(content, META_ALT_ON, event) == 1) {
- if (deleteLine(view, content)) {
- return true;
+ // MetaKeyKeyListener doesn't track control key state. Need to check the KeyEvent instead.
+ boolean isCtrlActive = ((event.getMetaState() & KeyEvent.META_CTRL_ON) != 0);
+ boolean isShiftActive = (getMetaState(content, META_SHIFT_ON, event) == 1);
+ boolean isAltActive = (getMetaState(content, META_ALT_ON, event) == 1);
+
+ if (isCtrlActive) {
+ if (isAltActive || isShiftActive) {
+ // Ctrl+Alt, Ctrl+Shift, Ctrl+Alt+Shift should not delete any characters.
+ return false;
}
+ return deleteUntilWordBoundary(view, content, isForwardDelete);
+ }
+
+ // Alt+Backspace or Alt+ForwardDelete deletes the current line, if possible.
+ if (isAltActive && deleteLine(view, content)) {
+ return true;
}
// Delete a character.
final int start = Selection.getSelectionEnd(content);
final int end;
- if (isForwardDelete || event.isShiftPressed()
- || getMetaState(content, META_SHIFT_ON) == 1) {
+ if (isForwardDelete || event.isShiftPressed() || isShiftActive) {
end = TextUtils.getOffsetAfter(content, start);
} else {
end = TextUtils.getOffsetBefore(content, start);
@@ -97,6 +109,54 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener
return false;
}
+ private boolean deleteUntilWordBoundary(View view, Editable content, boolean isForwardDelete) {
+ int currentCursorOffset = Selection.getSelectionStart(content);
+
+ // If there is a selection, do nothing.
+ if (currentCursorOffset != Selection.getSelectionEnd(content)) {
+ return false;
+ }
+
+ // Early exit if there is no contents to delete.
+ if ((!isForwardDelete && currentCursorOffset == 0) ||
+ (isForwardDelete && currentCursorOffset == content.length())) {
+ return false;
+ }
+
+ WordIterator wordIterator = null;
+ if (view instanceof TextView) {
+ wordIterator = ((TextView)view).getWordIterator();
+ }
+
+ if (wordIterator == null) {
+ // Default locale is used for WordIterator since the appropriate locale is not clear
+ // here.
+ // TODO: Use appropriate locale for WordIterator.
+ wordIterator = new WordIterator();
+ }
+
+ int deleteFrom;
+ int deleteTo;
+
+ if (isForwardDelete) {
+ deleteFrom = currentCursorOffset;
+ wordIterator.setCharSequence(content, deleteFrom, content.length());
+ deleteTo = wordIterator.following(currentCursorOffset);
+ if (deleteTo == BreakIterator.DONE) {
+ deleteTo = content.length();
+ }
+ } else {
+ deleteTo = currentCursorOffset;
+ wordIterator.setCharSequence(content, 0, deleteTo);
+ deleteFrom = wordIterator.preceding(currentCursorOffset);
+ if (deleteFrom == BreakIterator.DONE) {
+ deleteFrom = 0;
+ }
+ }
+ content.delete(deleteFrom, deleteTo);
+ return true;
+ }
+
private boolean deleteSelection(View view, Editable content) {
int selectionStart = Selection.getSelectionStart(content);
int selectionEnd = Selection.getSelectionEnd(content);
diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java
index 0b70fdb..5209f90 100644
--- a/core/java/android/transition/TransitionManager.java
+++ b/core/java/android/transition/TransitionManager.java
@@ -430,7 +430,6 @@ public class TransitionManager {
* Ends all pending and ongoing transitions on the specified scene root.
*
* @param sceneRoot The root of the View hierarchy to end transitions on.
- * @hide
*/
public static void endTransitions(final ViewGroup sceneRoot) {
sPendingTransitions.remove(sceneRoot);
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 2eac549..1ee4780 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -49,7 +49,6 @@ public final class InputDevice implements Parcelable {
private final String mName;
private final int mVendorId;
private final int mProductId;
- private final String mUniqueId;
private final String mDescriptor;
private final InputDeviceIdentifier mIdentifier;
private final boolean mIsExternal;
@@ -57,6 +56,7 @@ public final class InputDevice implements Parcelable {
private final int mKeyboardType;
private final KeyCharacterMap mKeyCharacterMap;
private final boolean mHasVibrator;
+ private final boolean mHasMic;
private final boolean mHasButtonUnderPad;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
@@ -357,8 +357,8 @@ public final class InputDevice implements Parcelable {
// Called by native code.
private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
- int productId, String uniqueId, String descriptor, boolean isExternal, int sources,
- int keyboardType, KeyCharacterMap keyCharacterMap, boolean hasVibrator,
+ int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
+ KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasMic,
boolean hasButtonUnderPad) {
mId = id;
mGeneration = generation;
@@ -366,13 +366,13 @@ public final class InputDevice implements Parcelable {
mName = name;
mVendorId = vendorId;
mProductId = productId;
- mUniqueId = uniqueId;
mDescriptor = descriptor;
mIsExternal = isExternal;
mSources = sources;
mKeyboardType = keyboardType;
mKeyCharacterMap = keyCharacterMap;
mHasVibrator = hasVibrator;
+ mHasMic = hasMic;
mHasButtonUnderPad = hasButtonUnderPad;
mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId);
}
@@ -384,13 +384,13 @@ public final class InputDevice implements Parcelable {
mName = in.readString();
mVendorId = in.readInt();
mProductId = in.readInt();
- mUniqueId = in.readString();
mDescriptor = in.readString();
mIsExternal = in.readInt() != 0;
mSources = in.readInt();
mKeyboardType = in.readInt();
mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in);
mHasVibrator = in.readInt() != 0;
+ mHasMic = in.readInt() != 0;
mHasButtonUnderPad = in.readInt() != 0;
mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId);
@@ -509,23 +509,6 @@ public final class InputDevice implements Parcelable {
}
/**
- * Gets the vendor's unique id for the given device, if available.
- * <p>
- * A vendor may assign a unique id to a device (e.g., MAC address for
- * Bluetooth devices). A null value will be assigned where a unique id is
- * not available.
- * </p><p>
- * This method is dependent on the vendor, whereas {@link #getDescriptor}
- * attempts to create a unique id even when the vendor has not provided one.
- * </p>
- *
- * @return The unique id of a given device
- */
- public String getUniqueId() {
- return mUniqueId;
- }
-
- /**
* Gets the input device descriptor, which is a stable identifier for an input device.
* <p>
* An input device descriptor uniquely identifies an input device. Its value
@@ -737,6 +720,14 @@ public final class InputDevice implements Parcelable {
}
/**
+ * Reports whether the device has a built-in microphone.
+ * @return Whether the device has a built-in microphone.
+ */
+ public boolean hasMic() {
+ return mHasMic;
+ }
+
+ /**
* Reports whether the device has a button under its touchpad
* @return Whether the device has a button under its touchpad
* @hide
@@ -864,13 +855,13 @@ public final class InputDevice implements Parcelable {
out.writeString(mName);
out.writeInt(mVendorId);
out.writeInt(mProductId);
- out.writeString(mUniqueId);
out.writeString(mDescriptor);
out.writeInt(mIsExternal ? 1 : 0);
out.writeInt(mSources);
out.writeInt(mKeyboardType);
mKeyCharacterMap.writeToParcel(out, flags);
out.writeInt(mHasVibrator ? 1 : 0);
+ out.writeInt(mHasMic ? 1 : 0);
out.writeInt(mHasButtonUnderPad ? 1 : 0);
final int numRanges = mMotionRanges.size();
@@ -916,6 +907,8 @@ public final class InputDevice implements Parcelable {
description.append(" Has Vibrator: ").append(mHasVibrator).append("\n");
+ description.append(" Has mic: ").append(mHasMic).append("\n");
+
description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 4d1209a..0417921 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -16,6 +16,7 @@
package android.view.animation;
+import android.content.res.Configuration;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -44,6 +45,16 @@ public class AnimationUtils {
private static final int TOGETHER = 0;
private static final int SEQUENTIALLY = 1;
+ private static final float RECOMMENDED_FIELD_OF_VIEW_FOR_TV = 40f;
+ private static final float ESTIMATED_VIEWING_DISTANCE_FOR_WATCH = 11f;
+ private static final float AVERAGE_VIEWING_DISTANCE_FOR_PHONES = 14.2f;
+ private static final float N5_DIAGONAL_VIEW_ANGLE = 19.58f;
+ private static final float N5_DENSITY = 3.0f;
+ private static final float N5_DPI = 443f;
+
+ private static final float COTANGENT_OF_HALF_TV_ANGLE = (float) (1 / Math.tan(Math.toRadians
+ (RECOMMENDED_FIELD_OF_VIEW_FOR_TV / 2)));
+
/**
* Returns the current animation time in milliseconds. This time should be used when invoking
@@ -367,4 +378,78 @@ public class AnimationUtils {
}
return interpolator;
}
+
+ /**
+ * Derives the viewing distance of a device based on the device size (in inches), and the
+ * device type.
+ * @hide
+ */
+ public static float getViewingDistance(float width, float height, int uiMode) {
+ if (uiMode == Configuration.UI_MODE_TYPE_TELEVISION) {
+ // TV
+ return (width / 2) * COTANGENT_OF_HALF_TV_ANGLE;
+ } else if (uiMode == Configuration.UI_MODE_TYPE_WATCH) {
+ // Watch
+ return ESTIMATED_VIEWING_DISTANCE_FOR_WATCH;
+ } else {
+ // Tablet, phone, etc
+ return AVERAGE_VIEWING_DISTANCE_FOR_PHONES;
+ }
+ }
+
+ /**
+ * Calculates the duration scaling factor of an animation based on the hint that the animation
+ * will move across the entire screen. A scaling factor of 1 means the duration on this given
+ * device will be the same as the duration set through
+ * {@link android.animation.Animator#setDuration(long)}. The calculation uses Nexus 5 as a
+ * baseline device. That is, the duration of the animation on a given device will scale its
+ * duration so that it has the same look and feel as the animation on Nexus 5. In order to
+ * achieve the same perceived effect of the animation across different devices, we maintain
+ * the same angular speed of the same animation in users' field of view. Therefore, the
+ * duration scale factor is determined by the ratio of the angular movement on current
+ * devices to that on the baseline device.
+ *
+ * @param width width of the screen (in inches)
+ * @param height height of the screen (in inches)
+ * @param viewingDistance the viewing distance of the device (i.e. watch, phone, TV, etc) in
+ * inches
+ * @return scaling factor (or multiplier) of the duration set through
+ * {@link android.animation.Animator#setDuration(long)} on current device.
+ * @hide
+ */
+ public static float getScreenSizeBasedDurationScale(float width, float height,
+ float viewingDistance) {
+ // Animation's moving distance is proportional to the screen size.
+ float diagonal = (float) Math.sqrt(width * width + height * height);
+ float diagonalViewAngle = (float) Math.toDegrees(Math.atan((diagonal / 2f)
+ / viewingDistance) * 2);
+ return diagonalViewAngle / N5_DIAGONAL_VIEW_ANGLE;
+ }
+
+ /**
+ * Calculates the duration scaling factor of an animation under the assumption that the
+ * animation is defined to move the same amount of distance (in dp) across all devices. A
+ * scaling factor of 1 means the duration on this given device will be the same as the
+ * duration set through {@link android.animation.Animator#setDuration(long)}. The calculation
+ * uses Nexus 5 as a baseline device. That is, the duration of the animation on a given
+ * device will scale its duration so that it has the same look and feel as the animation on
+ * Nexus 5. In order to achieve the same perceived effect of the animation across different
+ * devices, we maintain the same angular velocity of the same animation in users' field of
+ * view. Therefore, the duration scale factor is determined by the ratio of the angular
+ * movement on current devices to that on the baseline device.
+ *
+ * @param density logical density of the display. {@link android.util.DisplayMetrics#density}
+ * @param dpi pixels per inch
+ * @param viewingDistance viewing distance of the device (in inches)
+ * @return the scaling factor of duration
+ * @hide
+ */
+ public static float getDpBasedDurationScale(float density, float dpi,
+ float viewingDistance) {
+ // Angle in users' field of view per dp:
+ float anglePerDp = (float) Math.atan2((density / dpi) / 2, viewingDistance) * 2;
+ float baselineAnglePerDp = (float) Math.atan2((N5_DENSITY / N5_DPI) / 2,
+ AVERAGE_VIEWING_DISTANCE_FOR_PHONES) * 2;
+ return anglePerDp / baselineAnglePerDp;
+ }
}
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 36bce0b..f951dc2 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -397,7 +397,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
public void updateMenuView(boolean cleared) {
final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
if (menuViewParent != null) {
- setupItemAnimations();
+// setupItemAnimations();
ActionBarTransition.beginDelayedTransition(menuViewParent);
}
super.updateMenuView(cleared);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 32b99a8..491826a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -557,7 +557,7 @@ public class Editor {
}
}
- private void hideInsertionPointCursorController() {
+ void hideInsertionPointCursorController() {
if (mInsertionPointCursorController != null) {
mInsertionPointCursorController.hide();
}
@@ -1668,10 +1668,12 @@ public class Editor {
if (mSelectionActionMode != null) {
// Selection action mode is already started
// TODO: revisit invocations to minimize this case.
+ mSelectionActionMode.invalidate();
return false;
}
ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
- mSelectionActionMode = mTextView.startActionMode(actionModeCallback);
+ mSelectionActionMode = mTextView.startActionMode(
+ actionModeCallback, ActionMode.TYPE_FLOATING);
return mSelectionActionMode != null;
}
@@ -1681,6 +1683,7 @@ public class Editor {
boolean startSelectionActionModeWithSelection() {
if (mSelectionActionMode != null) {
// Selection action mode is already started
+ mSelectionActionMode.invalidate();
return false;
}
@@ -1704,7 +1707,8 @@ public class Editor {
// immediately hide the newly created action bar and would be visually distracting.
if (!willExtract) {
ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
- mSelectionActionMode = mTextView.startActionMode(actionModeCallback);
+ mSelectionActionMode = mTextView.startActionMode(
+ actionModeCallback, ActionMode.TYPE_FLOATING);
}
final boolean selectionStarted = mSelectionActionMode != null || willExtract;
@@ -2963,6 +2967,28 @@ public class Editor {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ mode.setTitle(mTextView.getContext().getString(
+ com.android.internal.R.string.textSelectionCABTitle));
+ mode.setSubtitle(null);
+ mode.setTitleOptionalHint(true);
+ populateMenuWithItems(menu);
+
+ if (mCustomSelectionActionModeCallback != null) {
+ if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
+ // The custom mode can choose to cancel the action mode
+ return false;
+ }
+ }
+
+ if (menu.hasVisibleItems() || mode.getCustomView() != null) {
+ mTextView.setHasTransientState(true);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void populateMenuWithItems(Menu menu) {
final boolean legacy = mTextView.getContext().getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.LOLLIPOP;
final Context context = !legacy && menu instanceof MenuBuilder ?
@@ -2971,11 +2997,6 @@ public class Editor {
final TypedArray styledAttributes = context.obtainStyledAttributes(
com.android.internal.R.styleable.SelectionModeDrawables);
- mode.setTitle(mTextView.getContext().getString(
- com.android.internal.R.string.textSelectionCABTitle));
- mode.setSubtitle(null);
- mode.setTitleOptionalHint(true);
-
if (mTextView.canCut()) {
menu.add(0, TextView.ID_CUT, 0, com.android.internal.R.string.cut).
setIcon(styledAttributes.getResourceId(
@@ -3010,37 +3031,33 @@ public class Editor {
setShowAsAction(
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
- if (mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan()) {
- menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace).
- setShowAsAction(
- MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
- }
+ updateReplaceItem(menu);
styledAttributes.recycle();
-
- if (mCustomSelectionActionModeCallback != null) {
- if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
- // The custom mode can choose to cancel the action mode
- return false;
- }
- }
-
- if (menu.hasVisibleItems() || mode.getCustomView() != null) {
- mTextView.setHasTransientState(true);
- return true;
- } else {
- return false;
- }
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ updateReplaceItem(menu);
+
if (mCustomSelectionActionModeCallback != null) {
return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
}
return true;
}
+ private void updateReplaceItem(Menu menu) {
+ boolean canReplace = mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan();
+ boolean replaceItemExists = menu.findItem(TextView.ID_REPLACE) != null;
+ if (canReplace && !replaceItemExists) {
+ menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace).
+ setShowAsAction(
+ MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ } else if (!canReplace && replaceItemExists) {
+ menu.removeItem(TextView.ID_REPLACE);
+ }
+ }
+
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (mCustomSelectionActionModeCallback != null &&
@@ -3091,6 +3108,17 @@ public class Editor {
mTextView.getSelectionStart(), mTextView.getSelectionEnd(), mSelectionPath);
mSelectionPath.computeBounds(mSelectionBounds, true);
mSelectionBounds.bottom += mSelectionHandleHeight;
+ } else if (mCursorCount == 2) {
+ // We have a split cursor. In this case, we take the rectangle that includes both
+ // parts of the cursor to ensure we don't obscure either of them.
+ Rect firstCursorBounds = mCursorDrawable[0].getBounds();
+ Rect secondCursorBounds = mCursorDrawable[1].getBounds();
+ mSelectionBounds.set(
+ Math.min(firstCursorBounds.left, secondCursorBounds.left),
+ Math.min(firstCursorBounds.top, secondCursorBounds.top),
+ Math.max(firstCursorBounds.right, secondCursorBounds.right),
+ Math.max(firstCursorBounds.bottom, secondCursorBounds.bottom)
+ + mInsertionHandleHeight);
} else {
// We have a single cursor.
int line = mTextView.getLayout().getLineForOffset(mTextView.getSelectionStart());
@@ -3796,6 +3824,9 @@ public class Editor {
Selection.setSelection((Spannable) mTextView.getText(), offset,
mTextView.getSelectionEnd());
updateDrawable();
+ if (mSelectionActionMode != null) {
+ mSelectionActionMode.invalidate();
+ }
}
@Override
@@ -3898,6 +3929,9 @@ public class Editor {
public void updateSelection(int offset) {
Selection.setSelection((Spannable) mTextView.getText(),
mTextView.getSelectionStart(), offset);
+ if (mSelectionActionMode != null) {
+ mSelectionActionMode.invalidate();
+ }
updateDrawable();
}
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index 21213ac..4b5407a 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -46,6 +46,8 @@ import android.view.ViewGroupOverlay;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView.ScaleType;
+import com.android.internal.R;
+
/**
* Helper class for AbsListView to draw and control the Fast Scroll thumb
*/
@@ -82,6 +84,10 @@ class FastScroller {
private static final int OVERLAY_AT_THUMB = 1;
private static final int OVERLAY_ABOVE_THUMB = 2;
+ // Positions for thumb in relation to track.
+ private static final int THUMB_POSITION_MIDPOINT = 0;
+ private static final int THUMB_POSITION_INSIDE = 1;
+
// Indices for mPreviewResId.
private static final int PREVIEW_LEFT = 0;
private static final int PREVIEW_RIGHT = 1;
@@ -100,7 +106,6 @@ class FastScroller {
private final ImageView mThumbImage;
private final ImageView mTrackImage;
private final View mPreviewImage;
-
/**
* Preview image resource IDs for left- and right-aligned layouts. See
* {@link #PREVIEW_LEFT} and {@link #PREVIEW_RIGHT}.
@@ -130,6 +135,11 @@ class FastScroller {
private Drawable mThumbDrawable;
private Drawable mTrackDrawable;
private int mTextAppearance;
+ private int mThumbPosition;
+
+ // Used to convert between y-coordinate and thumb position within track.
+ private float mThumbOffset;
+ private float mThumbRange;
/** Total width of decorations. */
private int mWidth;
@@ -278,7 +288,6 @@ class FastScroller {
}
private void updateAppearance() {
- final Context context = mList.getContext();
int width = 0;
// Add track to overlay if it has an image.
@@ -298,12 +307,9 @@ class FastScroller {
// Account for minimum thumb width.
mWidth = Math.max(width, mThumbMinWidth);
- mPreviewImage.setMinimumWidth(mPreviewMinWidth);
- mPreviewImage.setMinimumHeight(mPreviewMinHeight);
-
if (mTextAppearance != 0) {
- mPrimaryText.setTextAppearance(context, mTextAppearance);
- mSecondaryText.setTextAppearance(context, mTextAppearance);
+ mPrimaryText.setTextAppearance(mTextAppearance);
+ mSecondaryText.setTextAppearance(mTextAppearance);
}
if (mTextColor != null) {
@@ -316,13 +322,11 @@ class FastScroller {
mSecondaryText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
}
- final int textMinSize = Math.max(0, mPreviewMinHeight);
- mPrimaryText.setMinimumWidth(textMinSize);
- mPrimaryText.setMinimumHeight(textMinSize);
+ final int padding = mPreviewPadding;
mPrimaryText.setIncludeFontPadding(false);
- mSecondaryText.setMinimumWidth(textMinSize);
- mSecondaryText.setMinimumHeight(textMinSize);
+ mPrimaryText.setPadding(padding, padding, padding, padding);
mSecondaryText.setIncludeFontPadding(false);
+ mSecondaryText.setPadding(padding, padding, padding, padding);
refreshDrawablePressedState();
}
@@ -330,50 +334,53 @@ class FastScroller {
public void setStyle(@StyleRes int resId) {
final Context context = mList.getContext();
final TypedArray ta = context.obtainStyledAttributes(null,
- com.android.internal.R.styleable.FastScroll, android.R.attr.fastScrollStyle, resId);
+ R.styleable.FastScroll, R.attr.fastScrollStyle, resId);
final int N = ta.getIndexCount();
for (int i = 0; i < N; i++) {
final int index = ta.getIndex(i);
switch (index) {
- case com.android.internal.R.styleable.FastScroll_position:
+ case R.styleable.FastScroll_position:
mOverlayPosition = ta.getInt(index, OVERLAY_FLOATING);
break;
- case com.android.internal.R.styleable.FastScroll_backgroundLeft:
+ case R.styleable.FastScroll_backgroundLeft:
mPreviewResId[PREVIEW_LEFT] = ta.getResourceId(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_backgroundRight:
+ case R.styleable.FastScroll_backgroundRight:
mPreviewResId[PREVIEW_RIGHT] = ta.getResourceId(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_thumbDrawable:
+ case R.styleable.FastScroll_thumbDrawable:
mThumbDrawable = ta.getDrawable(index);
break;
- case com.android.internal.R.styleable.FastScroll_trackDrawable:
+ case R.styleable.FastScroll_trackDrawable:
mTrackDrawable = ta.getDrawable(index);
break;
- case com.android.internal.R.styleable.FastScroll_textAppearance:
+ case R.styleable.FastScroll_textAppearance:
mTextAppearance = ta.getResourceId(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_textColor:
+ case R.styleable.FastScroll_textColor:
mTextColor = ta.getColorStateList(index);
break;
- case com.android.internal.R.styleable.FastScroll_textSize:
+ case R.styleable.FastScroll_textSize:
mTextSize = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_minWidth:
+ case R.styleable.FastScroll_minWidth:
mPreviewMinWidth = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_minHeight:
+ case R.styleable.FastScroll_minHeight:
mPreviewMinHeight = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_thumbMinWidth:
+ case R.styleable.FastScroll_thumbMinWidth:
mThumbMinWidth = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_thumbMinHeight:
+ case R.styleable.FastScroll_thumbMinHeight:
mThumbMinHeight = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_padding:
+ case R.styleable.FastScroll_padding:
mPreviewPadding = ta.getDimensionPixelSize(index, 0);
break;
+ case R.styleable.FastScroll_thumbPosition:
+ mThumbPosition = ta.getInt(index, THUMB_POSITION_MIDPOINT);
+ break;
}
}
@@ -478,14 +485,16 @@ class FastScroller {
final int previewResId = mPreviewResId[mLayoutFromRight ? PREVIEW_RIGHT : PREVIEW_LEFT];
mPreviewImage.setBackgroundResource(previewResId);
- // Add extra padding for text.
- final Drawable background = mPreviewImage.getBackground();
- if (background != null) {
- final Rect padding = mTempBounds;
- background.getPadding(padding);
- padding.offset(mPreviewPadding, mPreviewPadding);
- mPreviewImage.setPadding(padding.left, padding.top, padding.right, padding.bottom);
- }
+ // Propagate padding to text min width/height.
+ final int textMinWidth = Math.max(0, mPreviewMinWidth - mPreviewImage.getPaddingLeft()
+ - mPreviewImage.getPaddingRight());
+ mPrimaryText.setMinimumWidth(textMinWidth);
+ mSecondaryText.setMinimumWidth(textMinWidth);
+
+ final int textMinHeight = Math.max(0, mPreviewMinHeight - mPreviewImage.getPaddingTop()
+ - mPreviewImage.getPaddingBottom());
+ mPrimaryText.setMinimumHeight(textMinHeight);
+ mSecondaryText.setMinimumHeight(textMinHeight);
// Requires re-layout.
updateLayout();
@@ -560,6 +569,8 @@ class FastScroller {
layoutThumb();
layoutTrack();
+ updateOffsetAndRange();
+
final Rect bounds = mTempBounds;
measurePreview(mPrimaryText, bounds);
applyLayout(mPrimaryText, bounds);
@@ -758,15 +769,45 @@ class FastScroller {
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
track.measure(widthMeasureSpec, heightMeasureSpec);
+ final int top;
+ final int bottom;
+ if (mThumbPosition == THUMB_POSITION_INSIDE) {
+ top = container.top;
+ bottom = container.bottom;
+ } else {
+ final int thumbHalfHeight = thumb.getHeight() / 2;
+ top = container.top + thumbHalfHeight;
+ bottom = container.bottom - thumbHalfHeight;
+ }
+
final int trackWidth = track.getMeasuredWidth();
- final int thumbHalfHeight = thumb.getHeight() / 2;
final int left = thumb.getLeft() + (thumb.getWidth() - trackWidth) / 2;
final int right = left + trackWidth;
- final int top = container.top + thumbHalfHeight;
- final int bottom = container.bottom - thumbHalfHeight;
track.layout(left, top, right, bottom);
}
+ /**
+ * Updates the offset and range used to convert from absolute y-position to
+ * thumb position within the track.
+ */
+ private void updateOffsetAndRange() {
+ final View trackImage = mTrackImage;
+ final View thumbImage = mThumbImage;
+ final float min;
+ final float max;
+ if (mThumbPosition == THUMB_POSITION_INSIDE) {
+ final float halfThumbHeight = thumbImage.getHeight() / 2f;
+ min = trackImage.getTop() + halfThumbHeight;
+ max = trackImage.getBottom() - halfThumbHeight;
+ } else{
+ min = trackImage.getTop();
+ max = trackImage.getBottom();
+ }
+
+ mThumbOffset = min;
+ mThumbRange = max - min;
+ }
+
private void setState(int state) {
mList.removeCallbacks(mDeferHide);
@@ -1145,18 +1186,8 @@ class FastScroller {
* to place the thumb.
*/
private void setThumbPos(float position) {
- final Rect container = mContainerRect;
- final int top = container.top;
- final int bottom = container.bottom;
-
- final View trackImage = mTrackImage;
- final View thumbImage = mThumbImage;
- final float min = trackImage.getTop();
- final float max = trackImage.getBottom();
- final float offset = min;
- final float range = max - min;
- final float thumbMiddle = position * range + offset;
- thumbImage.setTranslationY(thumbMiddle - thumbImage.getHeight() / 2);
+ final float thumbMiddle = position * mThumbRange + mThumbOffset;
+ mThumbImage.setTranslationY(thumbMiddle - mThumbImage.getHeight() / 2f);
final View previewImage = mPreviewImage;
final float previewHalfHeight = previewImage.getHeight() / 2f;
@@ -1175,6 +1206,9 @@ class FastScroller {
}
// Center the preview on the thumb, constrained to the list bounds.
+ final Rect container = mContainerRect;
+ final int top = container.top;
+ final int bottom = container.bottom;
final float minP = top + previewHalfHeight;
final float maxP = bottom - previewHalfHeight;
final float previewMiddle = MathUtils.constrain(previewPos, minP, maxP);
@@ -1186,19 +1220,13 @@ class FastScroller {
}
private float getPosFromMotionEvent(float y) {
- final View trackImage = mTrackImage;
- final float min = trackImage.getTop();
- final float max = trackImage.getBottom();
- final float offset = min;
- final float range = max - min;
-
// If the list is the same height as the thumbnail or shorter,
// effectively disable scrolling.
- if (range <= 0) {
+ if (mThumbRange <= 0) {
return 0f;
}
- return MathUtils.constrain((y - offset) / range, 0f, 1f);
+ return MathUtils.constrain((y - mThumbOffset) / mThumbRange, 0f, 1f);
}
/**
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 205d35e..24e9cbe 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -60,7 +60,6 @@ import android.widget.RemoteViews.RemoteView;
import java.util.ArrayList;
-
/**
* <p>
* Visual indicator of progress in some operation. Displays a bar to the user
@@ -266,9 +265,14 @@ public class ProgressBar extends View {
final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
if (progressDrawable != null) {
- // Calling this method can set mMaxHeight, make sure the corresponding
- // XML attribute for mMaxHeight is read after calling this method
- setProgressDrawableTiled(progressDrawable);
+ // Calling setProgressDrawable can set mMaxHeight, so make sure the
+ // corresponding XML attribute for mMaxHeight is read after calling
+ // this method.
+ if (needsTileify(progressDrawable)) {
+ setProgressDrawableTiled(progressDrawable);
+ } else {
+ setProgressDrawable(progressDrawable);
+ }
}
@@ -292,13 +296,17 @@ public class ProgressBar extends View {
setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress));
- setSecondaryProgress(
- a.getInt(R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
+ setSecondaryProgress(a.getInt(
+ R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
final Drawable indeterminateDrawable = a.getDrawable(
R.styleable.ProgressBar_indeterminateDrawable);
if (indeterminateDrawable != null) {
- setIndeterminateDrawableTiled(indeterminateDrawable);
+ if (needsTileify(indeterminateDrawable)) {
+ setIndeterminateDrawableTiled(indeterminateDrawable);
+ } else {
+ setIndeterminateDrawable(indeterminateDrawable);
+ }
}
mOnlyIndeterminate = a.getBoolean(
@@ -395,6 +403,45 @@ public class ProgressBar extends View {
}
/**
+ * Returns {@code true} if the target drawable needs to be tileified.
+ *
+ * @param dr the drawable to check
+ * @return {@code true} if the target drawable needs to be tileified,
+ * {@code false} otherwise
+ */
+ private static boolean needsTileify(Drawable dr) {
+ if (dr instanceof LayerDrawable) {
+ final LayerDrawable orig = (LayerDrawable) dr;
+ final int N = orig.getNumberOfLayers();
+ for (int i = 0; i < N; i++) {
+ if (needsTileify(orig.getDrawable(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ if (dr instanceof StateListDrawable) {
+ final StateListDrawable in = (StateListDrawable) dr;
+ final int N = in.getStateCount();
+ for (int i = 0; i < N; i++) {
+ if (needsTileify(in.getStateDrawable(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // If there's a bitmap that's not wrapped with a ClipDrawable or
+ // ScaleDrawable, we'll need to wrap it and apply tiling.
+ if (dr instanceof BitmapDrawable) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
* Converts a drawable to a tiled version of itself. It will recursively
* traverse layer and state list drawables.
*/
@@ -448,18 +495,14 @@ public class ProgressBar extends View {
mSampleTile = tileBitmap;
}
- final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
- final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
- Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
- shapeDrawable.getPaint().setShader(bitmapShader);
+ final BitmapDrawable clone = (BitmapDrawable) bitmap.getConstantState().newDrawable();
+ clone.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
- // Ensure the tint and filter are propagated in the correct order.
- shapeDrawable.setTintList(bitmap.getTint());
- shapeDrawable.setTintMode(bitmap.getTintMode());
- shapeDrawable.setColorFilter(bitmap.getColorFilter());
-
- return clip ? new ClipDrawable(
- shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL) : shapeDrawable;
+ if (clip) {
+ return new ClipDrawable(clone, Gravity.LEFT, ClipDrawable.HORIZONTAL);
+ } else {
+ return clone;
+ }
}
return drawable;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9caa584..11439e4 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9067,6 +9067,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
boolean selectAllText() {
+ // Need to hide insert point cursor controller before settings selection, otherwise insert
+ // point cursor controller obtains cursor update event and update cursor with cancelling
+ // selection.
+ if (mEditor != null) {
+ mEditor.hideInsertionPointCursorController();
+ }
final int length = mText.length();
Selection.setSelection((Spannable) mText, 0, length);
return length > 0;
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 1746bed..4f0e29e 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -40,6 +40,9 @@ interface IBatteryStats {
ParcelFileDescriptor getStatisticsStream();
+ // Return true if we see the battery as currently charging.
+ boolean isCharging();
+
// Return the computed amount of time remaining on battery, in milliseconds.
// Returns -1 if nothing could be computed.
long computeBatteryTimeRemaining();
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 75beee9..fe79eff 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -140,6 +140,8 @@ public final class ProcessStats implements Parcelable {
STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT
STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
STATE_TOP, // ActivityManager.PROCESS_STATE_TOP
+ STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ STATE_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
STATE_IMPORTANT_BACKGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
STATE_BACKUP, // ActivityManager.PROCESS_STATE_BACKUP
diff --git a/core/java/com/android/internal/logging/EventLogTags.logtags b/core/java/com/android/internal/logging/EventLogTags.logtags
index 870d20d..b9208ff 100644
--- a/core/java/com/android/internal/logging/EventLogTags.logtags
+++ b/core/java/com/android/internal/logging/EventLogTags.logtags
@@ -5,3 +5,5 @@ option java_package com.android.internal.logging;
# interaction logs
524287 sysui_view_visibility (category|1|5),(visible|1|6)
524288 sysui_action (category|1|5)
+524290 sysui_count (name|3),(increment|1)
+524291 sysui_histogram (name|3),(bucket|1)
diff --git a/core/java/com/android/internal/logging/MetricsConstants.java b/core/java/com/android/internal/logging/MetricsConstants.java
index e5cba84..ee225a1 100644
--- a/core/java/com/android/internal/logging/MetricsConstants.java
+++ b/core/java/com/android/internal/logging/MetricsConstants.java
@@ -32,9 +32,16 @@ public interface MetricsConstants {
public static final int ACCOUNTS_ACCOUNT_SYNC = 9;
public static final int ACCOUNTS_CHOOSE_ACCOUNT_ACTIVITY = 10;
public static final int ACCOUNTS_MANAGE_ACCOUNTS = 11;
+ public static final int ACTION_WIFI_ADD_NETWORK = 134;
+ public static final int ACTION_WIFI_CONNECT = 135;
+ public static final int ACTION_WIFI_FORCE_SCAN = 136;
+ public static final int ACTION_WIFI_FORGET = 137;
+ public static final int ACTION_WIFI_OFF = 138;
+ public static final int ACTION_WIFI_ON = 139;
public static final int APN = 12;
public static final int APN_EDITOR = 13;
public static final int APPLICATION = 16;
+ public static final int APPLICATIONS_ADVANCED = 130;
public static final int APPLICATIONS_APP_LAUNCH = 17;
public static final int APPLICATIONS_APP_PERMISSION = 18;
public static final int APPLICATIONS_APP_STORAGE = 19;
@@ -85,8 +92,13 @@ public interface MetricsConstants {
public static final int INPUTMETHOD_USER_DICTIONARY_ADD_WORD = 62;
public static final int LOCATION = 63;
public static final int LOCATION_MODE = 64;
+ public static final int LOCATION_SCANNING = 131;
public static final int MAIN_SETTINGS = 1;
public static final int MANAGE_APPLICATIONS = 65;
+ public static final int MANAGE_APPLICATIONS_ALL = 132;
+ public static final int MANAGE_APPLICATIONS_NOTIFICATIONS = 133;
+ public static final int MANAGE_DOMAIN_URLS = 143;
+ public static final int MANAGE_PERMISSIONS = 140;
public static final int MASTER_CLEAR = 66;
public static final int MASTER_CLEAR_CONFIRM = 67;
public static final int NET_DATA_USAGE_METERED = 68;
@@ -94,10 +106,15 @@ public interface MetricsConstants {
public static final int NFC_PAYMENT = 70;
public static final int NOTIFICATION = 71;
public static final int NOTIFICATION_APP_NOTIFICATION = 72;
+ public static final int NOTIFICATION_ITEM = 128;
+ public static final int NOTIFICATION_ITEM_ACTION = 129;
public static final int NOTIFICATION_OTHER_SOUND = 73;
+ public static final int NOTIFICATION_PANEL = 127;
public static final int NOTIFICATION_REDACTION = 74;
public static final int NOTIFICATION_STATION = 75;
public static final int NOTIFICATION_ZEN_MODE = 76;
+ public static final int NOTIFICATION_ZEN_MODE_AUTOMATION = 142;
+ public static final int NOTIFICATION_ZEN_MODE_PRIORITY = 141;
public static final int OWNER_INFO = 77;
public static final int PRINT_JOB_SETTINGS = 78;
public static final int PRINT_SERVICE_SETTINGS = 79;
@@ -132,7 +149,6 @@ public interface MetricsConstants {
public static final int TRUST_AGENT = 91;
public static final int TTS_ENGINE_SETTINGS = 93;
public static final int TTS_TEXT_TO_SPEECH = 94;
- public static final int TYPE_UNKNOWN = 0;
public static final int USAGE_ACCESS = 95;
public static final int USER = 96;
public static final int USERS_APP_RESTRICTIONS = 97;
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 1038543..6be6389 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -26,23 +26,9 @@ import android.os.Build;
*/
public class MetricsLogger implements MetricsConstants {
// These constants are temporary, they should migrate to MetricsConstants.
- public static final int APPLICATIONS_ADVANCED = 132;
- public static final int LOCATION_SCANNING = 133;
- public static final int MANAGE_APPLICATIONS_ALL = 134;
- public static final int MANAGE_APPLICATIONS_NOTIFICATIONS = 135;
+ // next value is 145;
- public static final int ACTION_WIFI_ADD_NETWORK = 136;
- public static final int ACTION_WIFI_CONNECT = 137;
- public static final int ACTION_WIFI_FORCE_SCAN = 138;
- public static final int ACTION_WIFI_FORGET = 139;
- public static final int ACTION_WIFI_OFF = 140;
- public static final int ACTION_WIFI_ON = 141;
-
- public static final int MANAGE_PERMISSIONS = 142;
- public static final int NOTIFICATION_ZEN_MODE_PRIORITY = 143;
- public static final int NOTIFICATION_ZEN_MODE_AUTOMATION = 144;
-
- public static final int MANAGE_DOMAIN_URLS = 143;
+ public static final int NOTIFICATION_ZEN_MODE_SCHEDULE_RULE = 144;
public static void visible(Context context, int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
@@ -64,4 +50,14 @@ public class MetricsLogger implements MetricsConstants {
}
EventLogTags.writeSysuiAction(category);
}
+
+ /** Add an integer value to the monotonically increasing counter with the given name. */
+ public static void count(Context context, String name, int value) {
+ EventLogTags.writeSysuiCount(name, value);
+ }
+
+ /** Increment the bucket with the integer label on the histogram with the given name. */
+ public static void histogram(Context context, String name, int bucket) {
+ EventLogTags.writeSysuiHistogram(name, bucket);
+ }
}
diff --git a/core/java/com/android/internal/midi/EventScheduler.java b/core/java/com/android/internal/midi/EventScheduler.java
index 7b9a48c..506902f 100644
--- a/core/java/com/android/internal/midi/EventScheduler.java
+++ b/core/java/com/android/internal/midi/EventScheduler.java
@@ -16,6 +16,7 @@
package com.android.internal.midi;
+import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;
@@ -28,7 +29,7 @@ public class EventScheduler {
private static final long NANOS_PER_MILLI = 1000000;
private final Object mLock = new Object();
- private SortedMap<Long, FastEventQueue> mEventBuffer;
+ volatile private SortedMap<Long, FastEventQueue> mEventBuffer;
private FastEventQueue mEventPool = null;
private int mMaxPoolSize = 200;
private boolean mClosed;
@@ -68,6 +69,7 @@ public class EventScheduler {
mEventsRemoved++;
SchedulableEvent event = mFirst;
mFirst = event.mNext;
+ event.mNext = null;
return event;
}
@@ -87,7 +89,7 @@ public class EventScheduler {
*/
public static class SchedulableEvent {
private long mTimestamp;
- private SchedulableEvent mNext = null;
+ volatile private SchedulableEvent mNext = null;
/**
* @param timestamp
@@ -235,6 +237,11 @@ public class EventScheduler {
return event;
}
+ protected void flush() {
+ // Replace our event buffer with a fresh empty one
+ mEventBuffer = new TreeMap<Long, FastEventQueue>();
+ }
+
public void close() {
synchronized (mLock) {
mClosed = true;
diff --git a/core/java/com/android/internal/midi/MidiConstants.java b/core/java/com/android/internal/midi/MidiConstants.java
index 87552e4..f78f75a 100644
--- a/core/java/com/android/internal/midi/MidiConstants.java
+++ b/core/java/com/android/internal/midi/MidiConstants.java
@@ -19,7 +19,7 @@ package com.android.internal.midi;
/**
* MIDI related constants and static methods.
*/
-public class MidiConstants {
+public final class MidiConstants {
public static final byte STATUS_COMMAND_MASK = (byte) 0xF0;
public static final byte STATUS_CHANNEL_MASK = (byte) 0x0F;
@@ -85,4 +85,16 @@ public class MidiConstants {
}
return (goodBytes == 0);
}
+
+ // Returns true if this command can be used for running status
+ public static boolean allowRunningStatus(int command) {
+ // only Channel Voice and Channel Mode commands can use running status
+ return (command >= STATUS_NOTE_OFF && command < STATUS_SYSTEM_EXCLUSIVE);
+ }
+
+ // Returns true if this command cancels running status
+ public static boolean cancelsRunningStatus(int command) {
+ // System Common messages cancel running status
+ return (command >= STATUS_SYSTEM_EXCLUSIVE && command <= STATUS_END_SYSEX);
+ }
}
diff --git a/core/java/com/android/internal/midi/MidiDispatcher.java b/core/java/com/android/internal/midi/MidiDispatcher.java
index 377bc68..70e699a 100644
--- a/core/java/com/android/internal/midi/MidiDispatcher.java
+++ b/core/java/com/android/internal/midi/MidiDispatcher.java
@@ -83,4 +83,11 @@ public final class MidiDispatcher extends MidiReceiver {
}
}
}
+
+ @Override
+ public void flush() throws IOException {
+ for (MidiReceiver receiver : mReceivers) {
+ receiver.flush();
+ }
+ }
}
diff --git a/core/java/com/android/internal/midi/MidiEventScheduler.java b/core/java/com/android/internal/midi/MidiEventScheduler.java
index 42d70f6..4dc5838 100644
--- a/core/java/com/android/internal/midi/MidiEventScheduler.java
+++ b/core/java/com/android/internal/midi/MidiEventScheduler.java
@@ -28,16 +28,9 @@ public class MidiEventScheduler extends EventScheduler {
// Maintain a pool of scheduled events to reduce memory allocation.
// This pool increases performance by about 14%.
private final static int POOL_EVENT_SIZE = 16;
-
- private final MidiReceiver[] mReceivers;
+ private MidiReceiver mReceiver = new SchedulingReceiver();
private class SchedulingReceiver extends MidiReceiver {
- private final int mPortNumber;
-
- public SchedulingReceiver(int portNumber) {
- mPortNumber = portNumber;
- }
-
/**
* Store these bytes in the EventScheduler to be delivered at the specified
* time.
@@ -47,14 +40,17 @@ public class MidiEventScheduler extends EventScheduler {
throws IOException {
MidiEvent event = createScheduledEvent(msg, offset, count, timestamp);
if (event != null) {
- event.portNumber = mPortNumber;
add(event);
}
}
+
+ @Override
+ public void flush() {
+ MidiEventScheduler.this.flush();
+ }
}
public static class MidiEvent extends SchedulableEvent {
- public int portNumber;
public int count = 0;
public byte[] data;
@@ -80,17 +76,6 @@ public class MidiEventScheduler extends EventScheduler {
}
}
- public MidiEventScheduler() {
- this(0);
- }
-
- public MidiEventScheduler(int portCount) {
- mReceivers = new MidiReceiver[portCount];
- for (int i = 0; i < portCount; i++) {
- mReceivers[i] = new SchedulingReceiver(i);
- }
- }
-
/**
* Create an event that contains the message.
*/
@@ -132,15 +117,7 @@ public class MidiEventScheduler extends EventScheduler {
* @return the MidiReceiver
*/
public MidiReceiver getReceiver() {
- return mReceivers[0];
- }
-
- /**
- * This MidiReceiver will write date to the scheduling buffer.
- * @return the MidiReceiver
- */
- public MidiReceiver getReceiver(int portNumber) {
- return mReceivers[portNumber];
+ return mReceiver;
}
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index 024b7c5..59dbec6 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -21,7 +21,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.SensorManager;
import android.net.ConnectivityManager;
-import android.net.wifi.WifiManager;
import android.os.BatteryStats;
import android.os.BatteryStats.Uid;
import android.os.Bundle;
@@ -130,16 +129,11 @@ public final class BatteryStatsHelper {
return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
}
- public static boolean checkHasWifiPowerReporting(Context context, PowerProfile profile) {
- WifiManager manager = context.getSystemService(WifiManager.class);
- if (manager.isEnhancedPowerReportingSupported()) {
- if (profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE) != 0 &&
- profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX) != 0 &&
- profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX) != 0) {
- return true;
- }
- }
- return false;
+ public static boolean checkHasWifiPowerReporting(BatteryStats stats, PowerProfile profile) {
+ return stats.hasWifiActivityReporting() &&
+ profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE) != 0 &&
+ profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX) != 0 &&
+ profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX) != 0;
}
public BatteryStatsHelper(Context context) {
@@ -339,7 +333,7 @@ public final class BatteryStatsHelper {
mMobileRadioPowerCalculator.reset(mStats);
if (mWifiPowerCalculator == null) {
- if (checkHasWifiPowerReporting(mContext, mPowerProfile)) {
+ if (checkHasWifiPowerReporting(mStats, mPowerProfile)) {
mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile);
} else {
mWifiPowerCalculator = new WifiPowerEstimator(mPowerProfile);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 793d0d3..fbb2dfc 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -22,6 +22,7 @@ import android.bluetooth.BluetoothActivityEnergyInfo;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.content.Context;
+import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkStats;
import android.net.wifi.WifiActivityEnergyInfo;
@@ -127,6 +128,7 @@ public final class BatteryStatsImpl extends BatteryStats {
static final int MSG_UPDATE_WAKELOCKS = 1;
static final int MSG_REPORT_POWER_CHANGE = 2;
+ static final int MSG_REPORT_CHARGING = 3;
static final long DELAY_UPDATE_WAKELOCKS = 5*1000;
private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
@@ -135,6 +137,7 @@ public final class BatteryStatsImpl extends BatteryStats {
public interface BatteryCallback {
public void batteryNeedsCpuUpdate();
public void batteryPowerChanged(boolean onBattery);
+ public void batterySendBroadcast(Intent intent);
}
final class MyHandler extends Handler {
@@ -156,6 +159,18 @@ public final class BatteryStatsImpl extends BatteryStats {
cb.batteryPowerChanged(msg.arg1 != 0);
}
break;
+ case MSG_REPORT_CHARGING:
+ if (cb != null) {
+ final String action;
+ synchronized (BatteryStatsImpl.this) {
+ action = mCharging ? BatteryManager.ACTION_CHARGING
+ : BatteryManager.ACTION_DISCHARGING;
+ }
+ Intent intent = new Intent(action);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ cb.batterySendBroadcast(intent);
+ }
+ break;
}
}
}
@@ -393,6 +408,12 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mOnBattery;
boolean mOnBatteryInternal;
+ /**
+ * External reporting of whether the device is actually charging.
+ */
+ boolean mCharging = true;
+ int mLastChargingStateLevel;
+
/*
* These keep track of battery levels (1-100) at the last plug event and the last unplug event.
*/
@@ -451,6 +472,8 @@ public final class BatteryStatsImpl extends BatteryStats {
private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry();
private PowerProfile mPowerProfile;
+ private boolean mHasWifiEnergyReporting = false;
+ private boolean mHasBluetoothEnergyReporting = false;
/*
* Holds a SamplingTimer associated with each kernel wakelock name being tracked.
@@ -4277,6 +4300,10 @@ public final class BatteryStatsImpl extends BatteryStats {
return mBluetoothStateTimer[bluetoothState].getCountLocked(which);
}
+ @Override public boolean hasBluetoothActivityReporting() {
+ return mHasBluetoothEnergyReporting;
+ }
+
@Override public long getBluetoothControllerActivity(int type, int which) {
if (type >= 0 && type < mBluetoothActivityCounters.length) {
return mBluetoothActivityCounters[type].getCountLocked(which);
@@ -4284,6 +4311,10 @@ public final class BatteryStatsImpl extends BatteryStats {
return 0;
}
+ @Override public boolean hasWifiActivityReporting() {
+ return mHasWifiEnergyReporting;
+ }
+
@Override public long getWifiControllerActivity(int type, int which) {
if (type >= 0 && type < mWifiActivityCounters.length) {
return mWifiActivityCounters[type].getCountLocked(which);
@@ -7243,6 +7274,10 @@ public final class BatteryStatsImpl extends BatteryStats {
return mOnBattery;
}
+ public boolean isCharging() {
+ return mCharging;
+ }
+
public boolean isScreenOn() {
return mScreenState == Display.STATE_ON;
}
@@ -7542,6 +7577,8 @@ public final class BatteryStatsImpl extends BatteryStats {
}
if (info != null) {
+ mHasWifiEnergyReporting = true;
+
// Measured in mAms
final long txTimeMs = info.getControllerTxTimeMillis();
final long rxTimeMs = info.getControllerRxTimeMillis();
@@ -7753,6 +7790,7 @@ public final class BatteryStatsImpl extends BatteryStats {
*/
public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info) {
if (info != null && mOnBatteryInternal && false) {
+ mHasBluetoothEnergyReporting = true;
mBluetoothActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
info.getControllerRxTimeMillis());
mBluetoothActivityCounters[CONTROLLER_TX_TIME].addCountLocked(
@@ -7802,6 +7840,20 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ boolean setChargingLocked(boolean charging) {
+ if (mCharging != charging) {
+ mCharging = charging;
+ if (charging) {
+ mHistoryCur.states |= HistoryItem.STATE_CHARGING_FLAG;
+ } else {
+ mHistoryCur.states &= ~HistoryItem.STATE_CHARGING_FLAG;
+ }
+ mHandler.sendEmptyMessage(MSG_REPORT_CHARGING);
+ return true;
+ }
+ return false;
+ }
+
void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery,
final int oldStatus, final int level) {
boolean doWrite = false;
@@ -7861,6 +7913,10 @@ public final class BatteryStatsImpl extends BatteryStats {
reset = true;
mDischargeStepTracker.init();
}
+ if (mCharging) {
+ setChargingLocked(false);
+ }
+ mLastChargingStateLevel = level;
mOnBattery = mOnBatteryInternal = true;
mLastDischargeStepLevel = level;
mMinDischargeStepLevel = level;
@@ -7890,6 +7946,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mDischargeAmountScreenOff = 0;
updateTimeBasesLocked(true, !screenOn, uptime, realtime);
} else {
+ mLastChargingStateLevel = level;
mOnBattery = mOnBatteryInternal = false;
pullPendingStateUpdatesLocked();
mHistoryCur.batteryLevel = (byte)level;
@@ -7982,10 +8039,13 @@ public final class BatteryStatsImpl extends BatteryStats {
mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
}
}
+ // Always start out assuming charging, that will be updated later.
+ mHistoryCur.states |= HistoryItem.STATE_CHARGING_FLAG;
mHistoryCur.batteryStatus = (byte)status;
mHistoryCur.batteryLevel = (byte)level;
mMaxChargeStepLevel = mMinDischargeStepLevel =
mLastChargeStepLevel = mLastDischargeStepLevel = level;
+ mLastChargingStateLevel = level;
} else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) {
recordDailyStatsIfNeededLocked(level >= 100 && onBattery);
}
@@ -8046,13 +8106,11 @@ public final class BatteryStatsImpl extends BatteryStats {
mHistoryCur.batteryVoltage = (char)volt;
changed = true;
}
- if (changed) {
- addHistoryRecordLocked(elapsedRealtime, uptime);
- }
long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
| (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
| (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
if (onBattery) {
+ changed |= setChargingLocked(false);
if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) {
mDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
modeBits, elapsedRealtime);
@@ -8064,6 +8122,28 @@ public final class BatteryStatsImpl extends BatteryStats {
mModStepMode = 0;
}
} else {
+ if (level >= 90) {
+ // If the battery level is at least 90%, always consider the device to be
+ // charging even if it happens to go down a level.
+ changed |= setChargingLocked(true);
+ mLastChargeStepLevel = level;
+ } if (!mCharging) {
+ if (mLastChargeStepLevel < level) {
+ // We have not reporting that we are charging, but the level has now
+ // gone up, so consider the state to be charging.
+ changed |= setChargingLocked(true);
+ mLastChargeStepLevel = level;
+ }
+ } else {
+ if (mLastChargeStepLevel > level) {
+ // We had reported that the device was charging, but here we are with
+ // power connected and the level going down. Looks like the current
+ // power supplied isn't enough, so consider the device to now be
+ // discharging.
+ changed |= setChargingLocked(false);
+ mLastChargeStepLevel = level;
+ }
+ }
if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) {
mChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
modeBits, elapsedRealtime);
@@ -8075,6 +8155,9 @@ public final class BatteryStatsImpl extends BatteryStats {
mModStepMode = 0;
}
}
+ if (changed) {
+ addHistoryRecordLocked(elapsedRealtime, uptime);
+ }
}
if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) {
// We don't record history while we are plugged in and fully charged.
@@ -9463,6 +9546,8 @@ public final class BatteryStatsImpl extends BatteryStats {
mWifiActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
}
+ mHasWifiEnergyReporting = in.readInt() != 0;
+ mHasBluetoothEnergyReporting = in.readInt() != 0;
mNumConnectivityChange = in.readInt();
mLoadedNumConnectivityChange = in.readInt();
mUnpluggedNumConnectivityChange = in.readInt();
@@ -9616,6 +9701,8 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
mWifiActivityCounters[i].writeToParcel(out);
}
+ out.writeInt(mHasWifiEnergyReporting ? 1 : 0);
+ out.writeInt(mHasBluetoothEnergyReporting ? 1 : 0);
out.writeInt(mNumConnectivityChange);
out.writeInt(mLoadedNumConnectivityChange);
out.writeInt(mUnpluggedNumConnectivityChange);
diff --git a/core/java/com/android/internal/os/InstallerConnection.java b/core/java/com/android/internal/os/InstallerConnection.java
index a4cdf19..671bf24 100644
--- a/core/java/com/android/internal/os/InstallerConnection.java
+++ b/core/java/com/android/internal/os/InstallerConnection.java
@@ -90,12 +90,15 @@ public class InstallerConnection {
}
}
- public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) {
- return dexopt(apkPath, uid, isPublic, "*", instructionSet, false, false, null);
+ public int dexopt(String apkPath, int uid, boolean isPublic,
+ String instructionSet, int dexoptNeeded) {
+ return dexopt(apkPath, uid, isPublic, "*", instructionSet, dexoptNeeded,
+ false, false, null);
}
public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet, boolean vmSafeMode, boolean debuggable, String outputPath) {
+ String instructionSet, int dexoptNeeded, boolean vmSafeMode,
+ boolean debuggable, String outputPath) {
StringBuilder builder = new StringBuilder("dexopt");
builder.append(' ');
builder.append(apkPath);
@@ -106,6 +109,8 @@ public class InstallerConnection {
builder.append(pkgName);
builder.append(' ');
builder.append(instructionSet);
+ builder.append(' ');
+ builder.append(dexoptNeeded);
builder.append(vmSafeMode ? " 1" : " 0");
builder.append(debuggable ? " 1" : " 0");
builder.append(' ');
@@ -113,25 +118,6 @@ public class InstallerConnection {
return execute(builder.toString());
}
- public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) {
- return patchoat(apkPath, uid, isPublic, "*", instructionSet);
- }
-
- public int patchoat(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet) {
- StringBuilder builder = new StringBuilder("patchoat");
- builder.append(' ');
- builder.append(apkPath);
- builder.append(' ');
- builder.append(uid);
- builder.append(isPublic ? " 1" : " 0");
- builder.append(' ');
- builder.append(pkgName);
- builder.append(' ');
- builder.append(instructionSet);
- return execute(builder.toString());
- }
-
private boolean connect() {
if (mSocket != null) {
return true;
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
index 4e77f6b..4fb8b55 100644
--- a/core/java/com/android/internal/os/WifiPowerCalculator.java
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -16,12 +16,15 @@
package com.android.internal.os;
import android.os.BatteryStats;
+import android.util.Log;
/**
* WiFi power calculator for when BatteryStats supports energy reporting
* from the WiFi controller.
*/
public class WifiPowerCalculator extends PowerCalculator {
+ private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+ private static final String TAG = "WifiPowerCalculator";
private final double mIdleCurrentMa;
private final double mTxCurrentMa;
private final double mRxCurrentMa;
@@ -75,6 +78,10 @@ public class WifiPowerCalculator extends PowerCalculator {
+ (rxTimeMs * mRxCurrentMa)) / (1000*60*60);
}
app.wifiPowerMah = Math.max(0, powerDrain - mTotalAppPowerDrain);
+
+ if (DEBUG) {
+ Log.d(TAG, "left over WiFi power: " + BatteryStatsHelper.makemAh(app.wifiPowerMah));
+ }
}
@Override
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 70f7b72..3ad4f1c 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -467,12 +467,11 @@ public class ZygoteInit {
try {
for (String classPathElement : classPathElements) {
- final byte dexopt = DexFile.isDexOptNeededInternal(classPathElement, "*", instructionSet,
- false /* defer */);
- if (dexopt == DexFile.DEXOPT_NEEDED) {
- installer.dexopt(classPathElement, Process.SYSTEM_UID, false, instructionSet);
- } else if (dexopt == DexFile.PATCHOAT_NEEDED) {
- installer.patchoat(classPathElement, Process.SYSTEM_UID, false, instructionSet);
+ final int dexoptNeeded = DexFile.getDexOptNeeded(
+ classPathElement, "*", instructionSet, false /* defer */);
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ installer.dexopt(classPathElement, Process.SYSTEM_UID, false,
+ instructionSet, dexoptNeeded);
}
}
} catch (IOException ioe) {
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index be9945d..2219ad1 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -24,7 +24,9 @@ import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
+import android.util.Size;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -32,19 +34,28 @@ import android.view.MenuItem;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.Transformation;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
+import android.widget.ListView;
import android.widget.PopupWindow;
-
-import com.android.internal.R;
-import com.android.internal.util.Preconditions;
+import android.widget.TextView;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
+import com.android.internal.R;
+import com.android.internal.util.Preconditions;
+
/**
* A floating toolbar for showing contextual menu items.
* This view shows as many menu item buttons as can fit in the horizontal toolbar and the
@@ -53,6 +64,9 @@ import java.util.List;
*/
public final class FloatingToolbar {
+ // This class is responsible for the public API of the floating toolbar.
+ // It delegates rendering operations to the FloatingToolbarPopup.
+
private static final MenuItem.OnMenuItemClickListener NO_OP_MENUITEM_CLICK_LISTENER =
new MenuItem.OnMenuItemClickListener() {
@Override
@@ -63,17 +77,6 @@ public final class FloatingToolbar {
private final Context mContext;
private final FloatingToolbarPopup mPopup;
- private final ViewGroup mMenuItemButtonsContainer;
- private final View.OnClickListener mMenuItemButtonOnClickListener =
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (v.getTag() instanceof MenuItem) {
- mMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag());
- mPopup.dismiss();
- }
- }
- };
private final Rect mContentRect = new Rect();
private final Point mCoordinates = new Point();
@@ -81,17 +84,17 @@ public final class FloatingToolbar {
private Menu mMenu;
private List<CharSequence> mShowingTitles = new ArrayList<CharSequence>();
private MenuItem.OnMenuItemClickListener mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER;
- private View mOpenOverflowButton;
private int mSuggestedWidth;
+ private boolean mWidthChanged = true;
+ private int mOverflowDirection;
/**
* Initializes a floating toolbar.
*/
public FloatingToolbar(Context context, Window window) {
mContext = Preconditions.checkNotNull(context);
- mPopup = new FloatingToolbarPopup(Preconditions.checkNotNull(window.getDecorView()));
- mMenuItemButtonsContainer = createMenuButtonsContainer(context);
+ mPopup = new FloatingToolbarPopup(window.getDecorView());
}
/**
@@ -137,6 +140,10 @@ public final class FloatingToolbar {
* toolbar.
*/
public FloatingToolbar setSuggestedWidth(int suggestedWidth) {
+ // Check if there's been a substantial width spec change.
+ int difference = Math.abs(suggestedWidth - mSuggestedWidth);
+ mWidthChanged = difference > (mSuggestedWidth * 0.2);
+
mSuggestedWidth = suggestedWidth;
return this;
}
@@ -146,16 +153,18 @@ public final class FloatingToolbar {
*/
public FloatingToolbar show() {
List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
- if (hasContentChanged(menuItems) || hasWidthChanged()) {
+ if (!isCurrentlyShowing(menuItems) || mWidthChanged) {
mPopup.dismiss();
- layoutMenuItemButtons(menuItems);
+ mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth);
mShowingTitles = getMenuItemTitles(menuItems);
}
refreshCoordinates();
+ mPopup.setOverflowDirection(mOverflowDirection);
mPopup.updateCoordinates(mCoordinates.x, mCoordinates.y);
if (!mPopup.isShowing()) {
mPopup.show(mCoordinates.x, mCoordinates.y);
}
+ mWidthChanged = false;
return this;
}
@@ -189,45 +198,26 @@ public final class FloatingToolbar {
* Refreshes {@link #mCoordinates} with values based on {@link #mContentRect}.
*/
private void refreshCoordinates() {
- int popupWidth = mPopup.getWidth();
- int popupHeight = mPopup.getHeight();
- if (!mPopup.isShowing()) {
- // Popup isn't yet shown, get estimated size from the menu item buttons container.
- mMenuItemButtonsContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- popupWidth = mMenuItemButtonsContainer.getMeasuredWidth();
- popupHeight = mMenuItemButtonsContainer.getMeasuredHeight();
- }
- int x = mContentRect.centerX() - popupWidth / 2;
+ int x = mContentRect.centerX() - mPopup.getWidth() / 2;
int y;
- if (shouldDisplayAtTopOfContent()) {
- y = mContentRect.top - popupHeight;
+ if (mContentRect.top > mPopup.getHeight()) {
+ y = mContentRect.top - mPopup.getHeight();
+ mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_UP;
+ } else if (mContentRect.top > getEstimatedToolbarHeight(mContext)) {
+ y = mContentRect.top - getEstimatedToolbarHeight(mContext);
+ mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN;
} else {
y = mContentRect.bottom;
+ mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN;
}
mCoordinates.set(x, y);
}
/**
- * Returns true if this floating toolbar's menu items have been reordered or changed.
- */
- private boolean hasContentChanged(List<MenuItem> menuItems) {
- return !mShowingTitles.equals(getMenuItemTitles(menuItems));
- }
-
- /**
- * Returns true if there is a significant change in width of the toolbar.
- */
- private boolean hasWidthChanged() {
- int actualWidth = mPopup.getWidth();
- int difference = Math.abs(actualWidth - mSuggestedWidth);
- return difference > (actualWidth * 0.2);
- }
-
- /**
- * Returns true if the preferred positioning of the toolbar is above the content rect.
+ * Returns true if this floating toolbar is currently showing the specified menu items.
*/
- private boolean shouldDisplayAtTopOfContent() {
- return mContentRect.top - getMinimumOverflowHeight(mContext) > 0;
+ private boolean isCurrentlyShowing(List<MenuItem> menuItems) {
+ return mShowingTitles.equals(getMenuItemTitles(menuItems));
}
/**
@@ -258,178 +248,162 @@ public final class FloatingToolbar {
return titles;
}
- private void layoutMenuItemButtons(List<MenuItem> menuItems) {
- final int toolbarWidth = getAdjustedToolbarWidth(mContext, mSuggestedWidth)
- // Reserve space for the "open overflow" button.
- - getEstimatedOpenOverflowButtonWidth(mContext);
-
- int availableWidth = toolbarWidth;
- LinkedList<MenuItem> remainingMenuItems = new LinkedList<MenuItem>(menuItems);
-
- mMenuItemButtonsContainer.removeAllViews();
-
- boolean isFirstItem = true;
- while (!remainingMenuItems.isEmpty()) {
- final MenuItem menuItem = remainingMenuItems.peek();
- Button menuItemButton = createMenuItemButton(mContext, menuItem);
-
- // Adding additional left padding for the first button to even out button spacing.
- if (isFirstItem) {
- menuItemButton.setPadding(
- 2 * menuItemButton.getPaddingLeft(),
- menuItemButton.getPaddingTop(),
- menuItemButton.getPaddingRight(),
- menuItemButton.getPaddingBottom());
- isFirstItem = false;
- }
-
- // Adding additional right padding for the last button to even out button spacing.
- if (remainingMenuItems.size() == 1) {
- menuItemButton.setPadding(
- menuItemButton.getPaddingLeft(),
- menuItemButton.getPaddingTop(),
- 2 * menuItemButton.getPaddingRight(),
- menuItemButton.getPaddingBottom());
- }
-
- menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
- if (menuItemButtonWidth <= availableWidth) {
- menuItemButton.setTag(menuItem);
- menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
- mMenuItemButtonsContainer.addView(menuItemButton);
- menuItemButton.getLayoutParams().width = menuItemButtonWidth;
- availableWidth -= menuItemButtonWidth;
- remainingMenuItems.pop();
- } else {
- // The "open overflow" button launches the vertical overflow from the
- // floating toolbar.
- createOpenOverflowButtonIfNotExists();
- mMenuItemButtonsContainer.addView(mOpenOverflowButton);
- break;
- }
- }
- mPopup.setContentView(mMenuItemButtonsContainer);
- }
-
- /**
- * Creates and returns the button that opens the vertical overflow.
- */
- private void createOpenOverflowButtonIfNotExists() {
- mOpenOverflowButton = (ImageButton) LayoutInflater.from(mContext)
- .inflate(R.layout.floating_popup_open_overflow_button, null);
- mOpenOverflowButton.setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Open the overflow.
- }
- });
- }
-
- /**
- * Creates and returns a floating toolbar menu buttons container.
- */
- private static ViewGroup createMenuButtonsContainer(Context context) {
- return (ViewGroup) LayoutInflater.from(context)
- .inflate(R.layout.floating_popup_container, null);
- }
/**
- * Creates and returns a menu button for the specified menu item.
+ * 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.
*/
- private static Button createMenuItemButton(Context context, MenuItem menuItem) {
- Button menuItemButton = (Button) LayoutInflater.from(context)
- .inflate(R.layout.floating_popup_menu_button, null);
- menuItemButton.setText(menuItem.getTitle());
- menuItemButton.setContentDescription(menuItem.getTitle());
- return menuItemButton;
- }
-
- private static int getMinimumOverflowHeight(Context context) {
- return context.getResources().
- getDimensionPixelSize(R.dimen.floating_toolbar_minimum_overflow_height);
- }
+ private static final class FloatingToolbarPopup {
- private static int getEstimatedOpenOverflowButtonWidth(Context context) {
- return context.getResources()
- .getDimensionPixelSize(R.dimen.floating_toolbar_menu_button_minimum_width);
- }
+ public static final int OVERFLOW_DIRECTION_UP = 0;
+ public static final int OVERFLOW_DIRECTION_DOWN = 1;
- private static int getAdjustedToolbarWidth(Context context, int width) {
- if (width <= 0 || width > getScreenWidth(context)) {
- width = context.getResources()
- .getDimensionPixelSize(R.dimen.floating_toolbar_default_width);
- }
- return width;
- }
+ private final View mParent;
+ private final PopupWindow mPopupWindow;
+ private final ViewGroup mContentContainer;
+ private final int mPadding;
- /**
- * Returns the device's screen width.
- */
- public static int getScreenWidth(Context context) {
- return context.getResources().getDisplayMetrics().widthPixels;
- }
+ private final Animation.AnimationListener mOnOverflowOpened =
+ new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {}
- /**
- * Returns the device's screen height.
- */
- public static int getScreenHeight(Context context) {
- return context.getResources().getDisplayMetrics().heightPixels;
- }
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // This animation should never be run if the overflow panel has not been
+ // initialized.
+ Preconditions.checkNotNull(mOverflowPanel);
+ mContentContainer.removeAllViews();
+ mContentContainer.addView(mOverflowPanel.getView());
+ mOverflowPanel.fadeIn(true);
+ setContentAreaAsTouchableSurface();
+ }
+ @Override
+ public void onAnimationRepeat(Animation animation) {}
+ };
+ private final Animation.AnimationListener mOnOverflowClosed =
+ new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {}
- /**
- * A popup window used by the floating toolbar.
- */
- private static final class FloatingToolbarPopup {
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // This animation should never be run if the main panel has not been
+ // initialized.
+ Preconditions.checkNotNull(mMainPanel);
+ mContentContainer.removeAllViews();
+ mContentContainer.addView(mMainPanel.getView());
+ mMainPanel.fadeIn(true);
+ setContentAreaAsTouchableSurface();
+ }
- private final View mParent;
- private final PopupWindow mPopupWindow;
- private final ViewGroup mContentContainer;
- private final Animator.AnimatorListener mOnDismissEnd =
- new AnimatorListenerAdapter() {
@Override
- public void onAnimationEnd(Animator animation) {
- mPopupWindow.dismiss();
- mDismissAnimating = false;
+ public void onAnimationRepeat(Animation animation) {
}
};
private final AnimatorSet mGrowFadeInFromBottomAnimation;
private final AnimatorSet mShrinkFadeOutFromBottomAnimation;
+ private final Runnable mOpenOverflow = new Runnable() {
+ @Override
+ public void run() {
+ openOverflow();
+ }
+ };
+ private final Runnable mCloseOverflow = new Runnable() {
+ @Override
+ public void run() {
+ closeOverflow();
+ }
+ };
+
+ private final Region mTouchableRegion = new Region();
+
private boolean mDismissAnimating;
+ private FloatingToolbarOverflowPanel mOverflowPanel;
+ private FloatingToolbarMainPanel mMainPanel;
+ private int mOverflowDirection;
+
/**
- * Initializes a new floating bar popup.
+ * Initializes a new floating toolbar popup.
*
- * @param parent A parent view to get the {@link View#getWindowToken()} token from.
+ * @param parent A parent view to get the {@link android.view.View#getWindowToken()} token
+ * from.
*/
public FloatingToolbarPopup(View parent) {
mParent = Preconditions.checkNotNull(parent);
mContentContainer = createContentContainer(parent.getContext());
mPopupWindow = createPopupWindow(mContentContainer);
mGrowFadeInFromBottomAnimation = createGrowFadeInFromBottom(mContentContainer);
- mShrinkFadeOutFromBottomAnimation =
- createShrinkFadeOutFromBottomAnimation(mContentContainer, mOnDismissEnd);
+ mShrinkFadeOutFromBottomAnimation = createShrinkFadeOutFromBottomAnimation(
+ mContentContainer,
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPopupWindow.dismiss();
+ mDismissAnimating = false;
+ setMainPanelAsContent();
+ }
+ });
+ // Make the touchable area of this popup be the area specified by mTouchableRegion.
+ mPopupWindow.getContentView()
+ .getRootView()
+ .getViewTreeObserver()
+ .addOnComputeInternalInsetsListener(
+ new ViewTreeObserver.OnComputeInternalInsetsListener() {
+ public void onComputeInternalInsets(
+ ViewTreeObserver.InternalInsetsInfo info) {
+ info.contentInsets.setEmpty();
+ info.visibleInsets.setEmpty();
+ info.touchableRegion.set(mTouchableRegion);
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo
+ .TOUCHABLE_INSETS_REGION);
+ }
+ });
+ mPadding = parent.getResources().getDimensionPixelSize(R.dimen.floating_toolbar_margin);
+ }
+
+ /**
+ * Lays out buttons for the specified menu items.
+ */
+ public void layoutMenuItems(List<MenuItem> menuItems,
+ MenuItem.OnMenuItemClickListener menuItemClickListener, int suggestedWidth) {
+ mContentContainer.removeAllViews();
+ if (mMainPanel == null) {
+ mMainPanel = new FloatingToolbarMainPanel(mParent.getContext(), mOpenOverflow);
+ }
+ List<MenuItem> overflowMenuItems =
+ mMainPanel.layoutMenuItems(menuItems, suggestedWidth);
+ mMainPanel.setOnMenuItemClickListener(menuItemClickListener);
+ if (!overflowMenuItems.isEmpty()) {
+ if (mOverflowPanel == null) {
+ mOverflowPanel =
+ new FloatingToolbarOverflowPanel(mParent.getContext(), mCloseOverflow);
+ }
+ mOverflowPanel.setMenuItems(overflowMenuItems);
+ mOverflowPanel.setOnMenuItemClickListener(menuItemClickListener);
+ }
+ updatePopupSize();
}
/**
* Shows this popup at the specified coordinates.
* The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
- * If this popup is already showing, this will be a no-op.
*/
public void show(int x, int y) {
if (isShowing()) {
- updateCoordinates(x, y);
return;
}
- mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, 0, 0);
- positionOnScreen(x, y);
+ stopDismissAnimation();
+ preparePopupContent();
+ mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, x, y);
growFadeInFromBottom();
-
- mDismissAnimating = false;
}
/**
@@ -440,12 +414,9 @@ public final class FloatingToolbar {
return;
}
- if (mDismissAnimating) {
- // This window is already dismissing. Don't restart the animation.
- return;
- }
mDismissAnimating = true;
shrinkFadeOutFromBottom();
+ setZeroTouchableSurface();
}
/**
@@ -460,32 +431,40 @@ public final class FloatingToolbar {
* The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
*/
public void updateCoordinates(int x, int y) {
- if (isShowing()) {
- positionOnScreen(x, y);
+ if (mDismissAnimating) {
+ // Already being dismissed. Ignore.
+ return;
}
+
+ preparePopupContent();
+ mPopupWindow.update(x, y, getWidth(), getHeight());
}
/**
- * Sets the content of this popup.
+ * Sets the direction in which the overflow will open. i.e. up or down.
+ *
+ * @param overflowDirection Either {@link #OVERFLOW_DIRECTION_UP}
+ * or {@link #OVERFLOW_DIRECTION_DOWN}.
*/
- public void setContentView(View view) {
- Preconditions.checkNotNull(view);
- mContentContainer.removeAllViews();
- mContentContainer.addView(view);
+ public void setOverflowDirection(int overflowDirection) {
+ mOverflowDirection = overflowDirection;
+ if (mOverflowPanel != null) {
+ mOverflowPanel.setOverflowDirection(mOverflowDirection);
+ }
}
/**
* Returns the width of this popup.
*/
public int getWidth() {
- return mContentContainer.getWidth();
+ return mPopupWindow.getWidth();
}
/**
* Returns the height of this popup.
*/
public int getHeight() {
- return mContentContainer.getHeight();
+ return mPopupWindow.getHeight();
}
/**
@@ -495,24 +474,10 @@ public final class FloatingToolbar {
return mContentContainer.getContext();
}
- private void positionOnScreen(int x, int y) {
- if (getWidth() == 0) {
- // content size is yet to be measured.
- mContentContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- }
- x = clamp(x, 0, getScreenWidth(getContext()) - getWidth());
- y = clamp(y, 0, getScreenHeight(getContext()) - getHeight());
-
- // Position the view w.r.t. the window.
- mContentContainer.setX(x);
- mContentContainer.setY(y);
- }
-
/**
* Performs the "grow and fade in from the bottom" animation on the floating popup.
*/
private void growFadeInFromBottom() {
- setPivot();
mGrowFadeInFromBottomAnimation.start();
}
@@ -520,77 +485,643 @@ public final class FloatingToolbar {
* Performs the "shrink and fade out from bottom" animation on the floating popup.
*/
private void shrinkFadeOutFromBottom() {
- setPivot();
mShrinkFadeOutFromBottomAnimation.start();
}
+ private void stopDismissAnimation() {
+ mDismissAnimating = false;
+ mShrinkFadeOutFromBottomAnimation.cancel();
+ }
+
+ /**
+ * Opens the floating toolbar overflow.
+ * This method should not be called if menu items have not been laid out with
+ * {@link #layoutMenuItems(List, MenuItem.OnMenuItemClickListener, int)}.
+ *
+ * @throws IllegalStateException if called when menu items have not been laid out.
+ */
+ private void openOverflow() {
+ Preconditions.checkNotNull(mMainPanel);
+ Preconditions.checkNotNull(mOverflowPanel);
+
+ mMainPanel.fadeOut(true);
+ Size overflowPanelSize = mOverflowPanel.measure();
+ final int targetWidth = getOverflowWidth(mParent.getContext());
+ final int targetHeight = overflowPanelSize.getHeight();
+ final boolean morphUpwards = (mOverflowDirection == OVERFLOW_DIRECTION_UP);
+ final int startWidth = mContentContainer.getWidth();
+ final int startHeight = mContentContainer.getHeight();
+ final float startY = mContentContainer.getY();
+ final float right = mContentContainer.getX() + 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);
+ mContentContainer.setX(right - mContentContainer.getWidth());
+ }
+ };
+ 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);
+ }
+ }
+ };
+ widthAnimation.setDuration(240);
+ heightAnimation.setDuration(180);
+ heightAnimation.setStartOffset(60);
+ AnimationSet animation = new AnimationSet(true);
+ animation.setAnimationListener(mOnOverflowOpened);
+ animation.addAnimation(widthAnimation);
+ animation.addAnimation(heightAnimation);
+ mContentContainer.startAnimation(animation);
+ }
+
+ /**
+ * 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
+ */
+ private void closeOverflow() {
+ Preconditions.checkNotNull(mMainPanel);
+ Preconditions.checkNotNull(mOverflowPanel);
+
+ mOverflowPanel.fadeOut(true);
+ Size mainPanelSize = mMainPanel.measure();
+ final int targetWidth = mainPanelSize.getWidth();
+ final int targetHeight = mainPanelSize.getHeight();
+ final int startWidth = mContentContainer.getWidth();
+ final int startHeight = mContentContainer.getHeight();
+ final float right = mContentContainer.getX() + mContentContainer.getWidth();
+ final float bottom = mContentContainer.getY() + mContentContainer.getHeight();
+ final boolean morphedUpwards = (mOverflowDirection == OVERFLOW_DIRECTION_UP);
+ 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);
+ mContentContainer.setX(right - mContentContainer.getWidth());
+ }
+ };
+ 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) {
+ mContentContainer.setY(bottom - mContentContainer.getHeight());
+ }
+ }
+ };
+ widthAnimation.setDuration(150);
+ widthAnimation.setStartOffset(150);
+ heightAnimation.setDuration(210);
+ AnimationSet animation = new AnimationSet(true);
+ animation.setAnimationListener(mOnOverflowClosed);
+ animation.addAnimation(widthAnimation);
+ animation.addAnimation(heightAnimation);
+ mContentContainer.startAnimation(animation);
+ }
+
+ /**
+ * Prepares the content container for show and update calls.
+ */
+ private void preparePopupContent() {
+ // Do not call this method if main view panel has not been initialized.
+ Preconditions.checkNotNull(mMainPanel);
+
+ // If we're yet to show the popup, set the container visibility to zero.
+ // The "show" animation will make this visible.
+ if (!mPopupWindow.isShowing()) {
+ mContentContainer.setAlpha(0);
+ }
+
+ // Make sure panels are visible.
+ mMainPanel.fadeIn(false);
+ if (mOverflowPanel != null) {
+ mOverflowPanel.fadeIn(false);
+ }
+
+ // Make sure a panel is set as the content.
+ if (mContentContainer.getChildCount() == 0) {
+ mContentContainer.addView(mMainPanel.getView());
+ }
+
+ // Make sure the main panel is at the correct position.
+ if (mContentContainer.getChildAt(0) == mMainPanel.getView()) {
+ mContentContainer.setX(mPadding);
+ float y = mPadding;
+ if (mOverflowDirection == OVERFLOW_DIRECTION_UP) {
+ y = getHeight() - getEstimatedToolbarHeight(mParent.getContext()) - mPadding;
+ }
+ mContentContainer.setY(y);
+ }
+
+ setContentAreaAsTouchableSurface();
+ }
+
+ /**
+ * Sets the current content to be the main view panel.
+ */
+ private void setMainPanelAsContent() {
+ 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());
+ }
+
+ private void updatePopupSize() {
+ int width = 0;
+ int height = 0;
+ if (mMainPanel != null) {
+ Size mainPanelSize = mMainPanel.measure();
+ width = mainPanelSize.getWidth();
+ height = mainPanelSize.getHeight();
+ }
+ if (mOverflowPanel != null) {
+ Size overflowPanelSize = mOverflowPanel.measure();
+ width = Math.max(width, overflowPanelSize.getWidth());
+ height = Math.max(height, overflowPanelSize.getHeight());
+ }
+ mPopupWindow.setWidth(width + mPadding * 2);
+ mPopupWindow.setHeight(height + mPadding * 2);
+ }
+
+ /**
+ * Sets the touchable region of this popup to be zero. This means that all touch events on
+ * this popup will go through to the surface behind it.
+ */
+ private void setZeroTouchableSurface() {
+ mTouchableRegion.setEmpty();
+ }
+
+ /**
+ * 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);
+ }
+ int width = mContentContainer.getMeasuredWidth();
+ int height = mContentContainer.getMeasuredHeight();
+ mTouchableRegion.set(
+ (int) mContentContainer.getX(),
+ (int) mContentContainer.getY(),
+ (int) mContentContainer.getX() + width,
+ (int) mContentContainer.getY() + height);
+ }
+ }
+
+ /**
+ * 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);
+ }
+
/**
- * Sets the popup content container's pivot.
+ * Fits as many menu items in the main panel and returns a list of the menu items that
+ * were not fit in.
+ *
+ * @return The menu items that are not included in this main panel.
*/
- private void setPivot() {
- mContentContainer.setPivotX(mContentContainer.getMeasuredWidth() / 2);
- mContentContainer.setPivotY(mContentContainer.getMeasuredHeight());
+ public List<MenuItem> layoutMenuItems(List<MenuItem> menuItems, int suggestedWidth) {
+ final int toolbarWidth = getAdjustedToolbarWidth(mContext, suggestedWidth)
+ // Reserve space for the "open overflow" button.
+ - getEstimatedOpenOverflowButtonWidth(mContext);
+
+ int availableWidth = toolbarWidth;
+ final LinkedList<MenuItem> remainingMenuItems = new LinkedList<MenuItem>(menuItems);
+
+ mContentView.removeAllViews();
+
+ boolean isFirstItem = true;
+ while (!remainingMenuItems.isEmpty()) {
+ final MenuItem menuItem = remainingMenuItems.peek();
+ Button menuItemButton = createMenuItemButton(mContext, menuItem);
+
+ // Adding additional left padding for the first button to even out button spacing.
+ if (isFirstItem) {
+ menuItemButton.setPadding(
+ 2 * menuItemButton.getPaddingLeft(),
+ menuItemButton.getPaddingTop(),
+ menuItemButton.getPaddingRight(),
+ menuItemButton.getPaddingBottom());
+ isFirstItem = false;
+ }
+
+ // Adding additional right padding for the last button to even out button spacing.
+ if (remainingMenuItems.size() == 1) {
+ menuItemButton.setPadding(
+ menuItemButton.getPaddingLeft(),
+ menuItemButton.getPaddingTop(),
+ 2 * menuItemButton.getPaddingRight(),
+ menuItemButton.getPaddingBottom());
+ }
+
+ menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
+ if (menuItemButtonWidth <= availableWidth) {
+ menuItemButton.setTag(menuItem);
+ menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
+ mContentView.addView(menuItemButton);
+ ViewGroup.LayoutParams params = menuItemButton.getLayoutParams();
+ params.width = menuItemButtonWidth;
+ menuItemButton.setLayoutParams(params);
+ availableWidth -= menuItemButtonWidth;
+ remainingMenuItems.pop();
+ } else {
+ if (mOpenOverflowButton == null) {
+ mOpenOverflowButton = (ImageButton) 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);
+ break;
+ }
+ }
+ return remainingMenuItems;
+ }
+
+ public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
+ mOnMenuItemClickListener = listener;
}
- private static ViewGroup createContentContainer(Context context) {
- return (ViewGroup) LayoutInflater.from(context)
- .inflate(R.layout.floating_popup_container, null);
+ public View getView() {
+ return mContentView;
}
- private static PopupWindow createPopupWindow(View content) {
- ViewGroup popupContentHolder = new LinearLayout(content.getContext());
- PopupWindow popupWindow = new PopupWindow(popupContentHolder);
- popupWindow.setAnimationStyle(0);
- popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- popupWindow.setWidth(getScreenWidth(content.getContext()));
- popupWindow.setHeight(getScreenHeight(content.getContext()));
- content.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
- popupContentHolder.addView(content);
- return popupWindow;
+ public void fadeIn(boolean animate) {
+ viewFader.fadeIn(animate);
+ }
+
+ public void fadeOut(boolean animate) {
+ viewFader.fadeOut(animate);
}
/**
- * Creates a "grow and fade in from the bottom" animation for the specified view.
+ * 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.
+ */
+ public Size measure() throws IllegalStateException {
+ Preconditions.checkState(mContentView.getParent() == null);
+ mContentView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ return new Size(mContentView.getMeasuredWidth(), mContentView.getMeasuredHeight());
+ }
+ }
+
+
+ /**
+ * 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 ViewFader mViewFader;
+ private final Runnable mCloseOverflow;
+
+ private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
+
+ /**
+ * Initializes a floating toolbar popup overflow view panel.
*
- * @param view The view to animate
+ * @param context
+ * @param closeOverflow The code that closes the toolbar popup's overflow.
+ */
+ public FloatingToolbarOverflowPanel(Context context, Runnable closeOverflow) {
+ mCloseOverflow = Preconditions.checkNotNull(closeOverflow);
+
+ mContentView = new LinearLayout(context);
+ mContentView.setOrientation(LinearLayout.VERTICAL);
+ mViewFader = new ViewFader(mContentView);
+
+ mBackButton = LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_close_overflow_button, null);
+ mBackButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mCloseOverflow.run();
+ }
+ });
+ mBackButtonContainer = new LinearLayout(context);
+ mBackButtonContainer.addView(mBackButton);
+
+ mListView = createOverflowListView(context);
+ mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ MenuItem menuItem = (MenuItem) mListView.getAdapter().getItem(position);
+ if (mOnMenuItemClickListener != null) {
+ mOnMenuItemClickListener.onMenuItemClick(menuItem);
+ }
+ }
+ });
+
+ mContentView.addView(mListView);
+ mContentView.addView(mBackButtonContainer);
+ }
+
+ /**
+ * Sets the menu items to be displayed in the overflow.
*/
- private static AnimatorSet createGrowFadeInFromBottom(View view) {
- AnimatorSet growFadeInFromBottomAnimation = new AnimatorSet();
- growFadeInFromBottomAnimation.playTogether(
- ObjectAnimator.ofFloat(view, View.SCALE_X, 0.5f, 1).setDuration(125),
- ObjectAnimator.ofFloat(view, View.SCALE_Y, 0.5f, 1).setDuration(125),
- ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(75));
- return growFadeInFromBottomAnimation;
+ public void setMenuItems(List<MenuItem> menuItems) {
+ ArrayAdapter overflowListViewAdapter = (ArrayAdapter) mListView.getAdapter();
+ overflowListViewAdapter.clear();
+ overflowListViewAdapter.addAll(menuItems);
+ setListViewHeight();
+ }
+
+ public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
+ mOnMenuItemClickListener = listener;
}
/**
- * Creates a "shrink and fade out from bottom" animation for the specified view.
+ * Notifies the overflow of the current direction in which the overflow will be opened.
*
- * @param view The view to animate
- * @param listener The animation listener
+ * @param overflowDirection {@link FloatingToolbarPopup#OVERFLOW_DIRECTION_UP}
+ * or {@link FloatingToolbarPopup#OVERFLOW_DIRECTION_DOWN}.
*/
- private static AnimatorSet createShrinkFadeOutFromBottomAnimation(
- View view, Animator.AnimatorListener listener) {
- AnimatorSet shrinkFadeOutFromBottomAnimation = new AnimatorSet();
- shrinkFadeOutFromBottomAnimation.playTogether(
- ObjectAnimator.ofFloat(view, View.SCALE_Y, 1, 0.5f).setDuration(125),
- ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(75));
- shrinkFadeOutFromBottomAnimation.setStartDelay(150);
- shrinkFadeOutFromBottomAnimation.addListener(listener);
- return shrinkFadeOutFromBottomAnimation;
+ public void setOverflowDirection(int overflowDirection) {
+ mContentView.removeView(mBackButtonContainer);
+ int index = (overflowDirection == FloatingToolbarPopup.OVERFLOW_DIRECTION_UP)? 1 : 0;
+ mContentView.addView(mBackButtonContainer, index);
}
/**
- * Returns value, restricted to the range min->max (inclusive).
- * If maximum is less than minimum, the result is undefined.
+ * Returns the content view of the overflow.
+ */
+ public View getView() {
+ return mContentView;
+ }
+
+ public void fadeIn(boolean animate) {
+ mViewFader.fadeIn(animate);
+ }
+
+ public void fadeOut(boolean animate) {
+ mViewFader.fadeOut(animate);
+ }
+
+ /**
+ * 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.
*
- * @param value The value to clamp.
- * @param minimum The minimum value in the range.
- * @param maximum The maximum value in the range. Must not be less than minimum.
+ * @throws IllegalStateException
*/
- private static int clamp(int value, int minimum, int maximum) {
- return Math.max(minimum, Math.min(value, maximum));
+ public Size measure() {
+ Preconditions.checkState(mContentView.getParent() == null);
+ mContentView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ return new Size(mContentView.getMeasuredWidth(), mContentView.getMeasuredHeight());
+ }
+
+ private void setListViewHeight() {
+ int itemHeight = getEstimatedToolbarHeight(mContentView.getContext());
+ int height = mListView.getAdapter().getCount() * itemHeight;
+ int maxHeight = mContentView.getContext().getResources().
+ getDimensionPixelSize(R.dimen.floating_toolbar_minimum_overflow_height);
+ ViewGroup.LayoutParams params = mListView.getLayoutParams();
+ params.height = Math.min(height, maxHeight);
+ mListView.setLayoutParams(params);
}
+
+ private static ListView createOverflowListView(final Context context) {
+ 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 ArrayAdapter overflowListViewAdapter =
+ new ArrayAdapter<MenuItem>(context, 0) {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView menuButton;
+ if (convertView != null) {
+ menuButton = (TextView) convertView;
+ } else {
+ menuButton = createOverflowMenuItemButton(context);
+ }
+ MenuItem menuItem = getItem(position);
+ menuButton.setText(menuItem.getTitle());
+ menuButton.setContentDescription(menuItem.getTitle());
+ return menuButton;
+ }
+ };
+ overflowListView.setAdapter(overflowListViewAdapter);
+ return overflowListView;
+ }
+ }
+
+
+ /**
+ * A helper for fading in or out a view.
+ */
+ private static final class ViewFader {
+
+ private static final int FADE_OUT_DURATION = 250;
+ private static final int FADE_IN_DURATION = 150;
+
+ private final View mView;
+ private final ObjectAnimator mFadeOutAnimation;
+ private final ObjectAnimator mFadeInAnimation;
+
+ 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 void fadeIn(boolean animate) {
+ if (animate) {
+ mFadeInAnimation.start();
+ } else {
+ mView.setAlpha(1);
+ }
+ }
+
+ public void fadeOut(boolean animate) {
+ if (animate) {
+ mFadeOutAnimation.start();
+ } else {
+ mView.setAlpha(0);
+ }
+ }
+ }
+
+
+ /**
+ * Creates and returns a menu button for the specified menu item.
+ */
+ private static Button createMenuItemButton(Context context, MenuItem menuItem) {
+ Button menuItemButton = (Button) LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_menu_button, null);
+ menuItemButton.setText(menuItem.getTitle());
+ menuItemButton.setContentDescription(menuItem.getTitle());
+ 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) {
+ return (ViewGroup) LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_container, null);
+ }
+
+ private static PopupWindow createPopupWindow(View content) {
+ ViewGroup popupContentHolder = new LinearLayout(content.getContext());
+ PopupWindow popupWindow = new PopupWindow(popupContentHolder);
+ popupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
+ popupWindow.setAnimationStyle(0);
+ popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ content.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ popupContentHolder.addView(content);
+ return popupWindow;
+ }
+
+ /**
+ * Creates a "grow and fade in from the bottom" animation for the specified view.
+ *
+ * @param view The view to animate
+ */
+ private static AnimatorSet createGrowFadeInFromBottom(View view) {
+ AnimatorSet growFadeInFromBottomAnimation = new AnimatorSet();
+ growFadeInFromBottomAnimation.playTogether(
+ ObjectAnimator.ofFloat(view, View.SCALE_X, 0.5f, 1).setDuration(125),
+ ObjectAnimator.ofFloat(view, View.SCALE_Y, 0.5f, 1).setDuration(125),
+ ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(75));
+ growFadeInFromBottomAnimation.setStartDelay(50);
+ return growFadeInFromBottomAnimation;
+ }
+
+ /**
+ * Creates a "shrink and fade out from bottom" animation for the specified view.
+ *
+ * @param view The view to animate
+ * @param listener The animation listener
+ */
+ private static AnimatorSet createShrinkFadeOutFromBottomAnimation(
+ View view, Animator.AnimatorListener listener) {
+ AnimatorSet shrinkFadeOutFromBottomAnimation = new AnimatorSet();
+ shrinkFadeOutFromBottomAnimation.playTogether(
+ ObjectAnimator.ofFloat(view, View.SCALE_Y, 1, 0.5f).setDuration(125),
+ ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(75));
+ shrinkFadeOutFromBottomAnimation.setStartDelay(150);
+ shrinkFadeOutFromBottomAnimation.addListener(listener);
+ return shrinkFadeOutFromBottomAnimation;
+ }
+
+ private static int getOverflowWidth(Context context) {
+ return context.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_overflow_width);
+ }
+
+ 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);
+ }
+
+ private static int getAdjustedToolbarWidth(Context context, int width) {
+ if (width <= 0 || width > getScreenWidth(context)) {
+ width = context.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_default_width);
+ }
+ return width;
+ }
+
+ /**
+ * Returns the device's screen width.
+ */
+ private static int getScreenWidth(Context context) {
+ return context.getResources().getDisplayMetrics().widthPixels;
+ }
+
+ /**
+ * Returns the device's screen height.
+ */
+ private static int getScreenHeight(Context context) {
+ return context.getResources().getDisplayMetrics().heightPixels;
+ }
+
+ /**
+ * Returns value, restricted to the range min->max (inclusive).
+ * If maximum is less than minimum, the result is undefined.
+ *
+ * @param value The value to clamp.
+ * @param minimum The minimum value in the range.
+ * @param maximum The maximum value in the range. Must not be less than minimum.
+ */
+ private static int clamp(int value, int minimum, int maximum) {
+ return Math.max(minimum, Math.min(value, maximum));
}
}
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 526885f..4c4a39d 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -543,6 +543,11 @@ static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) {
return ::lseek64(descriptor, 0, SEEK_CUR) != -1 ? JNI_TRUE : JNI_FALSE;
}
+jobject decodeBitmap(JNIEnv* env, void* data, size_t size) {
+ SkMemoryStream stream(data, size);
+ return doDecode(env, &stream, NULL, NULL);
+}
+
///////////////////////////////////////////////////////////////////////////////
static JNINativeMethod gMethods[] = {
diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h
index a54da43..22a955f 100644
--- a/core/jni/android/graphics/BitmapFactory.h
+++ b/core/jni/android/graphics/BitmapFactory.h
@@ -21,4 +21,6 @@ extern jfieldID gOptions_bitmapFieldID;
jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format);
+jobject decodeBitmap(JNIEnv* env, void* data, size_t size);
+
#endif // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_
diff --git a/core/jni/android_emoji_EmojiFactory.cpp b/core/jni/android_emoji_EmojiFactory.cpp
index 655b400..e9f18a6 100644
--- a/core/jni/android_emoji_EmojiFactory.cpp
+++ b/core/jni/android_emoji_EmojiFactory.cpp
@@ -5,6 +5,7 @@
#include <utils/Log.h>
#include <ScopedUtfChars.h>
+#include "BitmapFactory.h"
#include "EmojiFactory.h"
#include "GraphicsJNI.h"
#include <nativehelper/JNIHelp.h>
@@ -164,14 +165,7 @@ static jobject android_emoji_EmojiFactory_getBitmapFromAndroidPua(
return NULL;
}
- SkBitmap *bitmap = new SkBitmap;
- if (!SkImageDecoder::DecodeMemory(bytes, size, bitmap)) {
- ALOGE("SkImageDecoder::DecodeMemory() failed.");
- return NULL;
- }
-
- return GraphicsJNI::createBitmap(env, bitmap,
- GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL);
+ return decodeBitmap(env, (void*)bytes, size);
}
static void android_emoji_EmojiFactory_destructor(
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index fb91c8f..9cf6a9d 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -48,11 +48,6 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
return NULL;
}
- ScopedLocalRef<jstring> uniqueIdObj(env, env->NewStringUTF(deviceInfo.getIdentifier().uniqueId));
- if (!uniqueIdObj.get()) {
- return NULL;
- }
-
ScopedLocalRef<jobject> kcmObj(env,
android_view_KeyCharacterMap_create(env, deviceInfo.getId(),
deviceInfo.getKeyCharacterMap()));
@@ -62,13 +57,16 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
const InputDeviceIdentifier& ident = deviceInfo.getIdentifier();
+ // Not sure why, but JNI is complaining when I pass this through directly.
+ jboolean hasMic = deviceInfo.hasMic() ? JNI_TRUE : JNI_FALSE;
+
ScopedLocalRef<jobject> inputDeviceObj(env, env->NewObject(gInputDeviceClassInfo.clazz,
gInputDeviceClassInfo.ctor, deviceInfo.getId(), deviceInfo.getGeneration(),
deviceInfo.getControllerNumber(), nameObj.get(),
static_cast<int32_t>(ident.vendor), static_cast<int32_t>(ident.product),
- uniqueIdObj.get(), descriptorObj.get(), deviceInfo.isExternal(),
- deviceInfo.getSources(), deviceInfo.getKeyboardType(), kcmObj.get(),
- deviceInfo.hasVibrator(), deviceInfo.hasButtonUnderPad()));
+ descriptorObj.get(), deviceInfo.isExternal(), deviceInfo.getSources(),
+ deviceInfo.getKeyboardType(), kcmObj.get(), deviceInfo.hasVibrator(),
+ hasMic, deviceInfo.hasButtonUnderPad()));
const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
for (size_t i = 0; i < ranges.size(); i++) {
@@ -90,7 +88,7 @@ int register_android_view_InputDevice(JNIEnv* env)
gInputDeviceClassInfo.clazz = MakeGlobalRefOrDie(env, gInputDeviceClassInfo.clazz);
gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
- "(IIILjava/lang/String;IILjava/lang/String;Ljava/lang/String;ZIILandroid/view/KeyCharacterMap;ZZ)V");
+ "(IIILjava/lang/String;IILjava/lang/String;ZIILandroid/view/KeyCharacterMap;ZZZ)V");
gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz,
"addMotionRange", "(IIFFFFF)V");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f427f2b..453cb74 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -56,6 +56,8 @@
<protected-broadcast android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<protected-broadcast android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
<protected-broadcast android:name="android.intent.action.ACTION_SHUTDOWN" />
+ <protected-broadcast android:name="android.intent.action.CHARGING" />
+ <protected-broadcast android:name="android.intent.action.DISCHARGING" />
<protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_LOW" />
<protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_OK" />
<protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_FULL" />
@@ -307,6 +309,8 @@
<protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
+ <protected-broadcast android:name="android.service.persistentdata.action.WIPE_IF_ALLOWED" />
+
<!-- ====================================== -->
<!-- Permissions for things that cost money -->
<!-- ====================================== -->
@@ -3017,6 +3021,12 @@
android:description="@string/permdesc_accessVoiceInteractionService"
android:label="@string/permlab_accessVoiceInteractionService" />
+ <!-- Allows an app that has this permission and a permissions to install packages
+ to request all runtime permissions to be granted at installation.
+ @hide -->
+ <permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS"
+ android:protectionLevel="signature" />
+
<!-- The system process is explicitly the only one allowed to launch the
confirmation UI for full backup/restore -->
<uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
diff --git a/core/res/res/color/ratingbar_background_material.xml b/core/res/res/color/ratingbar_background_material.xml
new file mode 100644
index 0000000..e6f7488
--- /dev/null
+++ b/core/res/res/color/ratingbar_background_material.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:state_pressed="true"
+ android:color="?attr/colorControlActivated"
+ android:alpha="?attr/disabledAlpha" />
+ <item
+ android:color="?attr/colorControlNormal"
+ android:alpha="?attr/disabledAlpha" />
+</selector>
diff --git a/core/res/res/drawable-hdpi/ic_star_black_16dp.png b/core/res/res/drawable-hdpi/ic_star_black_16dp.png
new file mode 100644
index 0000000..a728afe
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_star_black_16dp.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_star_black_36dp.png b/core/res/res/drawable-hdpi/ic_star_black_36dp.png
new file mode 100644
index 0000000..4f67f97
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_star_black_36dp.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_star_black_48dp.png b/core/res/res/drawable-hdpi/ic_star_black_48dp.png
new file mode 100644
index 0000000..54d3065
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_star_black_48dp.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_star_half_black_16dp.png b/core/res/res/drawable-hdpi/ic_star_half_black_16dp.png
new file mode 100644
index 0000000..89919a3
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_star_half_black_16dp.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_star_half_black_36dp.png b/core/res/res/drawable-hdpi/ic_star_half_black_36dp.png
new file mode 100644
index 0000000..d28afab
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_star_half_black_36dp.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_star_half_black_48dp.png b/core/res/res/drawable-hdpi/ic_star_half_black_48dp.png
new file mode 100644
index 0000000..befe521
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_star_half_black_48dp.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_star_black_16dp.png b/core/res/res/drawable-mdpi/ic_star_black_16dp.png
new file mode 100644
index 0000000..3f5d25e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_star_black_16dp.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_star_black_36dp.png b/core/res/res/drawable-mdpi/ic_star_black_36dp.png
new file mode 100644
index 0000000..92a0f58
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_star_black_36dp.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_star_black_48dp.png b/core/res/res/drawable-mdpi/ic_star_black_48dp.png
new file mode 100644
index 0000000..c636ce8
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_star_black_48dp.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_star_half_black_16dp.png b/core/res/res/drawable-mdpi/ic_star_half_black_16dp.png
new file mode 100644
index 0000000..beea92a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_star_half_black_16dp.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_star_half_black_36dp.png b/core/res/res/drawable-mdpi/ic_star_half_black_36dp.png
new file mode 100644
index 0000000..5caae60
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_star_half_black_36dp.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_star_half_black_48dp.png b/core/res/res/drawable-mdpi/ic_star_half_black_48dp.png
new file mode 100644
index 0000000..d53afa22
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_star_half_black_48dp.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_star_black_16dp.png b/core/res/res/drawable-xhdpi/ic_star_black_16dp.png
new file mode 100644
index 0000000..732c48e
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_star_black_16dp.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_star_black_36dp.png b/core/res/res/drawable-xhdpi/ic_star_black_36dp.png
new file mode 100644
index 0000000..54d3065
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_star_black_36dp.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_star_black_48dp.png b/core/res/res/drawable-xhdpi/ic_star_black_48dp.png
new file mode 100644
index 0000000..7be2280
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_star_black_48dp.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_star_half_black_16dp.png b/core/res/res/drawable-xhdpi/ic_star_half_black_16dp.png
new file mode 100644
index 0000000..5d6f3c8
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_star_half_black_16dp.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_star_half_black_36dp.png b/core/res/res/drawable-xhdpi/ic_star_half_black_36dp.png
new file mode 100644
index 0000000..2ed3a20
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_star_half_black_36dp.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_star_half_black_48dp.png b/core/res/res/drawable-xhdpi/ic_star_half_black_48dp.png
new file mode 100644
index 0000000..348d4d8
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_star_half_black_48dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_star_black_16dp.png b/core/res/res/drawable-xxhdpi/ic_star_black_16dp.png
new file mode 100644
index 0000000..c636ce8
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_star_black_16dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_star_black_36dp.png b/core/res/res/drawable-xxhdpi/ic_star_black_36dp.png
new file mode 100644
index 0000000..52d03f1
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_star_black_36dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_star_black_48dp.png b/core/res/res/drawable-xxhdpi/ic_star_black_48dp.png
new file mode 100644
index 0000000..918a395
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_star_black_48dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_star_half_black_16dp.png b/core/res/res/drawable-xxhdpi/ic_star_half_black_16dp.png
new file mode 100644
index 0000000..9b268d1
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_star_half_black_16dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_star_half_black_36dp.png b/core/res/res/drawable-xxhdpi/ic_star_half_black_36dp.png
new file mode 100644
index 0000000..167d8ae
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_star_half_black_36dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_star_half_black_48dp.png b/core/res/res/drawable-xxhdpi/ic_star_half_black_48dp.png
new file mode 100644
index 0000000..64e76bb
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_star_half_black_48dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/ic_star_black_16dp.png b/core/res/res/drawable-xxxhdpi/ic_star_black_16dp.png
new file mode 100644
index 0000000..1fa274d
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/ic_star_black_16dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/ic_star_black_36dp.png b/core/res/res/drawable-xxxhdpi/ic_star_black_36dp.png
new file mode 100644
index 0000000..918a395
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/ic_star_black_36dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/ic_star_black_48dp.png b/core/res/res/drawable-xxxhdpi/ic_star_black_48dp.png
new file mode 100644
index 0000000..67e25d5
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/ic_star_black_48dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/ic_star_half_black_16dp.png b/core/res/res/drawable-xxxhdpi/ic_star_half_black_16dp.png
new file mode 100644
index 0000000..266c167
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/ic_star_half_black_16dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/ic_star_half_black_36dp.png b/core/res/res/drawable-xxxhdpi/ic_star_half_black_36dp.png
new file mode 100644
index 0000000..debdb77
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/ic_star_half_black_36dp.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/ic_star_half_black_48dp.png b/core/res/res/drawable-xxxhdpi/ic_star_half_black_48dp.png
new file mode 100644
index 0000000..bfb6e61
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/ic_star_half_black_48dp.png
Binary files differ
diff --git a/core/res/res/drawable/fastscroll_label_left_material.xml b/core/res/res/drawable/fastscroll_label_left_material.xml
index 430d1b0..c825f73 100644
--- a/core/res/res/drawable/fastscroll_label_left_material.xml
+++ b/core/res/res/drawable/fastscroll_label_left_material.xml
@@ -14,14 +14,18 @@
limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners
- android:topLeftRadius="44dp"
- android:topRightRadius="44dp"
- android:bottomRightRadius="44dp" />
- <padding
- android:paddingLeft="22dp"
- android:paddingRight="22dp" />
- <solid android:color="?attr/colorControlActivated" />
-</shape>
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="16dp">
+ <shape
+ android:shape="rectangle"
+ android:tint="?attr/colorControlActivated">
+ <corners
+ android:topLeftRadius="44dp"
+ android:topRightRadius="44dp"
+ android:bottomRightRadius="44dp" />
+ <padding
+ android:left="22dp"
+ android:right="22dp" />
+ <solid android:color="@color/white" />
+ </shape>
+</inset>
diff --git a/core/res/res/drawable/fastscroll_label_right_material.xml b/core/res/res/drawable/fastscroll_label_right_material.xml
index 6e61397..94f5fde 100644
--- a/core/res/res/drawable/fastscroll_label_right_material.xml
+++ b/core/res/res/drawable/fastscroll_label_right_material.xml
@@ -14,14 +14,18 @@
limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners
- android:topLeftRadius="44dp"
- android:topRightRadius="44dp"
- android:bottomLeftRadius="44dp" />
- <padding
- android:paddingLeft="22dp"
- android:paddingRight="22dp" />
- <solid android:color="?attr/colorControlActivated" />
-</shape>
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetRight="16dp">
+ <shape
+ android:shape="rectangle"
+ android:tint="?attr/colorControlActivated">
+ <corners
+ android:topLeftRadius="44dp"
+ android:topRightRadius="44dp"
+ android:bottomLeftRadius="44dp" />
+ <padding
+ android:left="22dp"
+ android:right="22dp" />
+ <solid android:color="@color/white" />
+ </shape>
+</inset>
diff --git a/core/res/res/drawable/ratingbar_full_empty_material.xml b/core/res/res/drawable/ratingbar_full_empty_material.xml
index a2ae7d9..12b01ba 100644
--- a/core/res/res/drawable/ratingbar_full_empty_material.xml
+++ b/core/res/res/drawable/ratingbar_full_empty_material.xml
@@ -16,11 +16,14 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
- <bitmap android:src="@drawable/btn_rating_star_off_mtrl_alpha"
- android:tint="?attr/colorControlActivated" />
+ <bitmap
+ android:src="@drawable/btn_rating_star_off_mtrl_alpha"
+ android:tint="?attr/colorControlActivated"
+ android:alpha="?attr/disabledAlpha"/>
</item>
<item>
- <bitmap android:src="@drawable/btn_rating_star_off_mtrl_alpha"
+ <bitmap
+ android:src="@drawable/btn_rating_star_off_mtrl_alpha"
android:tint="?attr/colorControlNormal" />
</item>
</selector>
diff --git a/core/res/res/drawable/ratingbar_full_filled_material.xml b/core/res/res/drawable/ratingbar_full_filled_material.xml
index 801c85f..f167dae 100644
--- a/core/res/res/drawable/ratingbar_full_filled_material.xml
+++ b/core/res/res/drawable/ratingbar_full_filled_material.xml
@@ -16,11 +16,13 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
- <bitmap android:src="@drawable/btn_rating_star_on_mtrl_alpha"
+ <bitmap
+ android:src="@drawable/btn_rating_star_on_mtrl_alpha"
android:tint="?attr/colorControlActivated" />
</item>
<item>
- <bitmap android:src="@drawable/btn_rating_star_on_mtrl_alpha"
+ <bitmap
+ android:src="@drawable/btn_rating_star_on_mtrl_alpha"
android:tint="?attr/colorControlNormal" />
</item>
</selector>
diff --git a/core/res/res/drawable/ratingbar_full_material.xml b/core/res/res/drawable/ratingbar_full_half_material.xml
index 122dd1d..8b78b43 100644
--- a/core/res/res/drawable/ratingbar_full_material.xml
+++ b/core/res/res/drawable/ratingbar_full_half_material.xml
@@ -14,11 +14,15 @@
limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@id/background"
- android:drawable="@drawable/ratingbar_full_empty_material" />
- <item android:id="@id/secondaryProgress"
- android:drawable="@drawable/ratingbar_full_empty_material" />
- <item android:id="@id/progress"
- android:drawable="@drawable/ratingbar_full_filled_material" />
-</layer-list>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true">
+ <bitmap
+ android:src="@drawable/btn_rating_star_off_mtrl_alpha"
+ android:tint="?attr/colorControlActivated" />
+ </item>
+ <item>
+ <bitmap
+ android:src="@drawable/btn_rating_star_off_mtrl_alpha"
+ android:tint="?attr/colorControlNormal" />
+ </item>
+</selector>
diff --git a/core/res/res/drawable/ratingbar_indicator_material.xml b/core/res/res/drawable/ratingbar_indicator_material.xml
new file mode 100644
index 0000000..d8c6ee1
--- /dev/null
+++ b/core/res/res/drawable/ratingbar_indicator_material.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/background">
+ <bitmap
+ android:src="@drawable/ic_star_black_36dp"
+ android:tint="?attr/colorControlNormal"
+ android:alpha="?attr/disabledAlpha" />
+ </item>
+ <item android:id="@+id/secondaryProgress">
+ <bitmap
+ android:src="@drawable/ic_star_half_black_36dp"
+ android:tint="?attr/colorControlActivated" />
+ </item>
+ <item android:id="@+id/progress">
+ <bitmap
+ android:src="@drawable/ic_star_black_36dp"
+ android:tint="?attr/colorControlActivated"
+ android:tileModeX="repeat" />
+ </item>
+</layer-list>
diff --git a/core/res/res/drawable/ratingbar_material.xml b/core/res/res/drawable/ratingbar_material.xml
new file mode 100644
index 0000000..0cd7fac
--- /dev/null
+++ b/core/res/res/drawable/ratingbar_material.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/background">
+ <bitmap
+ android:src="@drawable/ic_star_black_48dp"
+ android:tint="@color/ratingbar_background_material" />
+ </item>
+ <item android:id="@+id/secondaryProgress">
+ <bitmap
+ android:src="@drawable/ic_star_half_black_48dp"
+ android:tint="?attr/colorControlActivated" />
+ </item>
+ <item android:id="@+id/progress">
+ <bitmap
+ android:src="@drawable/ic_star_black_48dp"
+ android:tint="?attr/colorControlActivated" />
+ </item>
+</layer-list>
diff --git a/core/res/res/drawable/ratingbar_small_material.xml b/core/res/res/drawable/ratingbar_small_material.xml
new file mode 100644
index 0000000..f24241c
--- /dev/null
+++ b/core/res/res/drawable/ratingbar_small_material.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/background">
+ <bitmap
+ android:src="@drawable/ic_star_black_16dp"
+ android:tint="?attr/colorControlNormal"
+ android:alpha="?attr/disabledAlpha" />
+ </item>
+ <item android:id="@+id/secondaryProgress">
+ <bitmap
+ android:src="@drawable/ic_star_half_black_16dp"
+ android:tint="?attr/colorControlActivated" />
+ </item>
+ <item android:id="@+id/progress">
+ <bitmap
+ android:src="@drawable/ic_star_black_16dp"
+ android:tint="?attr/colorControlActivated"
+ android:tileModeX="repeat" />
+ </item>
+</layer-list>
diff --git a/core/res/res/layout/floating_popup_close_overflow_button.xml b/core/res/res/layout/floating_popup_close_overflow_button.xml
new file mode 100644
index 0000000..a1d2811
--- /dev/null
+++ b/core/res/res/layout/floating_popup_close_overflow_button.xml
@@ -0,0 +1,24 @@
+<!--
+/* 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:layout_width="@dimen/floating_toolbar_menu_button_minimum_width"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/floating_toolbar_menu_button_minimum_width"
+ android:minHeight="@dimen/floating_toolbar_height"
+ android:src="?android:attr/actionModeCloseDrawable"
+ android:contentDescription="@string/floating_toolbar_close_overflow_description"
+ android:background="?attr/selectableItemBackgroundBorderless" />
diff --git a/core/res/res/layout/floating_popup_open_overflow_button.xml b/core/res/res/layout/floating_popup_open_overflow_button.xml
index 4c1176c..dca5384 100644
--- a/core/res/res/layout/floating_popup_open_overflow_button.xml
+++ b/core/res/res/layout/floating_popup_open_overflow_button.xml
@@ -21,5 +21,5 @@
android:minWidth="@dimen/floating_toolbar_menu_button_minimum_width"
android:minHeight="@dimen/floating_toolbar_height"
android:src="@drawable/ic_menu_moreoverflow_material"
- android:contentDescription="@string/action_menu_overflow_description"
+ android:contentDescription="@string/floating_toolbar_open_overflow_description"
android:background="?attr/selectableItemBackgroundBorderless" />
diff --git a/core/res/res/layout/floating_popup_overflow_list_item b/core/res/res/layout/floating_popup_overflow_list_item
new file mode 100644
index 0000000..9294f3b
--- /dev/null
+++ b/core/res/res/layout/floating_popup_overflow_list_item
@@ -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.
+*/
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceListItemSmall"
+ android:gravity="center_vertical"
+ android:minWidth="@dimen/floating_toolbar_menu_button_side_padding"
+ android:minHeight="@dimen/floating_toolbar_height"
+ android:paddingLeft="@dimen/floating_toolbar_menu_button_side_padding"
+ android:paddingRight="@dimen/floating_toolbar_menu_button_side_padding"
+ android:paddingTop="0dp"
+ android:paddingBottom="0dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:fontFamily="sans-serif"
+ android:textSize="@dimen/floating_toolbar_text_size"
+ android:textAllCaps="true" />
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 3312f4f..f6df01f 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -348,9 +348,9 @@
<item>@drawable/progress_large_material</item>
<item>@drawable/progress_medium_material</item>
<item>@drawable/progress_small_material</item>
- <item>@drawable/ratingbar_full_empty_material</item>
- <item>@drawable/ratingbar_full_filled_material</item>
- <item>@drawable/ratingbar_full_material</item>
+ <item>@drawable/ratingbar_material</item>
+ <item>@drawable/ratingbar_small_material</item>
+ <item>@drawable/ratingbar_indicator_material</item>
<item>@drawable/scrollbar_handle_material</item>
<item>@drawable/scrubber_control_material_anim</item>
<item>@drawable/scrubber_control_selector_material</item>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index eb37619..cbd74cd 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3414,9 +3414,9 @@
<attr name="position">
<!-- Floating at the top of the content. -->
<enum name="floating" value="0" />
- <!-- Pinned alongside the thumb. -->
+ <!-- Pinned to the thumb, vertically centered with the middle of the thumb. -->
<enum name="atThumb" value="1" />
- <!-- Pinned above the thumb. -->
+ <!-- Pinned to the thumb, vertically centered with the top edge of the thumb. -->
<enum name="aboveThumb" value="2" />
</attr>
<attr name="textAppearance" />
@@ -3428,6 +3428,16 @@
<attr name="minHeight" />
<!-- Padding for the section header preview. -->
<attr name="padding" />
+ <!-- Position of thumb in relation to the track. -->
+ <attr name="thumbPosition">
+ <!-- The thumb's midpoint is anchored to the track. At its
+ extremes, the thumb will extend half-way outside the
+ track. -->
+ <enum name="midpoint" value="0" />
+ <!-- The thumb is entirely inside the track. At its extremes,
+ the thumb will be contained entirely within the track. -->
+ <enum name="inside" value="1" />
+ </attr>
</declare-styleable>
<declare-styleable name="FrameLayout">
<!-- Determines whether to measure all children or just those in
@@ -5971,6 +5981,19 @@
<!-- values are colors, which are integers starting with "#". -->
<enum name="colorType" value="3" />
</attr>
+ <!-- Defines whether the animation should adjust duration in order to achieve the same
+ perceived effects on different devices. -->
+ <attr name="durationScaleHint" >
+ <!-- Default value for scale hint. When set, duration will not be scaled.-->
+ <enum name="noScale" value="0"/>
+ <!-- This should be used when the animation's moving distance is proportional to screen,
+ as the scaling is based on screen size. -->
+ <enum name="screen" value="1"/>
+ <!-- This is for animations that have a distance defined in dp, which will be the same
+ across different devices. In this case, scaling is based on the physical distance
+ per dp on the current device. -->
+ <enum name="dp" value="2"/>
+ </attr>
</declare-styleable>
<declare-styleable name="PropertyValuesHolder">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e879244..6d9bbae 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2044,15 +2044,11 @@
<!-- Enabled built-in zen mode condition providers -->
<string-array translatable="false" name="config_system_condition_providers">
<item>countdown</item>
- <item>downtime</item>
- <item>next_alarm</item>
+ <item>schedule</item>
</string-array>
- <!-- Show the next-alarm as a zen exit condition if it occurs in the next n hours. -->
- <integer name="config_next_alarm_condition_lookahead_threshold_hrs">12</integer>
-
- <!-- Show downtime as a zen exit condition if it starts in the next n hours. -->
- <integer name="config_downtime_condition_lookahead_threshold_hrs">4</integer>
+ <!-- Priority repeat caller threshold, in minutes -->
+ <integer name="config_zen_repeat_callers_threshold">15</integer>
<!-- Flags enabling default window features. See Window.java -->
<bool name="config_defaultWindowFeatureOptionsPanel">true</bool>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 7d08e7f..2654a25 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -379,11 +379,11 @@
<dimen name="text_handle_min_size">40dp</dimen>
<!-- Lighting and shadow properties -->
- <dimen name="light_y">-200dp</dimen>
- <dimen name="light_z">800dp</dimen>
- <dimen name="light_radius">600dp</dimen>
- <item type="dimen" format="float" name="ambient_shadow_alpha">0.075</item>
- <item type="dimen" format="float" name="spot_shadow_alpha">0.15</item>
+ <dimen name="light_y">0dp</dimen>
+ <dimen name="light_z">600dp</dimen>
+ <dimen name="light_radius">800dp</dimen>
+ <item type="dimen" format="float" name="ambient_shadow_alpha">0.039</item>
+ <item type="dimen" format="float" name="spot_shadow_alpha">0.19</item>
<!-- Floating toolbar dimensions -->
<dimen name="floating_toolbar_height">48dp</dimen>
@@ -392,4 +392,6 @@
<dimen name="floating_toolbar_menu_button_minimum_width">48dp</dimen>
<dimen name="floating_toolbar_default_width">250dp</dimen>
<dimen name="floating_toolbar_minimum_overflow_height">192dp</dimen>
+ <dimen name="floating_toolbar_overflow_width">130dp</dimen>
+ <dimen name="floating_toolbar_margin">2dp</dimen>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 24d17a4..c2f2c6d 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2659,4 +2659,8 @@
<public type="attr" name="breakStrategy" />
<public type="attr" name="supportsAssistGesture" />
+ <public type="attr" name="thumbPosition" />
+
+ <!-- Animation -->
+ <public type="attr" name="durationScaleHint" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7dc3ff7..578aa45 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5170,12 +5170,6 @@
<!-- [CHAR_LIMIT=NONE] Battery saver: Feature description -->
<string name="battery_saver_description">To help improve battery life, battery saver reduces your device’s performance and limits vibration, location services, and most background data. Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery saver turns off automatically when your device is charging.</string>
- <!-- [CHAR_LIMIT=NONE] Zen mode: Condition summary for built-in downtime condition, if active -->
- <string name="downtime_condition_summary">Until your downtime ends at <xliff:g id="formattedTime" example="10:00 PM">%1$s</xliff:g></string>
-
- <!-- [CHAR_LIMIT=NONE] Zen mode: Condition line one for built-in downtime condition, if active -->
- <string name="downtime_condition_line_one">Until your downtime ends</string>
-
<!-- Zen mode condition - summary: time duration in minutes. [CHAR LIMIT=NONE] -->
<plurals name="zen_mode_duration_minutes_summary">
<item quantity="one">For one minute (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
@@ -5206,14 +5200,23 @@
<!-- Zen mode condition: no exit criteria. [CHAR LIMIT=NONE] -->
<string name="zen_mode_forever">Until you turn this off</string>
+ <!-- Zen mode active automatic rule name separator. [CHAR LIMIT=NONE] -->
+ <string name="zen_mode_rule_name_combination"><xliff:g id="first" example="Weeknights">%1$s</xliff:g> / <xliff:g id="rest" example="Meetings">%2$s</xliff:g></string>
+
<!-- Content description for the Toolbar icon used to collapse an expanded action mode. [CHAR LIMIT=NONE] -->
<string name="toolbar_collapse_description">Collapse</string>
- <!-- Zen mode condition - summary: until next alarm. [CHAR LIMIT=NONE] -->
- <string name="zen_mode_next_alarm_summary">Until next alarm at <xliff:g id="formattedTime" example="7:30 AM">%1$s</xliff:g></string>
+ <!-- Zen mode - feature name. [CHAR LIMIT=40] -->
+ <string name="zen_mode_feature_name">Block interruptions</string>
+
+ <!-- Zen mode - downtime legacy feature name. [CHAR LIMIT=40] -->
+ <string name="zen_mode_downtime_feature_name">Downtime</string>
+
+ <!-- Zen mode - name of default automatic schedule for weeknights. [CHAR LIMIT=40] -->
+ <string name="zen_mode_default_weeknights_name">Weeknights</string>
- <!-- Zen mode condition - line one: until next alarm. [CHAR LIMIT=NONE] -->
- <string name="zen_mode_next_alarm_line_one">Until next alarm</string>
+ <!-- Zen mode - name of default automatic schedule for weekends. [CHAR LIMIT=40] -->
+ <string name="zen_mode_default_weekends_name">Weekends</string>
<!-- Indication that the current volume and other effects (vibration) are being suppressed by a third party, such as a notification listener. [CHAR LIMIT=30] -->
<string name="muted_by">Muted by <xliff:g id="third_party">%1$s</xliff:g></string>
@@ -5232,9 +5235,17 @@
<string name="stk_cc_ss_to_ussd">SS request is modified to USSD request.</string>
<string name="stk_cc_ss_to_ss">SS request is modified to new SS request.</string>
+ <!-- User visible name for USB MIDI Peripheral port -->
+ <string name="usb_midi_peripheral_name">Android USB Peripheral Port</string>
<!-- Manufacturer name for USB MIDI Peripheral port -->
<string name="usb_midi_peripheral_manufacturer_name">Android</string>
- <!-- Model name for USB MIDI Peripheral port -->
- <string name="usb_midi_peripheral_model_name">USB Peripheral Port</string>
+ <!-- Product name for USB MIDI Peripheral port -->
+ <string name="usb_midi_peripheral_product_name">USB Peripheral Port</string>
+
+ <!-- Floating toolbar strings -->
+ <!-- Content description for the button that opens the floating toolbar overflow. [CHAR LIMIT=NONE] -->
+ <string name="floating_toolbar_open_overflow_description">More options</string>
+ <!-- Content description for the button that closes the floating toolbar overflow. [CHAR LIMIT=NONE] -->
+ <string name="floating_toolbar_close_overflow_description">Close overflow</string>
</resources>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 9cf7884..88cac72 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -726,22 +726,22 @@ please see styles_device_defaults.xml.
</style>
<style name="Widget.Material.RatingBar" parent="Widget.RatingBar">
- <item name="progressDrawable">@drawable/ratingbar_full_material</item>
- <item name="indeterminateDrawable">@drawable/ratingbar_full_material</item>
+ <item name="progressDrawable">@drawable/ratingbar_material</item>
+ <item name="indeterminateDrawable">@drawable/ratingbar_material</item>
</style>
<style name="Widget.Material.RatingBar.Indicator" parent="Widget.RatingBar.Indicator">
- <item name="progressDrawable">@drawable/ratingbar_holo_dark</item>
- <item name="indeterminateDrawable">@drawable/ratingbar_holo_dark</item>
- <item name="minHeight">35dip</item>
- <item name="maxHeight">35dip</item>
+ <item name="progressDrawable">@drawable/ratingbar_indicator_material</item>
+ <item name="indeterminateDrawable">@drawable/ratingbar_indicator_material</item>
+ <item name="minHeight">36dp</item>
+ <item name="maxHeight">36dp</item>
</style>
<style name="Widget.Material.RatingBar.Small" parent="Widget.RatingBar.Small">
- <item name="progressDrawable">@drawable/ratingbar_small_holo_dark</item>
- <item name="indeterminateDrawable">@drawable/ratingbar_small_holo_dark</item>
- <item name="minHeight">16dip</item>
- <item name="maxHeight">16dip</item>
+ <item name="progressDrawable">@drawable/ratingbar_small_material</item>
+ <item name="indeterminateDrawable">@drawable/ratingbar_small_material</item>
+ <item name="minHeight">16dp</item>
+ <item name="maxHeight">16dp</item>
</style>
<style name="Widget.Material.ScrollView" parent="Widget.ScrollView"/>
@@ -943,9 +943,10 @@ please see styles_device_defaults.xml.
<item name="thumbMinWidth">0dp</item>
<item name="thumbMinHeight">0dp</item>
<item name="textSize">45sp</item>
- <item name="minWidth">88dp</item>
+ <item name="minWidth">104dp</item>
<item name="minHeight">88dp</item>
<item name="padding">0dp</item>
+ <item name="thumbPosition">inside</item>
</style>
<style name="Widget.Material.PreferenceFrameLayout">
@@ -1038,22 +1039,9 @@ please see styles_device_defaults.xml.
<style name="Widget.Material.Light.ProgressBar.Small.Inverse" parent="Widget.Material.ProgressBar.Small.Inverse"/>
<style name="Widget.Material.Light.ProgressBar.Large.Inverse" parent="Widget.Material.ProgressBar.Large.Inverse"/>
<style name="Widget.Material.Light.SeekBar" parent="Widget.Material.SeekBar"/>
- <style name="Widget.Material.Light.RatingBar" parent="Widget.Material.RatingBar" />
-
- <style name="Widget.Material.Light.RatingBar.Indicator" parent="Widget.RatingBar.Indicator">
- <item name="progressDrawable">@drawable/ratingbar_holo_light</item>
- <item name="indeterminateDrawable">@drawable/ratingbar_holo_light</item>
- <item name="minHeight">35dip</item>
- <item name="maxHeight">35dip</item>
- </style>
-
- <style name="Widget.Material.Light.RatingBar.Small" parent="Widget.RatingBar.Small">
- <item name="progressDrawable">@drawable/ratingbar_small_holo_light</item>
- <item name="indeterminateDrawable">@drawable/ratingbar_small_holo_light</item>
- <item name="minHeight">16dip</item>
- <item name="maxHeight">16dip</item>
- </style>
-
+ <style name="Widget.Material.Light.RatingBar" parent="Widget.Material.RatingBar"/>
+ <style name="Widget.Material.Light.RatingBar.Indicator" parent="Widget.Material.RatingBar.Indicator"/>
+ <style name="Widget.Material.Light.RatingBar.Small" parent="Widget.Material.RatingBar.Small"/>
<style name="Widget.Material.Light.ScrollView" parent="Widget.Material.ScrollView"/>
<style name="Widget.Material.Light.HorizontalScrollView" parent="Widget.Material.HorizontalScrollView"/>
<style name="Widget.Material.Light.Spinner" parent="Widget.Material.Spinner" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b4ba316..42d187d 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2037,19 +2037,18 @@
<java-symbol type="dimen" name="timepicker_text_size_normal" />
<java-symbol type="dimen" name="timepicker_text_size_inner" />
<java-symbol type="string" name="battery_saver_description" />
- <java-symbol type="string" name="downtime_condition_summary" />
- <java-symbol type="string" name="downtime_condition_line_one" />
<java-symbol type="string" name="zen_mode_forever" />
+ <java-symbol type="string" name="zen_mode_rule_name_combination" />
<java-symbol type="plurals" name="zen_mode_duration_minutes" />
<java-symbol type="plurals" name="zen_mode_duration_hours" />
<java-symbol type="plurals" name="zen_mode_duration_minutes_summary" />
<java-symbol type="plurals" name="zen_mode_duration_hours_summary" />
<java-symbol type="string" name="zen_mode_until" />
- <java-symbol type="string" name="zen_mode_next_alarm_summary" />
- <java-symbol type="string" name="zen_mode_next_alarm_line_one" />
+ <java-symbol type="string" name="zen_mode_feature_name" />
+ <java-symbol type="string" name="zen_mode_downtime_feature_name" />
+ <java-symbol type="string" name="zen_mode_default_weeknights_name" />
+ <java-symbol type="string" name="zen_mode_default_weekends_name" />
<java-symbol type="array" name="config_system_condition_providers" />
- <java-symbol type="integer" name="config_next_alarm_condition_lookahead_threshold_hrs" />
- <java-symbol type="integer" name="config_downtime_condition_lookahead_threshold_hrs" />
<java-symbol type="string" name="muted_by" />
<java-symbol type="string" name="select_day" />
@@ -2182,8 +2181,9 @@
<java-symbol type="bool" name="config_LTE_eri_for_network_name" />
<java-symbol type="bool" name="config_defaultInTouchMode" />
+ <java-symbol type="string" name="usb_midi_peripheral_name" />
<java-symbol type="string" name="usb_midi_peripheral_manufacturer_name" />
- <java-symbol type="string" name="usb_midi_peripheral_model_name" />
+ <java-symbol type="string" name="usb_midi_peripheral_product_name" />
<java-symbol type="bool" name="allow_stacked_button_bar" />
<java-symbol type="id" name="spacer" />
@@ -2214,12 +2214,16 @@
<java-symbol type="layout" name="floating_popup_container" />
<java-symbol type="layout" name="floating_popup_menu_button" />
<java-symbol type="layout" name="floating_popup_open_overflow_button" />
+ <java-symbol type="layout" name="floating_popup_close_overflow_button" />
+ <java-symbol type="layout" name="floating_popup_overflow_list_item" />
<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_text_size" />
<java-symbol type="dimen" name="floating_toolbar_menu_button_minimum_width" />
<java-symbol type="dimen" name="floating_toolbar_default_width" />
<java-symbol type="dimen" name="floating_toolbar_minimum_overflow_height" />
+ <java-symbol type="dimen" name="floating_toolbar_overflow_width" />
+ <java-symbol type="dimen" name="floating_toolbar_margin" />
<java-symbol type="drawable" name="ic_chevron_left" />
<java-symbol type="drawable" name="ic_chevron_right" />
@@ -2227,4 +2231,5 @@
<java-symbol type="string" name="date_picker_next_month_button" />
<java-symbol type="layout" name="date_picker_month_item_material" />
<java-symbol type="id" name="month_view" />
+ <java-symbol type="integer" name="config_zen_repeat_callers_threshold" />
</resources>
diff --git a/core/res/res/xml/default_zen_mode_config.xml b/core/res/res/xml/default_zen_mode_config.xml
index 1bdc1ec..5f4199a 100644
--- a/core/res/res/xml/default_zen_mode_config.xml
+++ b/core/res/res/xml/default_zen_mode_config.xml
@@ -18,7 +18,6 @@
-->
<!-- Default configuration for zen mode. See android.service.notification.ZenModeConfig. -->
-<zen version="1">
- <allow calls="false" messages="false" />
- <sleep startHour="22" startMin="0" endHour="7" endMin="0" />
+<zen version="2">
+ <allow calls="true" messages="false" reminders="true" events="true" />
</zen>
diff --git a/docs/html/google/play-services/setup.jd b/docs/html/google/play-services/setup.jd
index e75235e..70e7107 100644
--- a/docs/html/google/play-services/setup.jd
+++ b/docs/html/google/play-services/setup.jd
@@ -9,7 +9,7 @@ page.title=Setting Up Google Play Services
<h2>In this document</h2>
<ol>
<li><a href="#Setup">Add Google Play Services to Your Project</a></li>
- <li><a href="#Proguard">Create a Proguard Exception</a></li>
+ <li><a href="#Proguard">Create a ProGuard Exception</a></li>
<li><a href="#ensure">Ensure Devices Have the Google Play services APK</a></li>
</ol>
@@ -195,6 +195,17 @@ you include an API that does have a separate library.)</p>
</tr>
</table>
+<p class="note"><strong>Note:</strong> ProGuard directives are included in the Play services
+client libraries to preserve the required classes. The
+<a href="{@docRoot}tools/building/plugin-for-gradle.html">Android Plugin for Gradle</a>
+automatically appends ProGuard configuration files in an AAR (Android ARchive) package and appends
+that package to your ProGuard configuration. During project creation, Android Studio automatically
+creates the ProGuard configuration files and <code>build.gradle</code> properties for ProGuard use.
+To use ProGuard with Android Studio, you must enable the ProGuard setting in your
+<code>build.gradle</code> <code>buildTypes</code>. For more information, see the
+<a href="{@docRoot}tools/help/proguard.html">ProGuard</a> topic. </p>
+
+
</div><!-- end studio -->
<div class="select-ide eclipse">
@@ -230,6 +241,33 @@ element:
you can begin developing features with the
<a href="{@docRoot}reference/gms-packages.html">Google Play services APIs</a>.</p>
+
+<h2 id="Proguard">Create a ProGuard Exception</h2>
+
+<p>To prevent <a href="{@docRoot}tools/help/proguard.html">ProGuard</a> from stripping away
+required classes, add the following lines in the
+<code>&lt;project_directory&gt;/proguard-project.txt</code> file:
+<pre>
+-keep class * extends java.util.ListResourceBundle {
+ protected Object[][] getContents();
+}
+
+-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
+ public static final *** NULL;
+}
+
+-keepnames &#64;com.google.android.gms.common.annotation.KeepName class *
+-keepclassmembernames class * {
+ &#64;com.google.android.gms.common.annotation.KeepName *;
+}
+
+-keepnames class * implements android.os.Parcelable {
+ public static final ** CREATOR;
+}
+</pre>
+
+
+
</div><!-- end eclipse -->
<div class="select-ide other">
@@ -263,8 +301,6 @@ workspace&mdash;you should not reference the library directly from the Android S
you can begin developing features with the
<a href="{@docRoot}reference/gms-packages.html">Google Play services APIs</a>.</p>
-</div><!-- end other -->
-
<h2 id="Proguard">Create a Proguard Exception</h2>
@@ -290,11 +326,9 @@ required classes, add the following lines in the
}
</pre>
-<p class="note"><strong>Note:</strong> When using Android Studio, you must add Proguard
-to your <code>build.gradle</code> file's build types. For more information, see the
-<a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Running-ProGuard"
->Gradle Plugin User Guide</a>.
-</ol>
+
+</div><!-- end other -->
+
<h2 id="ensure">Ensure Devices Have the Google Play services APK</h2>
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index a2f71e5..05a81de 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -163,7 +163,6 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
inflateLayers(r, parser, attrs, theme);
ensurePadding();
- onStateChange(getState());
}
/**
@@ -211,7 +210,11 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
updateLayerFromTypedArray(layer, a);
a.recycle();
- if (layer.mDrawable == null) {
+ // If the layer doesn't have a drawable or unresolved theme
+ // attribute for a drawable, attempt to parse one from the child
+ // element.
+ if (layer.mDrawable == null && (layer.mThemeAttrs == null ||
+ layer.mThemeAttrs[R.styleable.LayerDrawableItem_drawable] == 0)) {
while ((type = parser.next()) == XmlPullParser.TEXT) {
}
if (type != XmlPullParser.START_TAG) {
@@ -303,7 +306,6 @@ public class LayerDrawable extends Drawable implements Drawable.Callback {
}
ensurePadding();
- onStateChange(getState());
}
@Override
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index a5776a4..da70e9b 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -112,7 +112,7 @@ struct __assertChar16Size {
*
* The PNG chunk type is "npTc".
*/
-struct Res_png_9patch
+struct alignas(uintptr_t) Res_png_9patch
{
Res_png_9patch() : wasDeserialized(false), xDivsOffset(0),
yDivsOffset(0), colorsOffset(0) { }
diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java
index ed690de..c5b6a68 100644
--- a/keystore/java/android/security/AndroidKeyStore.java
+++ b/keystore/java/android/security/AndroidKeyStore.java
@@ -512,12 +512,23 @@ public class AndroidKeyStore extends KeyStoreSpi {
}
}
- int purposes = params.getPurposes();
+ @KeyStoreKeyConstraints.PurposeEnum int purposes = params.getPurposes();
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes = params.getBlockModes();
+ if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
+ && (params.isRandomizedEncryptionRequired())) {
+ @KeyStoreKeyConstraints.BlockModeEnum int incompatibleBlockModes =
+ blockModes & ~KeyStoreKeyConstraints.BlockMode.IND_CPA_COMPATIBLE_MODES;
+ if (incompatibleBlockModes != 0) {
+ throw new KeyStoreException("Randomized encryption (IND-CPA) required but may be"
+ + " violated by block mode(s): "
+ + KeyStoreKeyConstraints.BlockMode.allToString(incompatibleBlockModes)
+ + ". See KeyStoreParameter documentation.");
+ }
+ }
for (int keymasterPurpose : KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
}
- for (int keymasterBlockMode :
- KeyStoreKeyConstraints.BlockMode.allToKeymaster(params.getBlockModes())) {
+ for (int keymasterBlockMode : KeyStoreKeyConstraints.BlockMode.allToKeymaster(blockModes)) {
args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockMode);
}
for (int keymasterPadding :
@@ -553,8 +564,8 @@ public class AndroidKeyStore extends KeyStoreSpi {
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8);
if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
- || ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) {
- // Permit caller-specified IV. This is needed for the Cipher abstraction.
+ && (!params.isRandomizedEncryptionRequired())) {
+ // Permit caller-provided IV when encrypting with this key
args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
}
diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java
index 635b2fa..43f3b30 100644
--- a/keystore/java/android/security/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/AndroidKeyStoreProvider.java
@@ -64,6 +64,7 @@ public class AndroidKeyStoreProvider extends Provider {
putSecretKeyFactoryImpl("HmacSHA512");
// javax.crypto.Mac
+ putMacImpl("HmacSHA1", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA1");
putMacImpl("HmacSHA224", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA224");
putMacImpl("HmacSHA256", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA256");
putMacImpl("HmacSHA384", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA384");
diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java
index 0e490cd..4eedb24 100644
--- a/keystore/java/android/security/KeyGeneratorSpec.java
+++ b/keystore/java/android/security/KeyGeneratorSpec.java
@@ -22,6 +22,7 @@ import android.text.TextUtils;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Date;
+import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@@ -51,6 +52,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final boolean mRandomizedEncryptionRequired;
private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private final int mUserAuthenticationValidityDurationSeconds;
private final boolean mInvalidatedOnNewFingerprintEnrolled;
@@ -66,6 +68,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
@KeyStoreKeyConstraints.PurposeEnum int purposes,
@KeyStoreKeyConstraints.PaddingEnum int paddings,
@KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ boolean randomizedEncryptionRequired,
@KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
int userAuthenticationValidityDurationSeconds,
boolean invalidatedOnNewFingerprintEnrolled) {
@@ -89,6 +92,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
mPurposes = purposes;
mPaddings = paddings;
mBlockModes = blockModes;
+ mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticators = userAuthenticators;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
@@ -172,6 +176,19 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
}
/**
+ * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic property
+ * being required is <em>indistinguishability under chosen-plaintext attack ({@code
+ * IND-CPA})</em>. This property is important because it mitigates several classes of
+ * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
+ * given plaintext always produces the same ciphertext, an attacker may see the repeated
+ * ciphertexts and be able to deduce something about the plaintext.
+ */
+ public boolean isRandomizedEncryptionRequired() {
+ return mRandomizedEncryptionRequired;
+ }
+
+ /**
* Gets the set of user authenticators which protect access to this key. The key can only be
* used iff the user has authenticated to at least one of these user authenticators.
*
@@ -223,6 +240,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
private @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private boolean mRandomizedEncryptionRequired = true;
private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private int mUserAuthenticationValidityDurationSeconds = -1;
private boolean mInvalidatedOnNewFingerprintEnrolled;
@@ -281,7 +299,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant before which the key is not yet valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityEnd(Date)
*/
@@ -293,7 +311,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant after which the key is no longer valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityStart(Date)
* @see #setKeyValidityForConsumptionEnd(Date)
@@ -308,7 +326,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant after which the key is no longer valid for encryption and signing.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForConsumptionEnd(Date)
*/
@@ -321,7 +339,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
* Sets the time instant after which the key is no longer valid for decryption and
* verification.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForOriginationEnd(Date)
*/
@@ -363,6 +381,43 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
}
/**
+ * Sets whether encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic
+ * property being required is <em>indistinguishability under chosen-plaintext attack
+ * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
+ * of weaknesses due to which ciphertext may leak information about plaintext. For example,
+ * if a given plaintext always produces the same ciphertext, an attacker may see the
+ * repeated ciphertexts and be able to deduce something about the plaintext.
+ *
+ * <p>By default, {@code IND-CPA} is required.
+ *
+ * <p>When {@code IND-CPA} is required:
+ * <ul>
+ * <li>block modes which do not offer {@code IND-CPA}, such as {@code ECB}, are prohibited;
+ * </li>
+ * <li>in block modes which use an IV, such as {@code CBC}, {@code CTR}, and {@code GCM},
+ * caller-provided IVs are rejected when encrypting, to ensure that only random IVs are
+ * used.</li>
+ *
+ * <p>Before disabling this requirement, consider the following approaches instead:
+ * <ul>
+ * <li>If you are generating a random IV for encryption and then initializing a {@code}
+ * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV
+ * instead. This will occur if the {@code Cipher} is initialized for encryption without an
+ * IV. The IV can then be queried via {@link Cipher#getIV()}.</li>
+ * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully
+ * random, such as the name of the file being encrypted, or transaction ID, or password,
+ * or a device identifier), consider changing your design to use a random IV which will then
+ * be provided in addition to the ciphertext to the entities which need to decrypt the
+ * ciphertext.</li>
+ * </ul>
+ */
+ public Builder setRandomizedEncryptionRequired(boolean required) {
+ mRandomizedEncryptionRequired = required;
+ return this;
+ }
+
+ /**
* Sets the user authenticators which protect access to this key. The key can only be used
* iff the user has authenticated to at least one of these user authenticators.
*
@@ -427,6 +482,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec {
mPurposes,
mPaddings,
mBlockModes,
+ mRandomizedEncryptionRequired,
mUserAuthenticators,
mUserAuthenticationValidityDurationSeconds,
mInvalidatedOnNewFingerprintEnrolled);
diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java
index 52b7097..4ca220d 100644
--- a/keystore/java/android/security/KeyPairGeneratorSpec.java
+++ b/keystore/java/android/security/KeyPairGeneratorSpec.java
@@ -86,6 +86,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final boolean mRandomizedEncryptionRequired;
+
private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private final int mUserAuthenticationValidityDurationSeconds;
@@ -134,6 +136,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
@KeyStoreKeyConstraints.DigestEnum int digests,
@KeyStoreKeyConstraints.PaddingEnum int paddings,
@KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ boolean randomizedEncryptionRequired,
@KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
int userAuthenticationValidityDurationSeconds,
boolean invalidatedOnNewFingerprintEnrolled) {
@@ -174,6 +177,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
mDigests = digests;
mPaddings = paddings;
mBlockModes = blockModes;
+ mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticators = userAuthenticators;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
@@ -186,8 +190,28 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize,
AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber,
Date startDate, Date endDate, int flags) {
- this(context, keyStoreAlias, keyType, keySize, spec, subjectDN, serialNumber, startDate,
- endDate, flags, startDate, endDate, endDate, 0, 0, 0, 0, 0, -1, false);
+
+ this(context,
+ keyStoreAlias,
+ keyType,
+ keySize,
+ spec,
+ subjectDN,
+ serialNumber,
+ startDate,
+ endDate,
+ flags,
+ startDate,
+ endDate,
+ endDate,
+ 0,
+ 0,
+ 0,
+ 0,
+ true,
+ 0,
+ -1,
+ false);
}
/**
@@ -347,6 +371,21 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
}
/**
+ * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic property
+ * being required is <em>indistinguishability under chosen-plaintext attack ({@code
+ * IND-CPA})</em>. This property is important because it mitigates several classes of
+ * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
+ * given plaintext always produces the same ciphertext, an attacker may see the repeated
+ * ciphertexts and be able to deduce something about the plaintext.
+ *
+ * @hide
+ */
+ public boolean isRandomizedEncryptionRequired() {
+ return mRandomizedEncryptionRequired;
+ }
+
+ /**
* Gets the set of user authenticators which protect access to the private key. The key can only
* be used iff the user has authenticated to at least one of these user authenticators.
*
@@ -446,6 +485,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private boolean mRandomizedEncryptionRequired = true;
+
private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private int mUserAuthenticationValidityDurationSeconds = -1;
@@ -580,7 +621,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant before which the key is not yet valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityEnd(Date)
*
@@ -594,7 +635,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant after which the key is no longer valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityStart(Date)
* @see #setKeyValidityForConsumptionEnd(Date)
@@ -611,7 +652,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
/**
* Sets the time instant after which the key is no longer valid for encryption and signing.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForConsumptionEnd(Date)
*
@@ -626,7 +667,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
* Sets the time instant after which the key is no longer valid for decryption and
* verification.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForOriginationEnd(Date)
*
@@ -689,6 +730,33 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
}
/**
+ * Sets whether encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic
+ * property being required is <em>indistinguishability under chosen-plaintext attack
+ * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
+ * of weaknesses due to which ciphertext may leak information about plaintext. For example,
+ * if a given plaintext always produces the same ciphertext, an attacker may see the
+ * repeated ciphertexts and be able to deduce something about the plaintext.
+ *
+ * <p>By default, {@code IND-CPA} is required.
+ *
+ * <p>When {@code IND-CPA} is required, encryption/decryption transformations which do not
+ * offer {@code IND-CPA}, such as RSA without padding, are prohibited.
+ *
+ * <p>Before disabling this requirement, consider the following approaches instead:
+ * <ul>
+ * <li>If you are using RSA encryption without padding, consider switching to padding
+ * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ public Builder setRandomizedEncryptionRequired(boolean required) {
+ mRandomizedEncryptionRequired = required;
+ return this;
+ }
+
+ /**
* Sets the user authenticators which protect access to this key. The key can only be used
* iff the user has authenticated to at least one of these user authenticators.
*
@@ -771,6 +839,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec {
mDigests,
mPaddings,
mBlockModes,
+ mRandomizedEncryptionRequired,
mUserAuthenticators,
mUserAuthenticationValidityDurationSeconds,
mInvalidatedOnNewFingerprintEnrolled);
diff --git a/keystore/java/android/security/KeyStoreKeyCharacteristics.java b/keystore/java/android/security/KeyStoreKeyCharacteristics.java
index 543b5d8..1f5d400 100644
--- a/keystore/java/android/security/KeyStoreKeyCharacteristics.java
+++ b/keystore/java/android/security/KeyStoreKeyCharacteristics.java
@@ -31,7 +31,7 @@ public abstract class KeyStoreKeyCharacteristics {
private KeyStoreKeyCharacteristics() {}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({Origin.GENERATED_INSIDE_TEE, Origin.GENERATED_OUTSIDE_OF_TEE, Origin.IMPORTED})
+ @IntDef({Origin.GENERATED, Origin.IMPORTED})
public @interface OriginEnum {}
/**
@@ -40,14 +40,11 @@ public abstract class KeyStoreKeyCharacteristics {
public static abstract class Origin {
private Origin() {}
- /** Key was generated inside a TEE. */
- public static final int GENERATED_INSIDE_TEE = 1;
+ /** Key was generated inside AndroidKeyStore. */
+ public static final int GENERATED = 1 << 0;
- /** Key was generated outside of a TEE. */
- public static final int GENERATED_OUTSIDE_OF_TEE = 2;
-
- /** Key was imported. */
- public static final int IMPORTED = 0;
+ /** Key was imported into AndroidKeyStore. */
+ public static final int IMPORTED = 1 << 1;
/**
* @hide
@@ -55,9 +52,7 @@ public abstract class KeyStoreKeyCharacteristics {
public static @OriginEnum int fromKeymaster(int origin) {
switch (origin) {
case KeymasterDefs.KM_ORIGIN_HARDWARE:
- return GENERATED_INSIDE_TEE;
- case KeymasterDefs.KM_ORIGIN_SOFTWARE:
- return GENERATED_OUTSIDE_OF_TEE;
+ return GENERATED;
case KeymasterDefs.KM_ORIGIN_IMPORTED:
return IMPORTED;
default:
diff --git a/keystore/java/android/security/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java
index 097c20f..98ac3e7 100644
--- a/keystore/java/android/security/KeyStoreKeyConstraints.java
+++ b/keystore/java/android/security/KeyStoreKeyConstraints.java
@@ -123,7 +123,7 @@ public abstract class KeyStoreKeyConstraints {
}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({Algorithm.AES, Algorithm.HMAC})
+ @IntDef({Algorithm.AES, Algorithm.HMAC, Algorithm.RSA, Algorithm.EC})
public @interface AlgorithmEnum {}
/**
@@ -135,12 +135,22 @@ public abstract class KeyStoreKeyConstraints {
/**
* Key algorithm: AES.
*/
- public static final int AES = 0;
+ public static final int AES = 1 << 0;
/**
* Key algorithm: HMAC.
*/
- public static final int HMAC = 1;
+ public static final int HMAC = 1 << 1;
+
+ /**
+ * Key algorithm: RSA.
+ */
+ public static final int RSA = 1 << 2;
+
+ /**
+ * Key algorithm: EC.
+ */
+ public static final int EC = 1 << 3;
/**
* @hide
@@ -151,6 +161,10 @@ public abstract class KeyStoreKeyConstraints {
return KeymasterDefs.KM_ALGORITHM_AES;
case HMAC:
return KeymasterDefs.KM_ALGORITHM_HMAC;
+ case RSA:
+ return KeymasterDefs.KM_ALGORITHM_RSA;
+ case EC:
+ return KeymasterDefs.KM_ALGORITHM_EC;
default:
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
}
@@ -165,6 +179,10 @@ public abstract class KeyStoreKeyConstraints {
return AES;
case KeymasterDefs.KM_ALGORITHM_HMAC:
return HMAC;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ return RSA;
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ return EC;
default:
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
}
@@ -179,6 +197,10 @@ public abstract class KeyStoreKeyConstraints {
return "AES";
case HMAC:
return "HMAC";
+ case RSA:
+ return "RSA";
+ case EC:
+ return "EC";
default:
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
}
@@ -213,8 +235,18 @@ public abstract class KeyStoreKeyConstraints {
throw new IllegalArgumentException("HMAC digest not specified");
}
switch (digest) {
+ case Digest.MD5:
+ return "HmacMD5";
+ case Digest.SHA1:
+ return "HmacSHA1";
+ case Digest.SHA224:
+ return "HmacSHA224";
case Digest.SHA256:
return "HmacSHA256";
+ case Digest.SHA384:
+ return "HmacSHA384";
+ case Digest.SHA512:
+ return "HmacSHA512";
default:
throw new IllegalArgumentException(
"Unsupported HMAC digest: " + digest);
@@ -223,11 +255,32 @@ public abstract class KeyStoreKeyConstraints {
throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm);
}
}
+
+ /**
+ * @hide
+ */
+ public static String toJCAKeyPairAlgorithm(@AlgorithmEnum int algorithm) {
+ switch (algorithm) {
+ case RSA:
+ return "RSA";
+ case EC:
+ return "EC";
+ default:
+ throw new IllegalArgumentException("Unsupported key alorithm: " + algorithm);
+ }
+ }
}
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true,
- value = {Padding.NONE, Padding.PKCS7})
+ value = {
+ Padding.NONE,
+ Padding.PKCS7,
+ Padding.RSA_PKCS1_ENCRYPTION,
+ Padding.RSA_PKCS1_SIGNATURE,
+ Padding.RSA_OAEP,
+ Padding.RSA_PSS,
+ })
public @interface PaddingEnum {}
/**
@@ -247,6 +300,26 @@ public abstract class KeyStoreKeyConstraints {
public static final int PKCS7 = 1 << 1;
/**
+ * RSA PKCS#1 v1.5 padding for encryption/decryption.
+ */
+ public static final int RSA_PKCS1_ENCRYPTION = 1 << 2;
+
+ /**
+ * RSA PKCS#1 v1.5 padding for signatures.
+ */
+ public static final int RSA_PKCS1_SIGNATURE = 1 << 3;
+
+ /**
+ * RSA Optimal Asymmetric Encryption Padding (OAEP).
+ */
+ public static final int RSA_OAEP = 1 << 4;
+
+ /**
+ * RSA PKCS#1 v2.1 Probabilistic Signature Scheme (PSS) padding.
+ */
+ public static final int RSA_PSS = 1 << 5;
+
+ /**
* @hide
*/
public static int toKeymaster(int padding) {
@@ -255,6 +328,14 @@ public abstract class KeyStoreKeyConstraints {
return KeymasterDefs.KM_PAD_NONE;
case PKCS7:
return KeymasterDefs.KM_PAD_PKCS7;
+ case RSA_PKCS1_ENCRYPTION:
+ return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT;
+ case RSA_PKCS1_SIGNATURE:
+ return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN;
+ case RSA_OAEP:
+ return KeymasterDefs.KM_PAD_RSA_OAEP;
+ case RSA_PSS:
+ return KeymasterDefs.KM_PAD_RSA_PSS;
default:
throw new IllegalArgumentException("Unknown padding: " + padding);
}
@@ -269,6 +350,14 @@ public abstract class KeyStoreKeyConstraints {
return NONE;
case KeymasterDefs.KM_PAD_PKCS7:
return PKCS7;
+ case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
+ return RSA_PKCS1_ENCRYPTION;
+ case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN:
+ return RSA_PKCS1_SIGNATURE;
+ case KeymasterDefs.KM_PAD_RSA_OAEP:
+ return RSA_OAEP;
+ case KeymasterDefs.KM_PAD_RSA_PSS:
+ return RSA_PSS;
default:
throw new IllegalArgumentException("Unknown padding: " + padding);
}
@@ -283,6 +372,14 @@ public abstract class KeyStoreKeyConstraints {
return "NONE";
case PKCS7:
return "PKCS#7";
+ case RSA_PKCS1_ENCRYPTION:
+ return "RSA PKCS#1 (encryption)";
+ case RSA_PKCS1_SIGNATURE:
+ return "RSA PKCS#1 (signature)";
+ case RSA_OAEP:
+ return "RSA OAEP";
+ case RSA_PSS:
+ return "RSA PSS";
default:
throw new IllegalArgumentException("Unknown padding: " + padding);
}
@@ -291,12 +388,18 @@ public abstract class KeyStoreKeyConstraints {
/**
* @hide
*/
- public static @PaddingEnum int fromJCAPadding(String padding) {
+ public static @PaddingEnum int fromJCACipherPadding(String padding) {
String paddingLower = padding.toLowerCase(Locale.US);
if ("nopadding".equals(paddingLower)) {
return NONE;
} else if ("pkcs7padding".equals(paddingLower)) {
return PKCS7;
+ } else if ("pkcs1padding".equals(paddingLower)) {
+ return RSA_PKCS1_ENCRYPTION;
+ } else if (("oaeppadding".equals(paddingLower))
+ || ((paddingLower.startsWith("oaepwith"))
+ && (paddingLower.endsWith("padding")))) {
+ return RSA_OAEP;
} else {
throw new IllegalArgumentException("Unknown padding: " + padding);
}
@@ -592,6 +695,14 @@ public abstract class KeyStoreKeyConstraints {
public static final int GCM = 1 << 3;
/**
+ * Set of block modes compatible with IND-CPA if used correctly.
+ *
+ * @hide
+ */
+ public static final @BlockModeEnum int IND_CPA_COMPATIBLE_MODES =
+ CBC | CTR | GCM;
+
+ /**
* @hide
*/
public static int toKeymaster(@BlockModeEnum int mode) {
@@ -670,6 +781,24 @@ public abstract class KeyStoreKeyConstraints {
/**
* @hide
*/
+ public static String allToString(@BlockModeEnum int modes) {
+ StringBuilder result = new StringBuilder("[");
+ boolean firstValue = true;
+ for (@BlockModeEnum int mode : getSetFlags(modes)) {
+ if (firstValue) {
+ firstValue = false;
+ } else {
+ result.append(", ");
+ }
+ result.append(toString(mode));
+ }
+ result.append(']');
+ return result.toString();
+ }
+
+ /**
+ * @hide
+ */
public static @BlockModeEnum int fromJCAMode(String mode) {
String modeLower = mode.toLowerCase(Locale.US);
if ("ecb".equals(modeLower)) {
diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
index 279acd6..b39d16d 100644
--- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
@@ -138,13 +138,26 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
}
int keySizeBits = (spec.getKeySize() != null) ? spec.getKeySize() : mDefaultKeySizeBits;
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits);
- int purposes = spec.getPurposes();
+ @KeyStoreKeyConstraints.PurposeEnum int purposes = spec.getPurposes();
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes = spec.getBlockModes();
+ if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
+ && (spec.isRandomizedEncryptionRequired())) {
+ @KeyStoreKeyConstraints.BlockModeEnum int incompatibleBlockModes =
+ blockModes & ~KeyStoreKeyConstraints.BlockMode.IND_CPA_COMPATIBLE_MODES;
+ if (incompatibleBlockModes != 0) {
+ throw new IllegalStateException(
+ "Randomized encryption (IND-CPA) required but may be violated by block"
+ + " mode(s): "
+ + KeyStoreKeyConstraints.BlockMode.allToString(incompatibleBlockModes)
+ + ". See KeyGeneratorSpec documentation.");
+ }
+ }
+
for (int keymasterPurpose :
KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
}
- for (int keymasterBlockMode :
- KeyStoreKeyConstraints.BlockMode.allToKeymaster(spec.getBlockModes())) {
+ for (int keymasterBlockMode : KeyStoreKeyConstraints.BlockMode.allToKeymaster(blockModes)) {
args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockMode);
}
for (int keymasterPadding :
@@ -177,8 +190,8 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
? spec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE));
if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
- || ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) {
- // Permit caller-specified IV. This is needed due to the Cipher abstraction.
+ && (!spec.isRandomizedEncryptionRequired())) {
+ // Permit caller-provided IV when encrypting with this key
args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
}
diff --git a/keystore/java/android/security/KeyStoreKeySpec.java b/keystore/java/android/security/KeyStoreKeySpec.java
index 256d9b3..65bb236 100644
--- a/keystore/java/android/security/KeyStoreKeySpec.java
+++ b/keystore/java/android/security/KeyStoreKeySpec.java
@@ -28,6 +28,7 @@ import java.util.Date;
public class KeyStoreKeySpec implements KeySpec {
private final String mKeystoreAlias;
private final int mKeySize;
+ private final boolean mTeeBacked;
private final @KeyStoreKeyCharacteristics.OriginEnum int mOrigin;
private final Date mKeyValidityStart;
private final Date mKeyValidityForOriginationEnd;
@@ -46,6 +47,7 @@ public class KeyStoreKeySpec implements KeySpec {
* @hide
*/
KeyStoreKeySpec(String keystoreKeyAlias,
+ boolean teeBacked,
@KeyStoreKeyCharacteristics.OriginEnum int origin,
int keySize,
Date keyValidityStart,
@@ -61,6 +63,7 @@ public class KeyStoreKeySpec implements KeySpec {
int userAuthenticationValidityDurationSeconds,
boolean invalidatedOnNewFingerprintEnrolled) {
mKeystoreAlias = keystoreKeyAlias;
+ mTeeBacked = teeBacked;
mOrigin = origin;
mKeySize = keySize;
mKeyValidityStart = keyValidityStart;
@@ -85,6 +88,14 @@ public class KeyStoreKeySpec implements KeySpec {
}
/**
+ * Returns {@code true} if the key is TEE-backed. Key material of TEE-backed keys is available
+ * in plaintext only inside the TEE.
+ */
+ public boolean isTeeBacked() {
+ return mTeeBacked;
+ }
+
+ /**
* Gets the origin of the key.
*/
public @KeyStoreKeyCharacteristics.OriginEnum int getOrigin() {
diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java
index 0b2f9b6..751eef5 100644
--- a/keystore/java/android/security/KeyStoreParameter.java
+++ b/keystore/java/android/security/KeyStoreParameter.java
@@ -19,13 +19,14 @@ package android.security;
import android.content.Context;
import java.security.Key;
-import java.security.KeyPairGenerator;
import java.security.KeyStore.ProtectionParameter;
import java.util.Date;
+import javax.crypto.Cipher;
+
/**
- * This provides the optional parameters that can be specified for
- * {@code KeyStore} entries that work with
+ * Parameters specifying how to secure and restrict the use of a key being
+ * imported into the
* <a href="{@docRoot}training/articles/keystore.html">Android KeyStore
* facility</a>. The Android KeyStore facility is accessed through a
* {@link java.security.KeyStore} API using the {@code AndroidKeyStore}
@@ -36,12 +37,6 @@ import java.util.Date;
* there is only one logical instance of the {@code KeyStore} per application
* UID so apps using the {@code sharedUid} facility will also share a
* {@code KeyStore}.
- * <p>
- * Keys may be generated using the {@link KeyPairGenerator} facility with a
- * {@link KeyPairGeneratorSpec} to specify the entry's {@code alias}. A
- * self-signed X.509 certificate will be attached to generated entries, but that
- * may be replaced at a later time by a certificate signed by a real Certificate
- * Authority.
*/
public final class KeyStoreParameter implements ProtectionParameter {
private int mFlags;
@@ -52,6 +47,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
private final @KeyStoreKeyConstraints.DigestEnum Integer mDigests;
private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final boolean mRandomizedEncryptionRequired;
private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private final int mUserAuthenticationValidityDurationSeconds;
private final boolean mInvalidatedOnNewFingerprintEnrolled;
@@ -64,6 +60,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
@KeyStoreKeyConstraints.PaddingEnum int paddings,
@KeyStoreKeyConstraints.DigestEnum Integer digests,
@KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ boolean randomizedEncryptionRequired,
@KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
int userAuthenticationValidityDurationSeconds,
boolean invalidatedOnNewFingerprintEnrolled) {
@@ -81,6 +78,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
mPaddings = paddings;
mDigests = digests;
mBlockModes = blockModes;
+ mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticators = userAuthenticators;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
@@ -188,6 +186,21 @@ public final class KeyStoreParameter implements ProtectionParameter {
}
/**
+ * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic property
+ * being required is <em>indistinguishability under chosen-plaintext attack ({@code
+ * IND-CPA})</em>. This property is important because it mitigates several classes of
+ * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
+ * given plaintext always produces the same ciphertext, an attacker may see the repeated
+ * ciphertexts and be able to deduce something about the plaintext.
+ *
+ * @hide
+ */
+ public boolean isRandomizedEncryptionRequired() {
+ return mRandomizedEncryptionRequired;
+ }
+
+ /**
* Gets the set of user authenticators which protect access to this key. The key can only be
* used iff the user has authenticated to at least one of these user authenticators.
*
@@ -251,6 +264,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
private @KeyStoreKeyConstraints.DigestEnum Integer mDigests;
private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private boolean mRandomizedEncryptionRequired = true;
private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private int mUserAuthenticationValidityDurationSeconds = -1;
private boolean mInvalidatedOnNewFingerprintEnrolled;
@@ -287,7 +301,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
/**
* Sets the time instant before which the key is not yet valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityEnd(Date)
*
@@ -301,7 +315,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
/**
* Sets the time instant after which the key is no longer valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityStart(Date)
* @see #setKeyValidityForConsumptionEnd(Date)
@@ -318,7 +332,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
/**
* Sets the time instant after which the key is no longer valid for encryption and signing.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForConsumptionEnd(Date)
*
@@ -333,7 +347,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
* Sets the time instant after which the key is no longer valid for decryption and
* verification.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForOriginationEnd(Date)
*
@@ -398,6 +412,47 @@ public final class KeyStoreParameter implements ProtectionParameter {
}
/**
+ * Sets whether encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic
+ * property being required is <em>indistinguishability under chosen-plaintext attack
+ * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
+ * of weaknesses due to which ciphertext may leak information about plaintext. For example,
+ * if a given plaintext always produces the same ciphertext, an attacker may see the
+ * repeated ciphertexts and be able to deduce something about the plaintext.
+ *
+ * <p>By default, {@code IND-CPA} is required.
+ *
+ * <p>When {@code IND-CPA} is required:
+ * <ul>
+ * <li>transformation which do not offer {@code IND-CPA}, such as symmetric ciphers using
+ * {@code ECB} mode or RSA encryption without padding, are prohibited;</li>
+ * <li>in transformations which use an IV, such as symmetric ciphers in {@code CBC},
+ * {@code CTR}, and {@code GCM} block modes, caller-provided IVs are rejected when
+ * encrypting, to ensure that only random IVs are used.</li>
+ *
+ * <p>Before disabling this requirement, consider the following approaches instead:
+ * <ul>
+ * <li>If you are generating a random IV for encryption and then initializing a {@code}
+ * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV
+ * instead. This will occur if the {@code Cipher} is initialized for encryption without an
+ * IV. The IV can then be queried via {@link Cipher#getIV()}.</li>
+ * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully
+ * random, such as the name of the file being encrypted, or transaction ID, or password,
+ * or a device identifier), consider changing your design to use a random IV which will then
+ * be provided in addition to the ciphertext to the entities which need to decrypt the
+ * ciphertext.</li>
+ * <li>If you are using RSA encryption without padding, consider switching to padding
+ * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ public Builder setRandomizedEncryptionRequired(boolean required) {
+ mRandomizedEncryptionRequired = required;
+ return this;
+ }
+
+ /**
* Sets the user authenticators which protect access to this key. The key can only be used
* iff the user has authenticated to at least one of these user authenticators.
*
@@ -465,6 +520,7 @@ public final class KeyStoreParameter implements ProtectionParameter {
mPaddings,
mDigests,
mBlockModes,
+ mRandomizedEncryptionRequired,
mUserAuthenticators,
mUserAuthenticationValidityDurationSeconds,
mInvalidatedOnNewFingerprintEnrolled);
diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
index 8bf228a..a5e87d1 100644
--- a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
@@ -70,7 +70,8 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
+ " Keystore error: " + errorCode);
}
- @KeyStoreKeyCharacteristics.OriginEnum Integer origin;
+ boolean teeBacked;
+ @KeyStoreKeyCharacteristics.OriginEnum int origin;
int keySize;
@KeyStoreKeyConstraints.PurposeEnum int purposes;
@KeyStoreKeyConstraints.AlgorithmEnum int algorithm;
@@ -80,11 +81,17 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
@KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators;
@KeyStoreKeyConstraints.UserAuthenticatorEnum int teeEnforcedUserAuthenticators;
try {
- origin = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_ORIGIN);
- if (origin == null) {
+ if (keyCharacteristics.hwEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) {
+ teeBacked = true;
+ origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(
+ keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1));
+ } else if (keyCharacteristics.swEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) {
+ teeBacked = false;
+ origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(
+ keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1));
+ } else {
throw new InvalidKeySpecException("Key origin not available");
}
- origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(origin);
Integer keySizeInteger =
KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_KEY_SIZE);
if (keySizeInteger == null) {
@@ -147,6 +154,7 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
boolean invalidatedOnNewFingerprintEnrolled = false;
return new KeyStoreKeySpec(entryAlias,
+ teeBacked,
origin,
keySize,
keyValidityStart,
diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java
index c9a140c..1a5552a 100644
--- a/keystore/tests/src/android/security/KeyStoreTest.java
+++ b/keystore/tests/src/android/security/KeyStoreTest.java
@@ -294,14 +294,14 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
public void testSaw_ungrantedUid_Bluetooth() throws Exception {
String[] results1 = mKeyStore.saw(TEST_KEYNAME, Process.BLUETOOTH_UID);
- assertNull(results1);
+ assertEquals(0, results1.length);
mKeyStore.password(TEST_PASSWD);
mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
String[] results2 = mKeyStore.saw(TEST_KEYNAME, Process.BLUETOOTH_UID);
- assertNull(results2);
+ assertEquals(0, results2.length);
}
public void testSaw_grantedUid_Wifi() throws Exception {
@@ -798,7 +798,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
// TODO: Verify we have an RSA public key that's well formed.
}
- public void testAesOcbEncryptSuccess() throws Exception {
+ public void testAesGcmEncryptSuccess() throws Exception {
String name = "test";
KeymasterArguments args = new KeymasterArguments();
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
@@ -806,7 +806,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB);
+ args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM);
args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096);
args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16);
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
@@ -903,9 +903,7 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB);
- args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096);
- args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16);
+ args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR);
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
KeyCharacteristics outCharacteristics = new KeyCharacteristics();
@@ -935,11 +933,9 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_PKCS7);
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB);
- args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096);
- args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16);
+ args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 1);
KeyCharacteristics outCharacteristics = new KeyCharacteristics();
diff --git a/libs/hwui/AmbientShadow.cpp b/libs/hwui/AmbientShadow.cpp
index 0a210d6..a4100a2 100644
--- a/libs/hwui/AmbientShadow.cpp
+++ b/libs/hwui/AmbientShadow.cpp
@@ -45,8 +45,9 @@
/**
* Other constants:
*/
-// For the edge of the penumbra, the opacity is 0.
-#define OUTER_OPACITY (0.0f)
+// For the edge of the penumbra, the opacity is 0. After transform (1 - alpha),
+// it is 1.
+#define TRANSFORMED_OUTER_OPACITY (1.0f)
// Once the alpha difference is greater than this threshold, we will allocate extra
// edge vertices.
@@ -83,11 +84,13 @@ inline float getAlphaFromFactoredZ(float factoredZ) {
return 1.0 / (1 + MathUtils::max(factoredZ, 0.0f));
}
+// The shader is using gaussian function e^-(1-x)*(1-x)*4, therefore, we transform
+// the alpha value to (1 - alpha)
inline float getTransformedAlphaFromAlpha(float alpha) {
- return acosf(1.0f - 2.0f * alpha);
+ return 1.0f - alpha;
}
-// The output is ranged from 0 to M_PI.
+// The output is ranged from 0 to 1.
inline float getTransformedAlphaFromFactoredZ(float factoredZ) {
return getTransformedAlphaFromAlpha(getAlphaFromFactoredZ(factoredZ));
}
@@ -249,7 +252,7 @@ void AmbientShadow::createAmbientShadow(bool isCasterOpaque,
indexBuffer[indexBufferIndex++] = vertexBufferIndex;
indexBuffer[indexBufferIndex++] = currentInnerVertexIndex;
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], outerVertex.x,
- outerVertex.y, OUTER_OPACITY);
+ outerVertex.y, TRANSFORMED_OUTER_OPACITY);
if (j == 0) {
outerStart = outerVertex;
@@ -285,7 +288,7 @@ void AmbientShadow::createAmbientShadow(bool isCasterOpaque,
(outerLast * startWeight + outerNext * k) / extraVerticesNumber;
indexBuffer[indexBufferIndex++] = vertexBufferIndex;
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], currentOuter.x,
- currentOuter.y, OUTER_OPACITY);
+ currentOuter.y, TRANSFORMED_OUTER_OPACITY);
if (!isCasterOpaque) {
umbraVertices[umbraIndex++] = vertexBufferIndex;
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 56380db..c79ae77 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -164,6 +164,8 @@ void FontRenderer::flushAllAndInvalidate() {
for (uint32_t i = 0; i < mRGBACacheTextures.size(); i++) {
mRGBACacheTextures[i]->init();
}
+
+ mDrawn = false;
}
void FontRenderer::flushLargeCaches(Vector<CacheTexture*>& cacheTextures) {
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index e9b22e2..41adda1 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -240,8 +240,9 @@ const char* gFS_Main_ModulateColor =
const char* gFS_Main_ApplyVertexAlphaLinearInterp =
" fragColor *= alpha;\n";
const char* gFS_Main_ApplyVertexAlphaShadowInterp =
- " fragColor *= (1.0 - cos(alpha)) / 2.0;\n";
-
+ // Use a gaussian function for the shadow fall off. Note that alpha here
+ // is actually (1.0 - alpha) for saving computation.
+ " fragColor *= exp(- alpha * alpha * 4.0) - 0.018;\n";
const char* gFS_Main_FetchTexture[2] = {
// Don't modulate
" fragColor = texture2D(baseSampler, outTexCoords);\n",
diff --git a/libs/hwui/RenderBufferCache.cpp b/libs/hwui/RenderBufferCache.cpp
index 0380c51..d0812c9 100644
--- a/libs/hwui/RenderBufferCache.cpp
+++ b/libs/hwui/RenderBufferCache.cpp
@@ -158,6 +158,11 @@ bool RenderBufferCache::put(RenderBuffer* buffer) {
buffer->getWidth(), buffer->getHeight());
return true;
+ } else {
+ RENDER_BUFFER_LOGD("Deleted %s render buffer (%dx%d) Size=%d, MaxSize=%d",
+ RenderBuffer::formatName(buffer->getFormat()),
+ buffer->getWidth(), buffer->getHeight(), size, mMaxSize);
+ delete buffer;
}
return false;
}
diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp
index b3b06d6..db3c2d9 100644
--- a/libs/hwui/SpotShadow.cpp
+++ b/libs/hwui/SpotShadow.cpp
@@ -44,6 +44,9 @@
// For each RADIANS_DIVISOR, we would allocate one more vertex b/t the normals.
#define SPOT_CORNER_RADIANS_DIVISOR (M_PI / SPOT_EXTRA_CORNER_VERTEX_PER_PI)
+// For performance, we use (1 - alpha) value for the shader input.
+#define TRANSFORMED_PENUMBRA_ALPHA 1.0f
+#define TRANSFORMED_UMBRA_ALPHA 0.0f
#include <math.h>
#include <stdlib.h>
@@ -964,11 +967,11 @@ void SpotShadow::generateTriangleStrip(bool isCasterOpaque, float shadowStrength
// Fill the IB and VB for the penumbra area.
for (int i = 0; i < newPenumbraLength; i++) {
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], newPenumbra[i].x,
- newPenumbra[i].y, 0.0f);
+ newPenumbra[i].y, TRANSFORMED_PENUMBRA_ALPHA);
}
for (int i = 0; i < umbraLength; i++) {
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], umbra[i].x, umbra[i].y,
- M_PI);
+ TRANSFORMED_UMBRA_ALPHA);
}
for (int i = 0; i < verticesPairIndex; i++) {
@@ -1008,14 +1011,14 @@ void SpotShadow::generateTriangleStrip(bool isCasterOpaque, float shadowStrength
indexBuffer[indexBufferIndex++] = newPenumbraLength + i;
indexBuffer[indexBufferIndex++] = vertexBufferIndex;
AlphaVertex::set(&shadowVertices[vertexBufferIndex++],
- closerVertex.x, closerVertex.y, M_PI);
+ closerVertex.x, closerVertex.y, TRANSFORMED_UMBRA_ALPHA);
}
} else {
// If there is no occluded umbra at all, then draw the triangle fan
// starting from the centroid to all umbra vertices.
int lastCentroidIndex = vertexBufferIndex;
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], centroid.x,
- centroid.y, M_PI);
+ centroid.y, TRANSFORMED_UMBRA_ALPHA);
for (int i = 0; i < umbraLength; i++) {
indexBuffer[indexBufferIndex++] = newPenumbraLength + i;
indexBuffer[indexBufferIndex++] = lastCentroidIndex;
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index fd7fca6..e028e3f 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -325,6 +325,13 @@ final public class MediaCodec {
*/
public static final int BUFFER_FLAG_END_OF_STREAM = 4;
+ /**
+ * This indicates that the codec is released because the media resources used by the codec
+ * have been reclaimed, for example by the resource manager.
+ * This is used by the {@link Callback#onCodecReleased} callback.
+ */
+ public static final int REASON_RECLAIMED = 1;
+
private EventHandler mEventHandler;
private Callback mCallback;
@@ -335,6 +342,7 @@ final public class MediaCodec {
private static final int CB_OUTPUT_AVAILABLE = 2;
private static final int CB_ERROR = 3;
private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
+ private static final int CB_CODEC_RELEASED = 5;
private class EventHandler extends Handler {
private MediaCodec mCodec;
@@ -405,6 +413,13 @@ final public class MediaCodec {
break;
}
+ case CB_CODEC_RELEASED:
+ {
+ int reason = msg.arg2;
+ mCallback.onCodecReleased(mCodec, reason);
+ break;
+ }
+
default:
{
break;
@@ -720,6 +735,7 @@ final public class MediaCodec {
}
/* Must be in sync with android_media_MediaCodec.cpp */
+ private final static int ACTION_FATAL = 0;
private final static int ACTION_TRANSIENT = 1;
private final static int ACTION_RECOVERABLE = 2;
@@ -1654,6 +1670,22 @@ final public class MediaCodec {
* @param format The new output format.
*/
public abstract void onOutputFormatChanged(MediaCodec codec, MediaFormat format);
+
+ /**
+ * Called when the underlying codec component has been released.
+ * <p>
+ * At this point the MediaCodec must be released, as it has moved to terminal
+ * Uninitialized state.
+ *
+ * @param codec The MediaCodec object.
+ * @param reason The reason of the release.
+ */
+ public void onCodecReleased(MediaCodec codec, int reason) {
+ int errorCode = -1;
+ String detailMessage = "resources reclaimed";
+ onError(codec,
+ new CodecException(errorCode, CodecException.ACTION_FATAL, detailMessage));
+ }
}
private void postEventFromNative(
diff --git a/media/java/android/media/MediaCrypto.java b/media/java/android/media/MediaCrypto.java
index c7c3fc2..da81b37 100644
--- a/media/java/android/media/MediaCrypto.java
+++ b/media/java/android/media/MediaCrypto.java
@@ -70,6 +70,20 @@ public final class MediaCrypto {
*/
public final native boolean requiresSecureDecoderComponent(String mime);
+ /**
+ * Associate a MediaDrm session with this MediaCrypto instance. The
+ * MediaDrm session is used to securely load decryption keys for a
+ * crypto scheme. The crypto keys loaded through the MediaDrm session
+ * may be selected for use during the decryption operation performed
+ * by {@link android.media.MediaCodec#queueSecureInputBuffer} by specifying
+ * their key ids in the {@link android.media.MediaCodec.CryptoInfo#key} field.
+ * @param sessionId the MediaDrm sessionId to associate with this
+ * MediaCrypto instance
+ * @throws MediaCryptoException on failure to set the sessionId
+ */
+ public final native void setMediaDrmSession(byte[] sessionId)
+ throws MediaCryptoException;
+
@Override
protected void finalize() {
native_finalize();
diff --git a/media/java/android/media/MediaCryptoException.java b/media/java/android/media/MediaCryptoException.java
index 44c5222..703e96f 100644
--- a/media/java/android/media/MediaCryptoException.java
+++ b/media/java/android/media/MediaCryptoException.java
@@ -17,8 +17,8 @@
package android.media;
/**
- * Exception thrown if MediaCrypto object could not be instantiated for
- * whatever reason.
+ * Exception thrown if MediaCrypto object could not be instantiated or
+ * if unable to perform an operation on the MediaCrypto object.
*/
public final class MediaCryptoException extends Exception {
public MediaCryptoException(String detailMessage) {
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 069f7ff..fc5fc43 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -17,9 +17,10 @@
package android.media;
import java.lang.ref.WeakReference;
-import java.util.UUID;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.UUID;
import android.annotation.SystemApi;
import android.os.Handler;
import android.os.Looper;
@@ -98,12 +99,14 @@ import android.util.Log;
*/
public final class MediaDrm {
- private final static String TAG = "MediaDrm";
+ private static final String TAG = "MediaDrm";
private static final String PERMISSION = android.Manifest.permission.ACCESS_DRM_CERTIFICATES;
private EventHandler mEventHandler;
private OnEventListener mOnEventListener;
+ private OnKeysChangeListener mOnKeysChangeListener;
+ private OnExpirationUpdateListener mOnExpirationUpdateListener;
private long mNativeContext;
@@ -227,6 +230,148 @@ public final class MediaDrm {
}
/**
+ * Register a callback to be invoked when a session expiration update
+ * occurs. The app's OnExpirationUpdateListener will be notified
+ * when the expiration time of the keys in the session have changed.
+ * @param listener the callback that will be run
+ * @param handler the handler on which the listener should be invoked, or
+ * null if the listener should be invoked on the calling thread's looper.
+ */
+ public void setOnExpirationUpdateListener(OnExpirationUpdateListener listener,
+ Handler handler)
+ {
+ if (listener != null) {
+ Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
+ if (looper != null) {
+ if (mEventHandler == null || mEventHandler.getLooper() != looper) {
+ mEventHandler = new EventHandler(this, looper);
+ }
+ }
+ }
+ mOnExpirationUpdateListener = listener;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a drm session
+ * expiration update occurs
+ */
+ public interface OnExpirationUpdateListener
+ {
+ /**
+ * Called when a session expiration update occurs, to inform the app
+ * about the change in expiration time
+ *
+ * @param md the MediaDrm object on which the event occurred
+ * @param sessionId the DRM session ID on which the event occurred
+ * @param expirationTime the new expiration time for the keys in the session.
+ * The time is in milliseconds, relative to the Unix epoch.
+ */
+ void onExpirationUpdate(MediaDrm md, byte[] sessionId, long expirationTime);
+ }
+
+ /**
+ * Register a callback to be invoked when the state of keys in a session
+ * change, e.g. when a license update occurs or when a license expires.
+ *
+ * @param listener the callback that will be run when key status changes
+ * @param handler the handler on which the listener should be invoked, or
+ * null if the listener should be invoked on the calling thread's looper.
+ */
+ public void setOnKeysChangeListener(OnKeysChangeListener listener,
+ Handler handler)
+ {
+ if (listener != null) {
+ Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
+ if (looper != null) {
+ if (mEventHandler == null || mEventHandler.getLooper() != looper) {
+ mEventHandler = new EventHandler(this, looper);
+ }
+ }
+ }
+ mOnKeysChangeListener = listener;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the keys in a drm
+ * session change states.
+ */
+ public interface OnKeysChangeListener
+ {
+ /**
+ * Called when the keys in a session change status, such as when the license
+ * is renewed or expires.
+ *
+ * @param md the MediaDrm object on which the event occurred
+ * @param sessionId the DRM session ID on which the event occurred
+ * @param keyInformation a list of {@link MediaDrm.KeyStatus}
+ * instances indicating the status for each key in the session
+ * @param hasNewUsableKey indicates if a key has been added that is usable,
+ * which may trigger an attempt to resume playback on the media stream
+ * if it is currently blocked waiting for a key.
+ */
+ void onKeysChange(MediaDrm md, byte[] sessionId, List<KeyStatus> keyInformation,
+ boolean hasNewUsableKey);
+ }
+
+ /**
+ * The key is currently usable to decrypt media data
+ */
+ public static final int KEY_STATUS_USABLE = 0;
+
+ /**
+ * The key is no longer usable to decrypt media data because its
+ * expiration time has passed.
+ */
+ public static final int KEY_STATUS_EXPIRED = 1;
+
+ /**
+ * The key is not currently usable to decrypt media data because its
+ * output requirements cannot currently be met.
+ */
+ public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2;
+
+ /**
+ * The status of the key is not yet known and is being determined.
+ * The status will be updated with the actual status when it has
+ * been determined.
+ */
+ public static final int KEY_STATUS_PENDING = 3;
+
+ /**
+ * The key is not currently usable to decrypt media data because of an
+ * internal error in processing unrelated to input parameters. This error
+ * is not actionable by an app.
+ */
+ public static final int KEY_STATUS_INTERNAL_ERROR = 4;
+
+
+ /**
+ * Defines the status of a key.
+ * A KeyStatus for each key in a session is provided to the
+ * {@link OnKeysChangeListener#onKeysChange}
+ * listener.
+ */
+ public static final class KeyStatus {
+ private final byte[] mKeyId;
+ private final int mStatusCode;
+
+ KeyStatus(byte[] keyId, int statusCode) {
+ mKeyId = keyId;
+ mStatusCode = statusCode;
+ }
+
+ /**
+ * Returns the status code for the key
+ */
+ public int getStatusCode() { return mStatusCode; }
+
+ /**
+ * Returns the id for the key
+ */
+ public byte[] getKeyId() { return mKeyId; }
+ }
+
+ /**
* Register a callback to be invoked when an event occurs
*
* @param listener the callback that will be run
@@ -289,6 +434,8 @@ public final class MediaDrm {
public static final int EVENT_SESSION_RECLAIMED = 5;
private static final int DRM_EVENT = 200;
+ private static final int EXPIRATION_UPDATE = 201;
+ private static final int KEYS_CHANGE = 202;
private class EventHandler extends Handler
{
@@ -308,8 +455,6 @@ public final class MediaDrm {
switch(msg.what) {
case DRM_EVENT:
- Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")");
-
if (mOnEventListener != null) {
if (msg.obj != null && msg.obj instanceof Parcel) {
Parcel parcel = (Parcel)msg.obj;
@@ -321,11 +466,46 @@ public final class MediaDrm {
if (data.length == 0) {
data = null;
}
+
+ Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")");
mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data);
}
}
return;
+ case KEYS_CHANGE:
+ if (mOnKeysChangeListener != null) {
+ if (msg.obj != null && msg.obj instanceof Parcel) {
+ Parcel parcel = (Parcel)msg.obj;
+ byte[] sessionId = parcel.createByteArray();
+ if (sessionId.length > 0) {
+ List<KeyStatus> keyStatusList = keyStatusListFromParcel(parcel);
+ boolean hasNewUsableKey = (parcel.readInt() != 0);
+
+ Log.i(TAG, "Drm keys change");
+ mOnKeysChangeListener.onKeysChange(mMediaDrm, sessionId, keyStatusList,
+ hasNewUsableKey);
+ }
+ }
+ }
+ return;
+
+ case EXPIRATION_UPDATE:
+ if (mOnExpirationUpdateListener != null) {
+ if (msg.obj != null && msg.obj instanceof Parcel) {
+ Parcel parcel = (Parcel)msg.obj;
+ byte[] sessionId = parcel.createByteArray();
+ if (sessionId.length > 0) {
+ long expirationTime = parcel.readLong();
+
+ Log.i(TAG, "Drm key expiration update: " + expirationTime);
+ mOnExpirationUpdateListener.onExpirationUpdate(mMediaDrm, sessionId,
+ expirationTime);
+ }
+ }
+ }
+ return;
+
default:
Log.e(TAG, "Unknown message type " + msg.what);
return;
@@ -333,7 +513,21 @@ public final class MediaDrm {
}
}
- /*
+ /**
+ * Parse a list of KeyStatus objects from an event parcel
+ */
+ private List<KeyStatus> keyStatusListFromParcel(Parcel parcel) {
+ int nelems = parcel.readInt();
+ List<KeyStatus> keyStatusList = new ArrayList(nelems);
+ while (nelems-- > 0) {
+ byte[] keyId = parcel.createByteArray();
+ int keyStatusCode = parcel.readInt();
+ keyStatusList.add(new KeyStatus(keyId, keyStatusCode));
+ }
+ return keyStatusList;
+ }
+
+ /**
* This method is called from native code when an event occurs. This method
* just uses the EventHandler system to post the event back to the main app thread.
* We use a weak reference to the original MediaPlayer object so that the native
@@ -341,14 +535,14 @@ public final class MediaDrm {
* the cookie passed to native_setup().)
*/
private static void postEventFromNative(Object mediadrm_ref,
- int eventType, int extra, Object obj)
+ int what, int eventType, int extra, Object obj)
{
- MediaDrm md = (MediaDrm)((WeakReference)mediadrm_ref).get();
+ MediaDrm md = (MediaDrm)((WeakReference<MediaDrm>)mediadrm_ref).get();
if (md == null) {
return;
}
if (md.mEventHandler != null) {
- Message m = md.mEventHandler.obtainMessage(DRM_EVENT, eventType, extra, obj);
+ Message m = md.mEventHandler.obtainMessage(what, eventType, extra, obj);
md.mEventHandler.sendMessage(m);
}
}
@@ -404,7 +598,7 @@ public final class MediaDrm {
/**
* Contains the opaque data an app uses to request keys from a license server
*/
- public final static class KeyRequest {
+ public static final class KeyRequest {
private byte[] mData;
private String mDefaultUrl;
private int mRequestType;
@@ -521,7 +715,7 @@ public final class MediaDrm {
* Contains the opaque data an app uses to request a certificate from a provisioning
* server
*/
- public final static class ProvisionRequest {
+ public static final class ProvisionRequest {
ProvisionRequest() {}
/**
@@ -812,7 +1006,7 @@ public final class MediaDrm {
*
* @hide - not part of the public API at this time
*/
- public final static class CertificateRequest {
+ public static final class CertificateRequest {
private byte[] mData;
private String mDefaultUrl;
@@ -860,7 +1054,7 @@ public final class MediaDrm {
*
* @hide - not part of the public API at this time
*/
- public final static class Certificate {
+ public static final class Certificate {
Certificate() {}
/**
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 0c1c7e9..726622f 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -439,6 +439,22 @@ public final class MediaFormat {
public static final String KEY_PRIORITY = "priority";
/**
+ * A key describing the desired operating frame rate for video or sample rate for audio
+ * that the codec will need to operate at.
+ * <p>
+ * The associated value is an integer or a float representing frames-per-second or
+ * samples-per-second
+ * <p>
+ * This is used for cases like high-speed/slow-motion video capture, where the video encoder
+ * format contains the target playback rate (e.g. 30fps), but the component must be able to
+ * handle the high operating capture rate (e.g. 240fps).
+ * <p>
+ * This rate will be used by codec for resource planning and setting the operating points.
+ *
+ */
+ public static final String KEY_OPERATING_RATE = "operating-rate";
+
+ /**
* A key describing the desired profile to be used by an encoder.
* Constants are declared in {@link MediaCodecInfo.CodecProfileLevel}.
* This key is only supported for codecs that specify a profile.
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 9a69c06..9aa8003 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -498,5 +498,11 @@ public class MediaMetadataRetriever
* The video rotation angle may be 0, 90, 180, or 270 degrees.
*/
public static final int METADATA_KEY_VIDEO_ROTATION = 24;
+ /**
+ * This key retrieves the original capture framerate, if it's
+ * available. The capture framerate will be a floating point
+ * number.
+ */
+ public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25;
// Add more here...
}
diff --git a/media/java/android/media/MediaSync.java b/media/java/android/media/MediaSync.java
index e87647d..7350bd4 100644
--- a/media/java/android/media/MediaSync.java
+++ b/media/java/android/media/MediaSync.java
@@ -17,6 +17,7 @@
package android.media;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.media.AudioTrack;
import android.os.Handler;
import android.os.Looper;
@@ -386,6 +387,37 @@ final public class MediaSync {
return (mode == PLAYBACK_RATE_AUDIO_MODE_RESAMPLE);
}
+ /**
+ * Get current playback position.
+ * <p>
+ * The MediaTimestamp represents a clock ticking during media playback. It's represented
+ * by an anchor frame ({@link MediaTimestamp#mediaTimeUs} and {@link MediaTimestamp#nanoTime})
+ * and clock speed ({@link MediaTimestamp#clockRate}). For continous playback with
+ * constant speed, its anchor frame doesn't change that often. Thereafter, it's recommended
+ * to not call this method often.
+ * <p>
+ * To help users to get current playback position, this method always returns the timestamp of
+ * just-rendered frame, i.e., {@link System#nanoTime} and its corresponding media time. They
+ * can be used as current playback position.
+ *
+ * @param timestamp a reference to a non-null MediaTimestamp instance allocated
+ * and owned by caller.
+ * @return true if a timestamp is available, or false if no timestamp is available.
+ * If a timestamp if available, the MediaTimestamp instance is filled in with
+ * playback rate, together with the current media timestamp and the system nanoTime
+ * corresponding to the measured media timestamp.
+ * In the case that no timestamp is available, any supplied instance is left unaltered.
+ */
+ public boolean getTimestamp(@NonNull MediaTimestamp timestamp)
+ {
+ if (timestamp == null) {
+ throw new IllegalArgumentException();
+ }
+ return native_getTimestamp(timestamp);
+ }
+
+ private native final boolean native_getTimestamp(MediaTimestamp timestamp);
+
/**
* Queues the audio data asynchronously for playback (AudioTrack must be in streaming mode).
* @param audioData the buffer that holds the data to play. This buffer will be returned
diff --git a/media/java/android/media/MediaTimestamp.java b/media/java/android/media/MediaTimestamp.java
new file mode 100644
index 0000000..4b7e827
--- /dev/null
+++ b/media/java/android/media/MediaTimestamp.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * Structure that groups clock rate of the stream playback, together with the media timestamp
+ * of an anchor frame and the system time when that frame was presented or is committed
+ * to be presented.
+ * The "present" means that audio/video produced on device is detectable by an external
+ * observer off device.
+ * The time is based on the implementation's best effort, using whatever knowledge
+ * is available to the system, but cannot account for any delay unknown to the implementation.
+ * The anchor frame could be any frame, including just-rendered frame, dependent on how
+ * it's selected. When the anchor frame is the just-rendered one, the media time stands for
+ * current position of the playback.
+ *
+ * @see MediaSync#getTimestamp
+ */
+public final class MediaTimestamp
+{
+ /**
+ * Media timestamp in microseconds.
+ */
+ public long mediaTimeUs;
+
+ /**
+ * The {@link java.lang.System#nanoTime} corresponding to the media timestamp.
+ */
+ public long nanoTime;
+
+ /**
+ * Media clock rate.
+ * It is 1.0 if media clock is in sync with the system clock;
+ * greater than 1.0 if media clock is faster than the system clock;
+ * less than 1.0 if media clock is slower than the system clock.
+ */
+ public float clockRate;
+}
diff --git a/media/java/android/media/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl
index 642078a..96d12fd 100644
--- a/media/java/android/media/midi/IMidiDeviceServer.aidl
+++ b/media/java/android/media/midi/IMidiDeviceServer.aidl
@@ -16,6 +16,7 @@
package android.media.midi;
+import android.media.midi.MidiDeviceInfo;
import android.os.ParcelFileDescriptor;
/** @hide */
@@ -27,4 +28,6 @@ interface IMidiDeviceServer
// connects the input port pfd to the specified output port
void connectPorts(IBinder token, in ParcelFileDescriptor pfd, int outputPortNumber);
+
+ MidiDeviceInfo getDeviceInfo();
}
diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java
index 7201e25..af108eb 100644
--- a/media/java/android/media/midi/MidiDeviceInfo.java
+++ b/media/java/android/media/midi/MidiDeviceInfo.java
@@ -237,29 +237,23 @@ public final class MidiDeviceInfo implements Parcelable {
}
/**
- * Returns information about an input port.
+ * Returns information about the device's ports.
+ * The ports are in unspecified order.
*
- * @param portNumber the number of the input port
- * @return the input port's information object
+ * @return array of {@link PortInfo}
*/
- public PortInfo getInputPortInfo(int portNumber) {
- if (portNumber < 0 || portNumber >= mInputPortCount) {
- throw new IllegalArgumentException("portNumber out of range");
- }
- return new PortInfo(PortInfo.TYPE_INPUT, portNumber, mInputPortNames[portNumber]);
- }
+ public PortInfo[] getPortList() {
+ PortInfo[] portInfoList = new PortInfo[mInputPortCount + mOutputPortCount];
- /**
- * Returns information about an output port.
- *
- * @param portNumber the number of the output port
- * @return the output port's information object
- */
- public PortInfo getOutputPortInfo(int portNumber) {
- if (portNumber < 0 || portNumber >= mOutputPortCount) {
- throw new IllegalArgumentException("portNumber out of range");
+ int index = 0;
+ for (int i = 0; i < mInputPortCount; i++) {
+ portInfoList[index++] = new PortInfo(PortInfo.TYPE_INPUT, i, mInputPortNames[i]);
+ }
+ for (int i = 0; i < mOutputPortCount; i++) {
+ portInfoList[index++] = new PortInfo(PortInfo.TYPE_OUTPUT, i, mOutputPortNames[i]);
}
- return new PortInfo(PortInfo.TYPE_OUTPUT, portNumber, mOutputPortNames[portNumber]);
+
+ return portInfoList;
}
/**
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index bc85f92..a316a44 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -252,6 +252,11 @@ public final class MidiDeviceServer implements Closeable {
mPortClients.put(token, client);
}
}
+
+ @Override
+ public MidiDeviceInfo getDeviceInfo() {
+ return mDeviceInfo;
+ }
};
/* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
@@ -279,6 +284,10 @@ public final class MidiDeviceServer implements Closeable {
return mServer;
}
+ public IBinder asBinder() {
+ return mServer.asBinder();
+ }
+
/* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) {
if (mDeviceInfo != null) {
throw new IllegalStateException("setDeviceInfo should only be called once");
diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java
index 8b1de3e..ce12a4f 100644
--- a/media/java/android/media/midi/MidiDeviceService.java
+++ b/media/java/android/media/midi/MidiDeviceService.java
@@ -91,7 +91,7 @@ abstract public class MidiDeviceService extends Service {
/**
* Returns an array of {@link MidiReceiver} for the device's input ports.
* Subclasses must override this to provide the receivers which will receive
- * data sent to the device's input ports. An empty array or null should be returned if
+ * data sent to the device's input ports. An empty array should be returned if
* the device has no input ports.
* @return array of MidiReceivers
*/
diff --git a/media/java/android/media/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java
index 1d3b37a..ff16a57 100644
--- a/media/java/android/media/midi/MidiInputPort.java
+++ b/media/java/android/media/midi/MidiInputPort.java
@@ -83,7 +83,18 @@ public final class MidiInputPort extends MidiReceiver implements Closeable {
if (mOutputStream == null) {
throw new IOException("MidiInputPort is closed");
}
- int length = MidiPortImpl.packMessage(msg, offset, count, timestamp, mBuffer);
+ int length = MidiPortImpl.packData(msg, offset, count, timestamp, mBuffer);
+ mOutputStream.write(mBuffer, 0, length);
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ synchronized (mBuffer) {
+ if (mOutputStream == null) {
+ throw new IOException("MidiInputPort is closed");
+ }
+ int length = MidiPortImpl.packFlush(mBuffer);
mOutputStream.write(mBuffer, 0, length);
}
}
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index d62b2dc..0ba1744 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -16,6 +16,7 @@
package android.media.midi;
+import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -42,6 +43,24 @@ import java.util.HashMap;
public final class MidiManager {
private static final String TAG = "MidiManager";
+ /**
+ * Intent for starting BluetoothMidiService
+ * @hide
+ */
+ public static final String BLUETOOTH_MIDI_SERVICE_INTENT =
+ "android.media.midi.BluetoothMidiService";
+
+ /**
+ * BluetoothMidiService package name
+ */
+ private static final String BLUETOOTH_MIDI_SERVICE_PACKAGE = "com.android.bluetoothmidiservice";
+
+ /**
+ * BluetoothMidiService class name
+ */
+ private static final String BLUETOOTH_MIDI_SERVICE_CLASS =
+ "com.android.bluetoothmidiservice.BluetoothMidiService";
+
private final Context mContext;
private final IMidiManager mService;
private final IBinder mToken = new Binder();
@@ -145,6 +164,19 @@ public final class MidiManager {
}
/**
+ * Callback class used for receiving the results of {@link #openBluetoothDevice}
+ */
+ abstract public static class BluetoothOpenCallback {
+ /**
+ * Called to respond to a {@link #openBluetoothDevice} request
+ *
+ * @param bluetoothDevice the {@link android.bluetooth.BluetoothDevice} to open
+ * @param device a {@link MidiDevice} for opened device, or null if opening failed
+ */
+ abstract public void onDeviceOpened(BluetoothDevice bluetoothDevice, MidiDevice device);
+ }
+
+ /**
* @hide
*/
public MidiManager(Context context, IMidiManager service) {
@@ -214,6 +246,19 @@ public final class MidiManager {
}
}
+ private void sendBluetoothDeviceResponse(final BluetoothDevice bluetoothDevice,
+ final MidiDevice device, final BluetoothOpenCallback callback, Handler handler) {
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override public void run() {
+ callback.onDeviceOpened(bluetoothDevice, device);
+ }
+ });
+ } else {
+ callback.onDeviceOpened(bluetoothDevice, device);
+ }
+ }
+
/**
* Opens a MIDI device for reading and writing.
*
@@ -260,7 +305,7 @@ public final class MidiManager {
// return immediately to avoid calling sendOpenDeviceResponse below
return;
} else {
- Log.e(TAG, "Unable to bind service: " + intent);
+ Log.e(TAG, "Unable to bind service: " + intent);
}
}
} else {
@@ -272,6 +317,51 @@ public final class MidiManager {
sendOpenDeviceResponse(deviceInfo, device, callback, handler);
}
+ /**
+ * Opens a Bluetooth MIDI device for reading and writing.
+ *
+ * @param bluetoothDevice a {@link android.bluetooth.BluetoothDevice} to open as a MIDI device
+ * @param callback a {@link MidiManager.BluetoothOpenCallback} to be called to receive the
+ * result
+ * @param handler the {@link android.os.Handler Handler} that will be used for delivering
+ * the result. If handler is null, then the thread used for the
+ * callback is unspecified.
+ */
+ public void openBluetoothDevice(final BluetoothDevice bluetoothDevice,
+ final BluetoothOpenCallback callback, final Handler handler) {
+ Intent intent = new Intent(BLUETOOTH_MIDI_SERVICE_INTENT);
+ intent.setComponent(new ComponentName(BLUETOOTH_MIDI_SERVICE_PACKAGE,
+ BLUETOOTH_MIDI_SERVICE_CLASS));
+ intent.putExtra("device", bluetoothDevice);
+ if (!mContext.bindService(intent,
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ IMidiDeviceServer server =
+ IMidiDeviceServer.Stub.asInterface(binder);
+ try {
+ // fetch MidiDeviceInfo from the server
+ MidiDeviceInfo deviceInfo = server.getDeviceInfo();
+ MidiDevice device = new MidiDevice(deviceInfo, server, mContext, this);
+ sendBluetoothDeviceResponse(bluetoothDevice, device, callback, handler);
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception in onServiceConnected");
+ sendBluetoothDeviceResponse(bluetoothDevice, null, callback, handler);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // FIXME - anything to do here?
+ }
+ },
+ Context.BIND_AUTO_CREATE))
+ {
+ Log.e(TAG, "Unable to bind service: " + intent);
+ sendBluetoothDeviceResponse(bluetoothDevice, null, callback, handler);
+ }
+ }
+
/** @hide */
public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java
index 0290a76..7491f3c 100644
--- a/media/java/android/media/midi/MidiOutputPort.java
+++ b/media/java/android/media/midi/MidiOutputPort.java
@@ -62,12 +62,24 @@ public final class MidiOutputPort extends MidiSender implements Closeable {
// FIXME - inform receivers here?
}
- int offset = MidiPortImpl.getMessageOffset(buffer, count);
- int size = MidiPortImpl.getMessageSize(buffer, count);
- long timestamp = MidiPortImpl.getMessageTimeStamp(buffer, count);
-
- // dispatch to all our receivers
- mDispatcher.sendWithTimestamp(buffer, offset, size, timestamp);
+ int packetType = MidiPortImpl.getPacketType(buffer, count);
+ switch (packetType) {
+ case MidiPortImpl.PACKET_TYPE_DATA: {
+ int offset = MidiPortImpl.getDataOffset(buffer, count);
+ int size = MidiPortImpl.getDataSize(buffer, count);
+ long timestamp = MidiPortImpl.getPacketTimestamp(buffer, count);
+
+ // dispatch to all our receivers
+ mDispatcher.sendWithTimestamp(buffer, offset, size, timestamp);
+ break;
+ }
+ case MidiPortImpl.PACKET_TYPE_FLUSH:
+ mDispatcher.flush();
+ break;
+ default:
+ Log.e(TAG, "Unknown packet type " + packetType);
+ break;
+ }
}
} catch (IOException e) {
// FIXME report I/O failure?
diff --git a/media/java/android/media/midi/MidiPortImpl.java b/media/java/android/media/midi/MidiPortImpl.java
index 5795045..16fc214 100644
--- a/media/java/android/media/midi/MidiPortImpl.java
+++ b/media/java/android/media/midi/MidiPortImpl.java
@@ -24,6 +24,16 @@ package android.media.midi;
private static final String TAG = "MidiPort";
/**
+ * Packet type for data packet
+ */
+ public static final int PACKET_TYPE_DATA = 1;
+
+ /**
+ * Packet type for flush packet
+ */
+ public static final int PACKET_TYPE_FLUSH = 2;
+
+ /**
* Maximum size of a packet that can pass through our ParcelFileDescriptor.
*/
public static final int MAX_PACKET_SIZE = 1024;
@@ -34,12 +44,17 @@ package android.media.midi;
private static final int TIMESTAMP_SIZE = 8;
/**
+ * Data packet overhead is timestamp size plus packet type byte
+ */
+ private static final int DATA_PACKET_OVERHEAD = TIMESTAMP_SIZE + 1;
+
+ /**
* Maximum amount of MIDI data that can be included in a packet
*/
- public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - TIMESTAMP_SIZE;
+ public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - DATA_PACKET_OVERHEAD;
/**
- * Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor
+ * Utility function for packing MIDI data to be sent through our ParcelFileDescriptor
*
* message byte array contains variable length MIDI message.
* messageSize is size of variable length MIDI message
@@ -47,46 +62,65 @@ package android.media.midi;
* dest is buffer to pack into
* returns size of packed message
*/
- public static int packMessage(byte[] message, int offset, int size, long timestamp,
+ public static int packData(byte[] message, int offset, int size, long timestamp,
byte[] dest) {
- if (size + TIMESTAMP_SIZE > MAX_PACKET_SIZE) {
- size = MAX_PACKET_SIZE - TIMESTAMP_SIZE;
+ if (size > MAX_PACKET_DATA_SIZE) {
+ size = MAX_PACKET_DATA_SIZE;
}
- // message data goes first
- System.arraycopy(message, offset, dest, 0, size);
+ int length = 0;
+ // packet type goes first
+ dest[length++] = PACKET_TYPE_DATA;
+ // data goes next
+ System.arraycopy(message, offset, dest, length, size);
+ length += size;
// followed by timestamp
for (int i = 0; i < TIMESTAMP_SIZE; i++) {
- dest[size++] = (byte)timestamp;
+ dest[length++] = (byte)timestamp;
timestamp >>= 8;
}
- return size;
+ return length;
+ }
+
+ /**
+ * Utility function for packing a flush command to be sent through our ParcelFileDescriptor
+ */
+ public static int packFlush(byte[] dest) {
+ dest[0] = PACKET_TYPE_FLUSH;
+ return 1;
+ }
+
+ /**
+ * Returns the packet type (PACKET_TYPE_DATA or PACKET_TYPE_FLUSH)
+ */
+ public static int getPacketType(byte[] buffer, int bufferLength) {
+ return buffer[0];
}
/**
- * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
* returns the offset of the MIDI message in packed buffer
*/
- public static int getMessageOffset(byte[] buffer, int bufferLength) {
- // message is at the beginning
- return 0;
+ public static int getDataOffset(byte[] buffer, int bufferLength) {
+ // data follows packet type byte
+ return 1;
}
/**
- * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
* returns size of MIDI data in packed buffer
*/
- public static int getMessageSize(byte[] buffer, int bufferLength) {
+ public static int getDataSize(byte[] buffer, int bufferLength) {
// message length is total buffer length minus size of the timestamp
- return bufferLength - TIMESTAMP_SIZE;
+ return bufferLength - DATA_PACKET_OVERHEAD;
}
/**
- * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
* unpacks timestamp from packed buffer
*/
- public static long getMessageTimeStamp(byte[] buffer, int bufferLength) {
+ public static long getPacketTimestamp(byte[] buffer, int bufferLength) {
// timestamp is at end of the packet
int offset = bufferLength;
long timestamp = 0;
diff --git a/media/java/android/media/midi/MidiReceiver.java b/media/java/android/media/midi/MidiReceiver.java
index 6f4c266..d069075 100644
--- a/media/java/android/media/midi/MidiReceiver.java
+++ b/media/java/android/media/midi/MidiReceiver.java
@@ -42,6 +42,13 @@ abstract public class MidiReceiver {
throws IOException;
/**
+ * Instructs the receiver to discard all pending events.
+ * @throws IOException
+ */
+ public void flush() throws IOException {
+ }
+
+ /**
* Returns the maximum size of a message this receiver can receive.
* Defaults to {@link java.lang.Integer#MAX_VALUE} unless overridden.
* @return maximum message size
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 16758d0..71457b7 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -669,6 +669,14 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
break;
}
+ case MediaCodec::CB_CODEC_RELEASED:
+ {
+ if (!msg->findInt32("reason", &arg2)) {
+ arg2 = MediaCodec::REASON_UNKNOWN;
+ }
+ break;
+ }
+
default:
TRESPASS();
}
diff --git a/media/jni/android_media_MediaCrypto.cpp b/media/jni/android_media_MediaCrypto.cpp
index d2216fb..a9accb0 100644
--- a/media/jni/android_media_MediaCrypto.cpp
+++ b/media/jni/android_media_MediaCrypto.cpp
@@ -140,6 +140,15 @@ sp<ICrypto> JCrypto::GetCrypto(JNIEnv *env, jobject obj) {
return jcrypto->mCrypto;
}
+// JNI conversion utilities
+static Vector<uint8_t> JByteArrayToVector(JNIEnv *env, jbyteArray const &byteArray) {
+ Vector<uint8_t> vector;
+ size_t length = env->GetArrayLength(byteArray);
+ vector.insertAt((size_t)0, length);
+ env->GetByteArrayRegion(byteArray, 0, length, (jbyte *)vector.editArray());
+ return vector;
+}
+
} // namespace android
using namespace android;
@@ -274,6 +283,37 @@ static jboolean android_media_MediaCrypto_requiresSecureDecoderComponent(
return result ? JNI_TRUE : JNI_FALSE;
}
+static void android_media_MediaCrypto_setMediaDrmSession(
+ JNIEnv *env, jobject thiz, jbyteArray jsessionId) {
+ if (jsessionId == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ sp<ICrypto> crypto = JCrypto::GetCrypto(env, thiz);
+
+ if (crypto == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId));
+
+ status_t err = crypto->setMediaDrmSession(sessionId);
+
+ String8 msg("setMediaDrmSession failed");
+ if (err == ERROR_DRM_SESSION_NOT_OPENED) {
+ msg += ": session not opened";
+ } else if (err == ERROR_UNSUPPORTED) {
+ msg += ": not supported by this crypto scheme";
+ } else if (err == NO_INIT) {
+ msg += ": crypto plugin not initialized";
+ } else if (err != OK) {
+ msg.appendFormat(": general failure (%d)", err);
+ }
+ jniThrowException(env, "android/media/MediaCryptoException", msg.string());
+}
+
static JNINativeMethod gMethods[] = {
{ "release", "()V", (void *)android_media_MediaCrypto_release },
{ "native_init", "()V", (void *)android_media_MediaCrypto_native_init },
@@ -289,6 +329,9 @@ static JNINativeMethod gMethods[] = {
{ "requiresSecureDecoderComponent", "(Ljava/lang/String;)Z",
(void *)android_media_MediaCrypto_requiresSecureDecoderComponent },
+
+ { "setMediaDrmSession", "([B)V",
+ (void *)android_media_MediaCrypto_setMediaDrmSession },
};
int register_android_media_Crypto(JNIEnv *env) {
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 96d7133..f8146a7 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -96,6 +96,12 @@ struct EventTypes {
jint kEventSessionReclaimed;
} gEventTypes;
+struct EventWhat {
+ jint kWhatDrmEvent;
+ jint kWhatExpirationUpdate;
+ jint kWhatKeysChange;
+} gEventWhat;
+
struct KeyTypes {
jint kKeyTypeStreaming;
jint kKeyTypeOffline;
@@ -186,25 +192,37 @@ JNIDrmListener::~JNIDrmListener()
void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra,
const Parcel *obj)
{
- jint jeventType;
+ jint jwhat;
+ jint jeventType = 0;
// translate DrmPlugin event types into their java equivalents
switch (eventType) {
case DrmPlugin::kDrmPluginEventProvisionRequired:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventProvisionRequired;
break;
case DrmPlugin::kDrmPluginEventKeyNeeded:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventKeyRequired;
break;
case DrmPlugin::kDrmPluginEventKeyExpired:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventKeyExpired;
break;
case DrmPlugin::kDrmPluginEventVendorDefined:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventVendorDefined;
break;
case DrmPlugin::kDrmPluginEventSessionReclaimed:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventSessionReclaimed;
break;
+ case DrmPlugin::kDrmPluginEventExpirationUpdate:
+ jwhat = gEventWhat.kWhatExpirationUpdate;
+ break;
+ case DrmPlugin::kDrmPluginEventKeysChange:
+ jwhat = gEventWhat.kWhatKeysChange;
+ break;
default:
ALOGE("Invalid event DrmPlugin::EventType %d, ignored", (int)eventType);
return;
@@ -217,7 +235,7 @@ void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra,
Parcel* nativeParcel = parcelForJavaObject(env, jParcel);
nativeParcel->setData(obj->data(), obj->dataSize());
env->CallStaticVoidMethod(mClass, gFields.post_event, mObject,
- jeventType, extra, jParcel);
+ jwhat, jeventType, extra, jParcel);
env->DeleteLocalRef(jParcel);
}
}
@@ -573,7 +591,7 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) {
FIND_CLASS(clazz, "android/media/MediaDrm");
GET_FIELD_ID(gFields.context, clazz, "mNativeContext", "J");
GET_STATIC_METHOD_ID(gFields.post_event, clazz, "postEventFromNative",
- "(Ljava/lang/Object;IILjava/lang/Object;)V");
+ "(Ljava/lang/Object;IIILjava/lang/Object;)V");
jfieldID field;
GET_STATIC_FIELD_ID(field, clazz, "EVENT_PROVISION_REQUIRED", "I");
@@ -587,6 +605,13 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) {
GET_STATIC_FIELD_ID(field, clazz, "EVENT_SESSION_RECLAIMED", "I");
gEventTypes.kEventSessionReclaimed = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "DRM_EVENT", "I");
+ gEventWhat.kWhatDrmEvent = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "EXPIRATION_UPDATE", "I");
+ gEventWhat.kWhatExpirationUpdate = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "KEYS_CHANGE", "I");
+ gEventWhat.kWhatKeysChange = env->GetStaticIntField(clazz, field);
+
GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_STREAMING", "I");
gKeyTypes.kKeyTypeStreaming = env->GetStaticIntField(clazz, field);
GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_OFFLINE", "I");
@@ -837,7 +862,7 @@ static jobject android_media_MediaDrm_getKeyRequest(
env->SetIntField(keyObj, gFields.keyRequest.requestType,
gKeyRequestTypes.kKeyRequestTypeRelease);
break;
- case DrmPlugin::kKeyRequestType_Unknown:
+ default:
throwStateException(env, "DRM plugin failure: unknown key request type",
ERROR_DRM_UNKNOWN);
break;
diff --git a/media/jni/android_media_MediaSync.cpp b/media/jni/android_media_MediaSync.cpp
index f31b511..b96c733 100644
--- a/media/jni/android_media_MediaSync.cpp
+++ b/media/jni/android_media_MediaSync.cpp
@@ -29,6 +29,7 @@
#include <gui/Surface.h>
#include <media/AudioTrack.h>
+#include <media/stagefright/MediaClock.h>
#include <media/stagefright/MediaSync.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AString.h>
@@ -39,6 +40,9 @@ namespace android {
struct fields_t {
jfieldID context;
+ jfieldID mediaTimestampMediaTimeUsID;
+ jfieldID mediaTimestampNanoTimeID;
+ jfieldID mediaTimestampClockRateID;
};
static fields_t gFields;
@@ -71,6 +75,10 @@ void JMediaSync::setPlaybackRate(float rate) {
mSync->setPlaybackRate(rate);
}
+sp<const MediaClock> JMediaSync::getMediaClock() {
+ return mSync->getMediaClock();
+}
+
status_t JMediaSync::updateQueuedAudioData(
int sizeInBytes, int64_t presentationTimeUs) {
return mSync->updateQueuedAudioData(sizeInBytes, presentationTimeUs);
@@ -222,12 +230,55 @@ static void android_media_MediaSync_native_updateQueuedAudioData(
}
}
+static jboolean android_media_MediaSync_native_getTimestamp(
+ JNIEnv *env, jobject thiz, jobject timestamp) {
+ sp<JMediaSync> sync = getMediaSync(env, thiz);
+ if (sync == NULL) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return JNI_FALSE;
+ }
+
+ sp<const MediaClock> mediaClock = sync->getMediaClock();
+ if (mediaClock == NULL) {
+ return JNI_FALSE;
+ }
+
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t mediaUs = 0;
+ if (mediaClock->getMediaTime(nowUs, &mediaUs) != OK) {
+ return JNI_FALSE;
+ }
+
+ env->SetLongField(timestamp, gFields.mediaTimestampMediaTimeUsID,
+ (jlong)mediaUs);
+ env->SetLongField(timestamp, gFields.mediaTimestampNanoTimeID,
+ (jlong)(nowUs * 1000));
+ env->SetFloatField(timestamp, gFields.mediaTimestampClockRateID,
+ (jfloat)mediaClock->getPlaybackRate());
+ return JNI_TRUE;
+}
+
static void android_media_MediaSync_native_init(JNIEnv *env) {
ScopedLocalRef<jclass> clazz(env, env->FindClass("android/media/MediaSync"));
CHECK(clazz.get() != NULL);
gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J");
CHECK(gFields.context != NULL);
+
+ clazz.reset(env->FindClass("android/media/MediaTimestamp"));
+ CHECK(clazz.get() != NULL);
+
+ gFields.mediaTimestampMediaTimeUsID =
+ env->GetFieldID(clazz.get(), "mediaTimeUs", "J");
+ CHECK(gFields.mediaTimestampMediaTimeUsID != NULL);
+
+ gFields.mediaTimestampNanoTimeID =
+ env->GetFieldID(clazz.get(), "nanoTime", "J");
+ CHECK(gFields.mediaTimestampNanoTimeID != NULL);
+
+ gFields.mediaTimestampClockRateID =
+ env->GetFieldID(clazz.get(), "clockRate", "F");
+ CHECK(gFields.mediaTimestampClockRateID != NULL);
}
static void android_media_MediaSync_native_setup(JNIEnv *env, jobject thiz) {
@@ -267,6 +318,10 @@ static JNINativeMethod gMethods[] = {
"(IJ)V",
(void *)android_media_MediaSync_native_updateQueuedAudioData },
+ { "native_getTimestamp",
+ "(Landroid/media/MediaTimestamp;)Z",
+ (void *)android_media_MediaSync_native_getTimestamp },
+
{ "native_init", "()V", (void *)android_media_MediaSync_native_init },
{ "native_setup", "()V", (void *)android_media_MediaSync_native_setup },
diff --git a/media/jni/android_media_MediaSync.h b/media/jni/android_media_MediaSync.h
index 5750083..976a456 100644
--- a/media/jni/android_media_MediaSync.h
+++ b/media/jni/android_media_MediaSync.h
@@ -25,6 +25,7 @@ namespace android {
class AudioTrack;
struct IGraphicBufferProducer;
+struct MediaClock;
class MediaSync;
struct JMediaSync : public RefBase {
@@ -40,6 +41,8 @@ struct JMediaSync : public RefBase {
void setPlaybackRate(float rate);
+ sp<const MediaClock> getMediaClock();
+
protected:
virtual ~JMediaSync();
diff --git a/media/packages/BluetoothMidiService/Android.mk b/media/packages/BluetoothMidiService/Android.mk
new file mode 100644
index 0000000..2c9c3c5
--- /dev/null
+++ b/media/packages/BluetoothMidiService/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := BluetoothMidiService
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml
new file mode 100644
index 0000000..15aa581
--- /dev/null
+++ b/media/packages/BluetoothMidiService/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.bluetoothmidiservice"
+ >
+
+ <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
+ <uses-feature android:name="android.software.midi" android:required="true"/>
+ <uses-permission android:name="android.permission.BLUETOOTH"/>
+
+ <application
+ android:label="@string/app_name">
+ <service android:name="BluetoothMidiService">
+ <intent-filter>
+ <action android:name="android.media.midi.BluetoothMidiService" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/media/packages/BluetoothMidiService/res/values/strings.xml b/media/packages/BluetoothMidiService/res/values/strings.xml
new file mode 100644
index 0000000..c98e56c
--- /dev/null
+++ b/media/packages/BluetoothMidiService/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="app_name">Bluetooth MIDI Service</string>
+</resources>
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
new file mode 100644
index 0000000..8d194e5
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -0,0 +1,276 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.media.midi.MidiReceiver;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiDeviceServer;
+import android.media.midi.MidiDeviceInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.internal.midi.MidiEventScheduler;
+import com.android.internal.midi.MidiEventScheduler.MidiEvent;
+
+import libcore.io.IoUtils;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Class used to implement a Bluetooth MIDI device.
+ */
+public final class BluetoothMidiDevice {
+
+ private static final String TAG = "BluetoothMidiDevice";
+
+ private static final int MAX_PACKET_SIZE = 20;
+
+ // Bluetooth MIDI Gatt service UUID
+ private static final UUID MIDI_SERVICE = UUID.fromString(
+ "03B80E5A-EDE8-4B33-A751-6CE34EC4C700");
+ // Bluetooth MIDI Gatt characteristic UUID
+ private static final UUID MIDI_CHARACTERISTIC = UUID.fromString(
+ "7772E5DB-3868-4112-A1A9-F2669D106BF3");
+ // Descriptor UUID for enabling characteristic changed notifications
+ private static final UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString(
+ "00002902-0000-1000-8000-00805f9b34fb");
+
+ private final BluetoothDevice mBluetoothDevice;
+ private final BluetoothMidiService mService;
+ private final MidiManager mMidiManager;
+ private MidiReceiver mOutputReceiver;
+ private final MidiEventScheduler mEventScheduler = new MidiEventScheduler();
+
+ private MidiDeviceServer mDeviceServer;
+ private BluetoothGatt mBluetoothGatt;
+
+ private BluetoothGattCharacteristic mCharacteristic;
+
+ // PacketReceiver for receiving formatted packets from our BluetoothPacketEncoder
+ private final PacketReceiver mPacketReceiver = new PacketReceiver();
+
+ private final BluetoothPacketEncoder mPacketEncoder
+ = new BluetoothPacketEncoder(mPacketReceiver, MAX_PACKET_SIZE);
+
+ private final BluetoothPacketDecoder mPacketDecoder
+ = new BluetoothPacketDecoder(MAX_PACKET_SIZE);
+
+ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status,
+ int newState) {
+ String intentAction;
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ Log.i(TAG, "Connected to GATT server.");
+ Log.i(TAG, "Attempting to start service discovery:" +
+ mBluetoothGatt.discoverServices());
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ Log.i(TAG, "Disconnected from GATT server.");
+ // FIXME synchronize?
+ close();
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ List<BluetoothGattService> services = mBluetoothGatt.getServices();
+ for (BluetoothGattService service : services) {
+ if (MIDI_SERVICE.equals(service.getUuid())) {
+ Log.d(TAG, "found MIDI_SERVICE");
+ List<BluetoothGattCharacteristic> characteristics
+ = service.getCharacteristics();
+ for (BluetoothGattCharacteristic characteristic : characteristics) {
+ if (MIDI_CHARACTERISTIC.equals(characteristic.getUuid())) {
+ Log.d(TAG, "found MIDI_CHARACTERISTIC");
+ mCharacteristic = characteristic;
+
+ // Specification says to read the characteristic first and then
+ // switch to receiving notifications
+ mBluetoothGatt.readCharacteristic(characteristic);
+ break;
+ }
+ }
+ break;
+ }
+ }
+ } else {
+ Log.w(TAG, "onServicesDiscovered received: " + status);
+ // FIXME - report error back to client?
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic,
+ int status) {
+ Log.d(TAG, "onCharacteristicRead " + status);
+
+ // switch to receiving notifications after initial characteristic read
+ mBluetoothGatt.setCharacteristicNotification(characteristic, true);
+
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+ CLIENT_CHARACTERISTIC_CONFIG);
+ // FIXME null check
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ mBluetoothGatt.writeDescriptor(descriptor);
+ }
+
+ @Override
+ public void onCharacteristicWrite(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic,
+ int status) {
+ Log.d(TAG, "onCharacteristicWrite " + status);
+ mPacketEncoder.writeComplete();
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+// logByteArray("Received ", characteristic.getValue(), 0,
+// characteristic.getValue().length);
+ mPacketDecoder.decodePacket(characteristic.getValue(), mOutputReceiver);
+ }
+ };
+
+ // This receives MIDI data that has already been passed through our MidiEventScheduler
+ // and has been normalized by our MidiFramer.
+
+ private class PacketReceiver implements PacketEncoder.PacketReceiver {
+ // buffers of every possible packet size
+ private final byte[][] mWriteBuffers;
+
+ public PacketReceiver() {
+ // Create buffers of every possible packet size
+ mWriteBuffers = new byte[MAX_PACKET_SIZE + 1][];
+ for (int i = 0; i <= MAX_PACKET_SIZE; i++) {
+ mWriteBuffers[i] = new byte[i];
+ }
+ }
+
+ @Override
+ public void writePacket(byte[] buffer, int count) {
+ if (mCharacteristic == null) {
+ Log.w(TAG, "not ready to send packet yet");
+ return;
+ }
+ byte[] writeBuffer = mWriteBuffers[count];
+ System.arraycopy(buffer, 0, writeBuffer, 0, count);
+ mCharacteristic.setValue(writeBuffer);
+// logByteArray("Sent ", mCharacteristic.getValue(), 0,
+// mCharacteristic.getValue().length);
+ mBluetoothGatt.writeCharacteristic(mCharacteristic);
+ }
+ }
+
+ public BluetoothMidiDevice(Context context, BluetoothDevice device,
+ BluetoothMidiService service) {
+ mBluetoothDevice = device;
+ mService = service;
+
+ mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, mGattCallback);
+
+ mMidiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
+
+ Bundle properties = new Bundle();
+ properties.putString(MidiDeviceInfo.PROPERTY_NAME, mBluetoothGatt.getDevice().getName());
+ properties.putParcelable(MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE,
+ mBluetoothGatt.getDevice());
+
+ MidiReceiver[] inputPortReceivers = new MidiReceiver[1];
+ inputPortReceivers[0] = mEventScheduler.getReceiver();
+
+ mDeviceServer = mMidiManager.createDeviceServer(inputPortReceivers, 1,
+ null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, null);
+
+ mOutputReceiver = mDeviceServer.getOutputPortReceivers()[0];
+
+ // This thread waits for outgoing messages from our MidiEventScheduler
+ // And forwards them to our MidiFramer to be prepared to send via Bluetooth.
+ new Thread("BluetoothMidiDevice " + mBluetoothDevice) {
+ @Override
+ public void run() {
+ while (true) {
+ MidiEvent event;
+ try {
+ event = (MidiEvent)mEventScheduler.waitNextEvent();
+ } catch (InterruptedException e) {
+ // try again
+ continue;
+ }
+ if (event == null) {
+ break;
+ }
+ try {
+ mPacketEncoder.sendWithTimestamp(event.data, 0, event.count,
+ event.getTimestamp());
+ } catch (IOException e) {
+ Log.e(TAG, "mPacketAccumulator.sendWithTimestamp failed", e);
+ }
+ mEventScheduler.addEventToPool(event);
+ }
+ Log.d(TAG, "BluetoothMidiDevice thread exit");
+ }
+ }.start();
+ }
+
+ void close() {
+ mEventScheduler.close();
+ if (mDeviceServer != null) {
+ IoUtils.closeQuietly(mDeviceServer);
+ mDeviceServer = null;
+ mService.deviceClosed(mBluetoothDevice);
+ }
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.close();
+ mBluetoothGatt = null;
+ }
+ }
+
+ public IBinder getBinder() {
+ return mDeviceServer.asBinder();
+ }
+
+ private static void logByteArray(String prefix, byte[] value, int offset, int count) {
+ StringBuilder builder = new StringBuilder(prefix);
+ for (int i = offset; i < count; i++) {
+ String hex = Integer.toHexString(value[i]);
+ int length = hex.length();
+ if (length == 1) {
+ hex = "0x" + hex;
+ } else {
+ hex = hex.substring(length - 2, length);
+ }
+ builder.append(hex);
+ if (i != value.length - 1) {
+ builder.append(", ");
+ }
+ }
+ Log.d(TAG, builder.toString());
+ }
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java
new file mode 100644
index 0000000..fbde2b4
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java
@@ -0,0 +1,61 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import android.app.Service;
+import android.bluetooth.BluetoothDevice;
+import android.content.Intent;
+import android.media.midi.MidiManager;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.HashMap;
+
+public class BluetoothMidiService extends Service {
+ private static final String TAG = "BluetoothMidiService";
+
+ // BluetoothMidiDevices keyed by BluetoothDevice
+ private final HashMap<BluetoothDevice,BluetoothMidiDevice> mDeviceServerMap
+ = new HashMap<BluetoothDevice,BluetoothMidiDevice>();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (MidiManager.BLUETOOTH_MIDI_SERVICE_INTENT.equals(intent.getAction())) {
+ BluetoothDevice bluetoothDevice = (BluetoothDevice)intent.getParcelableExtra("device");
+ if (bluetoothDevice == null) {
+ Log.e(TAG, "no BluetoothDevice in onBind intent");
+ return null;
+ }
+
+ BluetoothMidiDevice device;
+ synchronized (mDeviceServerMap) {
+ device = mDeviceServerMap.get(bluetoothDevice);
+ if (device == null) {
+ device = new BluetoothMidiDevice(this, bluetoothDevice, this);
+ }
+ }
+ return device.getBinder();
+ }
+ return null;
+ }
+
+ void deviceClosed(BluetoothDevice device) {
+ synchronized (mDeviceServerMap) {
+ mDeviceServerMap.remove(device);
+ }
+ }
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java
new file mode 100644
index 0000000..c5bfb5f
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java
@@ -0,0 +1,106 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import android.media.midi.MidiReceiver;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * This is an abstract base class that decodes a packet buffer and passes it to a
+ * {@link android.media.midi.MidiReceiver}
+ */
+public class BluetoothPacketDecoder extends PacketDecoder {
+
+ private static final String TAG = "BluetoothPacketDecoder";
+
+ private final byte[] mBuffer;
+
+ private final int TIMESTAMP_MASK_HIGH = 0x1F80;
+ private final int TIMESTAMP_MASK_LOW = 0x7F;
+ private final int HEADER_TIMESTAMP_MASK = 0x3F;
+
+ public BluetoothPacketDecoder(int maxPacketSize) {
+ mBuffer = new byte[maxPacketSize];
+ }
+
+ @Override
+ public void decodePacket(byte[] buffer, MidiReceiver receiver) {
+ int length = buffer.length;
+
+ // NOTE his code allows running status across packets,
+ // although the specification does not allow that.
+
+ if (length < 1) {
+ Log.e(TAG, "empty packet");
+ return;
+ }
+ byte header = buffer[0];
+ if ((header & 0xC0) != 0x80) {
+ Log.e(TAG, "packet does not start with header");
+ return;
+ }
+
+ // shift bits 0 - 5 to bits 7 - 12
+ int timestamp = (header & HEADER_TIMESTAMP_MASK) << 7;
+ boolean lastWasTimestamp = false;
+ int dataCount = 0;
+ int previousLowTimestamp = 0;
+
+ // iterate through the rest of the packet, separating MIDI data from timestamps
+ for (int i = 1; i < buffer.length; i++) {
+ byte b = buffer[i];
+
+ if ((b & 0x80) != 0 && !lastWasTimestamp) {
+ lastWasTimestamp = true;
+ int lowTimestamp = b & TIMESTAMP_MASK_LOW;
+ int newTimestamp = (timestamp & TIMESTAMP_MASK_HIGH) | lowTimestamp;
+ if (lowTimestamp < previousLowTimestamp) {
+ newTimestamp = (newTimestamp + 0x0080) & TIMESTAMP_MASK_HIGH;
+ }
+ previousLowTimestamp = lowTimestamp;
+
+ if (newTimestamp != timestamp) {
+ if (dataCount > 0) {
+ // send previous message separately since it has a different timestamp
+ try {
+ // FIXME use sendWithTimestamp
+ receiver.send(mBuffer, 0, dataCount);
+ } catch (IOException e) {
+ // ???
+ }
+ dataCount = 0;
+ }
+ }
+ timestamp = newTimestamp;
+ } else {
+ lastWasTimestamp = false;
+ mBuffer[dataCount++] = b;
+ }
+ }
+
+ if (dataCount > 0) {
+ try {
+ // FIXME use sendWithTimestamp
+ receiver.send(mBuffer, 0, dataCount);
+ } catch (IOException e) {
+ // ???
+ }
+ }
+ }
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java
new file mode 100644
index 0000000..463edcf
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java
@@ -0,0 +1,157 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import android.media.midi.MidiReceiver;
+
+import com.android.internal.midi.MidiConstants;
+import com.android.internal.midi.MidiFramer;
+
+import java.io.IOException;
+
+/**
+ * This class accumulates MIDI messages to form a MIDI packet.
+ */
+public class BluetoothPacketEncoder extends PacketEncoder {
+
+ private static final String TAG = "BluetoothPacketEncoder";
+
+ private static final long MILLISECOND_NANOS = 1000000L;
+
+ // mask for generating 13 bit timestamps
+ private static final int MILLISECOND_MASK = 0x1FFF;
+
+ private final PacketReceiver mPacketReceiver;
+
+ // buffer for accumulating messages to write
+ private final byte[] mAccumulationBuffer;
+ // number of bytes currently in mAccumulationBuffer
+ private int mAccumulatedBytes;
+ // timestamp for first message in current packet
+ private int mPacketTimestamp;
+ // current running status, or zero if none
+ private int mRunningStatus;
+
+ private boolean mWritePending;
+
+ private final Object mLock = new Object();
+
+ // This receives normalized data from mMidiFramer and accumulates it into a packet buffer
+ private final MidiReceiver mFramedDataReceiver = new MidiReceiver() {
+ @Override
+ public void onReceive(byte[] msg, int offset, int count, long timestamp)
+ throws IOException {
+
+ int milliTimestamp = (int)(timestamp / MILLISECOND_NANOS) & MILLISECOND_MASK;
+ int status = msg[0] & 0xFF;
+
+ synchronized (mLock) {
+ boolean needsTimestamp = (milliTimestamp != mPacketTimestamp);
+ int bytesNeeded = count;
+ if (needsTimestamp) bytesNeeded++; // add one for timestamp byte
+ if (status == mRunningStatus) bytesNeeded--; // subtract one for status byte
+
+ if (mAccumulatedBytes + bytesNeeded > mAccumulationBuffer.length) {
+ // write out our data if there is no more room
+ // if necessary, block until previous packet is sent
+ flushLocked(true);
+ }
+
+ // write header if we are starting a new packet
+ if (mAccumulatedBytes == 0) {
+ // header byte with timestamp bits 7 - 12
+ mAccumulationBuffer[mAccumulatedBytes++] = (byte)(0x80 | (milliTimestamp >> 7));
+ mPacketTimestamp = milliTimestamp;
+ needsTimestamp = true;
+ }
+
+ // write new timestamp byte and status byte if necessary
+ if (needsTimestamp) {
+ // timestamp byte with bits 0 - 6 of timestamp
+ mAccumulationBuffer[mAccumulatedBytes++] =
+ (byte)(0x80 | (milliTimestamp & 0x7F));
+ mPacketTimestamp = milliTimestamp;
+ }
+
+ if (status != mRunningStatus) {
+ mAccumulationBuffer[mAccumulatedBytes++] = (byte)status;
+ if (MidiConstants.allowRunningStatus(status)) {
+ mRunningStatus = status;
+ } else if (MidiConstants.allowRunningStatus(status)) {
+ mRunningStatus = 0;
+ }
+ }
+
+ // now copy data bytes
+ int dataLength = count - 1;
+ System.arraycopy(msg, 1, mAccumulationBuffer, mAccumulatedBytes, dataLength);
+ // FIXME - handle long SysEx properly
+ mAccumulatedBytes += dataLength;
+
+ // write the packet if possible, but do not block
+ flushLocked(false);
+ }
+ }
+ };
+
+ // MidiFramer for normalizing incoming data
+ private final MidiFramer mMidiFramer = new MidiFramer(mFramedDataReceiver);
+
+ public BluetoothPacketEncoder(PacketReceiver packetReceiver, int maxPacketSize) {
+ mPacketReceiver = packetReceiver;
+ mAccumulationBuffer = new byte[maxPacketSize];
+ }
+
+ @Override
+ public void onReceive(byte[] msg, int offset, int count, long timestamp)
+ throws IOException {
+ // normalize the data by passing it through a MidiFramer first
+ mMidiFramer.sendWithTimestamp(msg, offset, count, timestamp);
+ }
+
+ @Override
+ public void writeComplete() {
+ synchronized (mLock) {
+ mWritePending = false;
+ flushLocked(false);
+ mLock.notify();
+ }
+ }
+
+ private void flushLocked(boolean canBlock) {
+ if (mWritePending && !canBlock) {
+ return;
+ }
+
+ while (mWritePending && mAccumulatedBytes > 0) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ // try again
+ continue;
+ }
+ }
+
+ if (mAccumulatedBytes > 0) {
+ mPacketReceiver.writePacket(mAccumulationBuffer, mAccumulatedBytes);
+ mAccumulatedBytes = 0;
+ mPacketTimestamp = 0;
+ mRunningStatus = 0;
+ mWritePending = true;
+ }
+ }
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketDecoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketDecoder.java
new file mode 100644
index 0000000..da4b63a
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketDecoder.java
@@ -0,0 +1,33 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import android.media.midi.MidiReceiver;
+
+/**
+ * This is an abstract base class that decodes a packet buffer and passes it to a
+ * {@link android.media.midi.MidiReceiver}
+ */
+public abstract class PacketDecoder {
+
+ /**
+ * Decodes MIDI data in a packet and passes it to a {@link android.media.midi.MidiReceiver}
+ * @param buffer the packet to decode
+ * @param receiver the {@link android.media.midi.MidiReceiver} to receive the decoded MIDI data
+ */
+ abstract public void decodePacket(byte[] buffer, MidiReceiver receiver);
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java
new file mode 100644
index 0000000..12c8b9b
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java
@@ -0,0 +1,41 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import android.media.midi.MidiReceiver;
+
+/**
+ * This is an abstract base class that encodes MIDI data into a packet buffer.
+ * PacketEncoder receives data via its {@link android.media.midi.MidiReceiver#onReceive} method
+ * and notifies its client of packets to write via the {@link PacketEncoder.PacketReceiver}
+ * interface.
+ */
+public abstract class PacketEncoder extends MidiReceiver {
+
+ public interface PacketReceiver {
+ /** Called to write an accumulated packet.
+ * @param buffer the packet buffer to write
+ * @param count the number of bytes in the packet buffer to write
+ */
+ public void writePacket(byte[] buffer, int count);
+ }
+
+ /**
+ * Called to inform PacketEncoder when the previous write is complete.
+ */
+ abstract public void writeComplete();
+}
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 310ccf0..3ca239a 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -123,5 +123,7 @@
<item quantity="one">Copying <xliff:g id="count" example="1">%1$d</xliff:g> file.</item>
<item quantity="other">Copying <xliff:g id="count" example="3">%1$d</xliff:g> files.</item>
</plurals>
+ <!-- Text shown on the copy notification while DocumentsUI performs setup in preparation for copying files [CHAR LIMIT=32] -->
+ <string name="copy_preparing">Preparing for copy\u2026</string>
</resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index f135af49b..c826aba 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -16,19 +16,24 @@
package com.android.documentsui;
+import static com.android.documentsui.model.DocumentInfo.getCursorLong;
+import static com.android.documentsui.model.DocumentInfo.getCursorString;
+
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.content.ContentResolver;
+import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
+import android.database.Cursor;
import android.net.Uri;
import android.os.CancellationSignal;
-import android.os.Environment;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
import android.text.format.DateUtils;
import android.util.Log;
@@ -36,12 +41,13 @@ import com.android.documentsui.model.DocumentInfo;
import libcore.io.IoUtils;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.NumberFormat;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
public class CopyService extends IntentService {
public static final String TAG = "CopyService";
@@ -56,6 +62,7 @@ public class CopyService extends IntentService {
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 List<Uri> mFailedFiles;
private long mBatchSize;
private long mBytesCopied;
private long mStartTime;
@@ -65,9 +72,15 @@ public class CopyService extends IntentService {
private long mSampleTime;
private long mSpeed;
private long mRemainingTime;
+ // Provider clients are acquired for the duration of each copy job. Note that there is an
+ // implicit assumption that all srcs come from the same authority.
+ private ContentProviderClient mSrcClient;
+ private ContentProviderClient mDstClient;
public CopyService() {
super("CopyService");
+
+ mFailedFiles = new ArrayList<Uri>();
}
@Override
@@ -88,27 +101,34 @@ public class CopyService extends IntentService {
ArrayList<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST);
Uri destinationUri = intent.getData();
- setupCopyJob(srcs, destinationUri);
+ try {
+ // Acquire content providers.
+ mSrcClient = DocumentsApplication.acquireUnstableProviderOrThrow(getContentResolver(),
+ srcs.get(0).authority);
+ mDstClient = DocumentsApplication.acquireUnstableProviderOrThrow(getContentResolver(),
+ destinationUri.getAuthority());
- ArrayList<String> failedFilenames = new ArrayList<String>();
- for (int i = 0; i < srcs.size() && !mIsCancelled; ++i) {
- DocumentInfo src = srcs.get(i);
- try {
- copyFile(src, destinationUri);
- } catch (IOException e) {
- Log.e(TAG, "Failed to copy " + src.displayName, e);
- failedFilenames.add(src.displayName);
+ setupCopyJob(srcs, destinationUri);
+
+ for (int i = 0; i < srcs.size() && !mIsCancelled; ++i) {
+ copy(srcs.get(i), destinationUri);
}
- }
+ } catch (Exception e) {
+ // Catch-all to prevent any copy errors from wedging the app.
+ Log.e(TAG, "Exceptions occurred during copying", e);
+ } finally {
+ ContentProviderClient.releaseQuietly(mSrcClient);
+ ContentProviderClient.releaseQuietly(mDstClient);
- if (failedFilenames.size() > 0) {
- // TODO: Display a notification when an error has occurred.
- }
+ // Dismiss the ongoing copy notification when the copy is done.
+ mNotificationManager.cancel(mJobId, 0);
- // Dismiss the ongoing copy notification when the copy is done.
- mNotificationManager.cancel(mJobId, 0);
+ if (mFailedFiles.size() > 0) {
+ // TODO: Display a notification when an error has occurred.
+ }
- // TODO: Display a toast if the copy was cancelled.
+ // TODO: Display a toast if the copy was cancelled.
+ }
}
@Override
@@ -123,8 +143,10 @@ public class CopyService extends IntentService {
*
* @param srcs A list of src files to copy.
* @param destinationUri The URI of the destination directory.
+ * @throws RemoteException
*/
- private void setupCopyJob(ArrayList<DocumentInfo> srcs, Uri destinationUri) {
+ private void setupCopyJob(ArrayList<DocumentInfo> srcs, Uri destinationUri)
+ throws RemoteException {
// Create an ID for this copy job. Use the timestamp.
mJobId = String.valueOf(SystemClock.elapsedRealtime());
// Reset the cancellation flag.
@@ -144,13 +166,13 @@ public class CopyService extends IntentService {
// TODO: Add a content intent to open the destination folder.
// Send an initial progress notification.
+ mProgressBuilder.setProgress(0, 0, true); // Indeterminate progress while setting up.
+ mProgressBuilder.setContentText(getString(R.string.copy_preparing));
mNotificationManager.notify(mJobId, 0, mProgressBuilder.build());
// Reset batch parameters.
- mBatchSize = 0;
- for (DocumentInfo doc : srcs) {
- mBatchSize += doc.size;
- }
+ mFailedFiles.clear();
+ mBatchSize = calculateFileSizes(srcs);
mBytesCopied = 0;
mStartTime = SystemClock.elapsedRealtime();
mLastNotificationTime = 0;
@@ -165,6 +187,66 @@ public class CopyService extends IntentService {
}
/**
+ * Calculates the cumulative size of all the documents in the list. Directories are recursed
+ * into and totaled up.
+ *
+ * @param srcs
+ * @return Size in bytes.
+ * @throws RemoteException
+ */
+ private long calculateFileSizes(List<DocumentInfo> srcs) throws RemoteException {
+ long result = 0;
+ for (DocumentInfo src : srcs) {
+ if (Document.MIME_TYPE_DIR.equals(src.mimeType)) {
+ // Directories need to be recursed into.
+ result += calculateFileSizesHelper(src.derivedUri);
+ } else {
+ result += src.size;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Calculates (recursively) the cumulative size of all the files under the given directory.
+ *
+ * @throws RemoteException
+ */
+ private long calculateFileSizesHelper(Uri uri) throws RemoteException {
+ final String authority = uri.getAuthority();
+ final Uri queryUri = DocumentsContract.buildChildDocumentsUri(authority,
+ DocumentsContract.getDocumentId(uri));
+ final String queryColumns[] = new String[] {
+ Document.COLUMN_DOCUMENT_ID,
+ Document.COLUMN_MIME_TYPE,
+ Document.COLUMN_SIZE
+ };
+
+ long result = 0;
+ Cursor cursor = null;
+ try {
+ cursor = mSrcClient.query(queryUri, queryColumns, null, null, null);
+ while (cursor.moveToNext()) {
+ if (Document.MIME_TYPE_DIR.equals(
+ getCursorString(cursor, Document.COLUMN_MIME_TYPE))) {
+ // Recurse into directories.
+ final Uri subdirUri = DocumentsContract.buildDocumentUri(authority,
+ getCursorString(cursor, Document.COLUMN_DOCUMENT_ID));
+ result += calculateFileSizesHelper(subdirUri);
+ } else {
+ // This may return -1 if the size isn't defined. Ignore those cases.
+ long size = getCursorLong(cursor, Document.COLUMN_SIZE);
+ result += size > 0 ? size : 0;
+ }
+ }
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ }
+
+ return result;
+ }
+
+ /**
* Cancels the current copy job, if its ID matches the given ID.
*
* @param intent The cancellation intent.
@@ -173,7 +255,7 @@ public class CopyService extends IntentService {
final String cancelledId = intent.getStringExtra(EXTRA_CANCEL);
// Do nothing if the cancelled ID doesn't match the current job ID. This prevents racey
// cancellation requests from affecting unrelated copy jobs.
- if (java.util.Objects.equals(mJobId, cancelledId)) {
+ if (Objects.equals(mJobId, cancelledId)) {
// Set the cancel flag. This causes the copy loops to exit.
mIsCancelled = true;
// Dismiss the progress notification here rather than in the copy loop. This preserves
@@ -237,21 +319,78 @@ public class CopyService extends IntentService {
}
/**
- * Copies a file to a given location.
+ * Copies a the given documents to the given location.
*
- * @param srcInfo The source file.
- * @param destinationUri The URI of the destination directory.
- * @throws IOException
+ * @param srcInfo DocumentInfos for the documents to copy.
+ * @param dstDirUri The URI of the destination directory.
+ * @throws RemoteException
*/
- private void copyFile(DocumentInfo srcInfo, Uri destinationUri) throws IOException {
- final Context context = getApplicationContext();
- final ContentResolver resolver = context.getContentResolver();
-
- final Uri writableDstUri = DocumentsContract.buildDocumentUriUsingTree(destinationUri,
- DocumentsContract.getTreeDocumentId(destinationUri));
- final Uri dstFileUri = DocumentsContract.createDocument(resolver, writableDstUri,
+ private void copy(DocumentInfo srcInfo, Uri dstDirUri) throws RemoteException {
+ final Uri dstUri = DocumentsContract.createDocument(mDstClient, dstDirUri,
srcInfo.mimeType, srcInfo.displayName);
+ if (dstUri == null) {
+ // If this is a directory, the entire subdir will not be copied over.
+ Log.e(TAG, "Error while copying " + srcInfo.displayName);
+ mFailedFiles.add(srcInfo.derivedUri);
+ return;
+ }
+
+ if (Document.MIME_TYPE_DIR.equals(srcInfo.mimeType)) {
+ copyDirectoryHelper(srcInfo.derivedUri, dstUri);
+ } else {
+ copyFileHelper(srcInfo.derivedUri, dstUri);
+ }
+ }
+ /**
+ * 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".
+ *
+ * @param srcDirUri URI of the directory to copy from. The routine will copy the directory's
+ * contents, not the directory itself.
+ * @param dstDirUri URI of the directory to copy to. Must be created beforehand.
+ * @throws RemoteException
+ */
+ private void copyDirectoryHelper(Uri srcDirUri, Uri dstDirUri) throws RemoteException {
+ // Recurse into directories. Copy children into the new subdirectory.
+ final String queryColumns[] = new String[] {
+ Document.COLUMN_DISPLAY_NAME,
+ Document.COLUMN_DOCUMENT_ID,
+ Document.COLUMN_MIME_TYPE,
+ Document.COLUMN_SIZE
+ };
+ final Uri queryUri = DocumentsContract.buildChildDocumentsUri(srcDirUri.getAuthority(),
+ DocumentsContract.getDocumentId(srcDirUri));
+ Cursor cursor = null;
+ try {
+ // Iterate over srcs in the directory; copy to the destination directory.
+ cursor = mSrcClient.query(queryUri, queryColumns, null, null, null);
+ while (cursor.moveToNext()) {
+ final String childMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+ final Uri dstUri = DocumentsContract.createDocument(mDstClient, dstDirUri,
+ childMimeType, getCursorString(cursor, Document.COLUMN_DISPLAY_NAME));
+ final Uri childUri = DocumentsContract.buildDocumentUri(srcDirUri.getAuthority(),
+ getCursorString(cursor, Document.COLUMN_DOCUMENT_ID));
+ if (Document.MIME_TYPE_DIR.equals(childMimeType)) {
+ copyDirectoryHelper(childUri, dstUri);
+ } else {
+ copyFileHelper(childUri, dstUri);
+ }
+ }
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ }
+ }
+
+ /**
+ * Handles copying a single file.
+ *
+ * @param srcUri URI of the file to copy from.
+ * @param dstUri URI of the *file* to copy to. Must be created beforehand.
+ * @throws RemoteException
+ */
+ private void copyFileHelper(Uri srcUri, Uri dstUri) throws RemoteException {
+ // Copy an individual file.
CancellationSignal canceller = new CancellationSignal();
ParcelFileDescriptor srcFile = null;
ParcelFileDescriptor dstFile = null;
@@ -260,8 +399,8 @@ public class CopyService extends IntentService {
boolean errorOccurred = false;
try {
- srcFile = resolver.openFileDescriptor(srcInfo.derivedUri, "r", canceller);
- dstFile = resolver.openFileDescriptor(dstFileUri, "w", canceller);
+ srcFile = mSrcClient.openFile(srcUri, "r", canceller);
+ dstFile = mDstClient.openFile(dstUri, "w", canceller);
src = new ParcelFileDescriptor.AutoCloseInputStream(srcFile);
dst = new ParcelFileDescriptor.AutoCloseOutputStream(dstFile);
@@ -275,7 +414,8 @@ public class CopyService extends IntentService {
dstFile.checkError();
} catch (IOException e) {
errorOccurred = true;
- Log.e(TAG, "Error while copying " + srcInfo.displayName, e);
+ Log.e(TAG, "Error while copying " + srcUri.toString(), e);
+ mFailedFiles.add(srcUri);
} finally {
// This also ensures the file descriptors are closed.
IoUtils.closeQuietly(src);
@@ -285,8 +425,13 @@ public class CopyService extends IntentService {
if (errorOccurred || mIsCancelled) {
// Clean up half-copied files.
canceller.cancel();
- if (!DocumentsContract.deleteDocument(resolver, dstFileUri)) {
- Log.w(TAG, "Failed to clean up: " + srcInfo.displayName);
+ try {
+ DocumentsContract.deleteDocument(mDstClient, dstUri);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to clean up: " + srcUri, e);
+ // RemoteExceptions usually signal that the connection is dead, so there's no point
+ // attempting to continue. Propagate the exception up so the copy job is cancelled.
+ throw e;
}
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 83071bd..0e3016d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -357,7 +357,12 @@ public class DirectoryFragment extends Fragment {
return;
}
- Uri destination = data.getData();
+ // Because the destination picker is launched using an open tree intent, the URI returned is
+ // a tree URI. Convert it to a document URI.
+ // TODO: Remove this step when the destination picker returns a document URI.
+ final Uri destinationTree = data.getData();
+ final Uri destination = DocumentsContract.buildDocumentUriUsingTree(destinationTree,
+ DocumentsContract.getTreeDocumentId(destinationTree));
List<DocumentInfo> docs = mSelectedDocumentsForCopy;
Intent copyIntent = new Intent(context, CopyService.class);
@@ -506,8 +511,10 @@ public class DirectoryFragment extends Fragment {
open.setVisible(!manageMode);
share.setVisible(manageMode);
delete.setVisible(manageMode);
- // Hide the copy feature by default.
- copy.setVisible(SystemProperties.getBoolean("debug.documentsui.enable_copy", false));
+ // Hide the copy menu item in the recents folder. For now, also hide it by default
+ // unless the debug flag is enabled.
+ copy.setVisible((mType != TYPE_RECENT_OPEN) &&
+ SystemProperties.getBoolean("debug.documentsui.enable_copy", false));
return true;
}
@@ -575,9 +582,7 @@ public class DirectoryFragment extends Fragment {
if (cursor != null) {
final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
- if (!Document.MIME_TYPE_DIR.equals(docMimeType)) {
- valid = isDocumentEnabled(docMimeType, docFlags);
- }
+ valid = isDocumentEnabled(docMimeType, docFlags);
}
if (!valid) {
@@ -606,8 +611,17 @@ public class DirectoryFragment extends Fragment {
private void onShareDocuments(List<DocumentInfo> docs) {
Intent intent;
- if (docs.size() == 1) {
- final DocumentInfo doc = docs.get(0);
+
+ // Filter out directories - those can't be shared.
+ List<DocumentInfo> docsForSend = Lists.newArrayList();
+ for (DocumentInfo doc: docs) {
+ if (!Document.MIME_TYPE_DIR.equals(doc.mimeType)) {
+ docsForSend.add(doc);
+ }
+ }
+
+ if (docsForSend.size() == 1) {
+ final DocumentInfo doc = docsForSend.get(0);
intent = new Intent(Intent.ACTION_SEND);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -615,14 +629,14 @@ public class DirectoryFragment extends Fragment {
intent.setType(doc.mimeType);
intent.putExtra(Intent.EXTRA_STREAM, doc.derivedUri);
- } else if (docs.size() > 1) {
+ } else if (docsForSend.size() > 1) {
intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addCategory(Intent.CATEGORY_DEFAULT);
final ArrayList<String> mimeTypes = Lists.newArrayList();
final ArrayList<Uri> uris = Lists.newArrayList();
- for (DocumentInfo doc : docs) {
+ for (DocumentInfo doc : docsForSend) {
mimeTypes.add(doc.mimeType);
uris.add(doc.derivedUri);
}
diff --git a/packages/Keyguard/res/values/strings.xml b/packages/Keyguard/res/values/strings.xml
index e835536..df221fa 100644
--- a/packages/Keyguard/res/values/strings.xml
+++ b/packages/Keyguard/res/values/strings.xml
@@ -303,4 +303,7 @@
<!-- Description of airplane mode -->
<string name="airplane_mode">Airplane mode</string>
+ <!-- Fingerprint hint message when finger was not recognized.-->
+ <string name="fingerprint_not_recognized">Not recognized</string>
+
</resources>
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 82dec30..5a4d5cd 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -149,6 +149,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
private boolean mScreenOn;
private SubscriptionManager mSubscriptionManager;
private List<SubscriptionInfo> mSubscriptionInfo;
+ private boolean mFingerprintDetectionRunning;
private final Handler mHandler = new Handler() {
@Override
@@ -332,27 +333,35 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
}
private void handleFingerprintAuthenticated(int fingerId, int groupId) {
- if (fingerId == 0) return; // not a valid fingerprint
-
- final int userId;
- try {
- userId = ActivityManagerNative.getDefault().getCurrentUser().id;
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to get current user id: ", e);
+ if (fingerId == 0) {
+ // not a valid fingerprint
+ handleFingerprintHelp(-1, mContext.getString(R.string.fingerprint_not_recognized));
return;
}
- if (isFingerprintDisabled(userId)) {
- Log.d(TAG, "Fingerprint disabled by DPM for userId: " + userId);
- return;
- }
- final ContentResolver res = mContext.getContentResolver();
- final int ids[] = FingerprintUtils.getFingerprintIdsForUser(res, userId);
- for (int i = 0; i < ids.length; i++) {
- // TODO: fix once HAL supports storing group id
- final boolean isCorrectUser = true || (groupId == userId);
- if (ids[i] == fingerId && isCorrectUser) {
- onFingerprintAuthenticated(userId);
+
+ try {
+ final int userId;
+ try {
+ userId = ActivityManagerNative.getDefault().getCurrentUser().id;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get current user id: ", e);
+ return;
+ }
+ if (isFingerprintDisabled(userId)) {
+ Log.d(TAG, "Fingerprint disabled by DPM for userId: " + userId);
+ return;
+ }
+ final ContentResolver res = mContext.getContentResolver();
+ final int ids[] = FingerprintUtils.getFingerprintIdsForUser(res, userId);
+ for (int i = 0; i < ids.length; i++) {
+ // TODO: fix once HAL supports storing group id
+ final boolean isCorrectUser = true || (groupId == userId);
+ if (ids[i] == fingerId && isCorrectUser) {
+ onFingerprintAuthenticated(userId);
+ }
}
+ } finally {
+ setFingerprintRunningDetectionRunning(false);
}
}
@@ -366,6 +375,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
}
private void handleFingerprintError(int msgId, String errString) {
+ setFingerprintRunningDetectionRunning(false);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -374,6 +384,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
}
}
+ private void setFingerprintRunningDetectionRunning(boolean running) {
+ if (running != mFingerprintDetectionRunning) {
+ mFingerprintDetectionRunning = running;
+ notifyFingerprintRunningStateChanged();
+ }
+ }
+
+ private void notifyFingerprintRunningStateChanged() {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onFingerprintRunningStateChanged(mFingerprintDetectionRunning);
+ }
+ }
+ }
private void handleFaceUnlockStateChanged(boolean running, int userId) {
mUserFaceUnlockRunning.put(userId, running);
for (int i = 0; i < mCallbacks.size(); i++) {
@@ -388,6 +413,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
return mUserFaceUnlockRunning.get(userId);
}
+ public boolean isFingerprintDetectionRunning() {
+ return mFingerprintDetectionRunning;
+ }
+
private boolean isTrustDisabled(int userId) {
// Don't allow trust agent if device is secured with a SIM PIN. This is here
// mainly because there's no other way to prompt the user to enter their SIM PIN
@@ -736,13 +765,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
}
private void startListeningForFingerprint(Context context) {
- if (mFpm != null && mFpm.isHardwareDetected()) {
+ if (mFpm != null && mFpm.isHardwareDetected()
+ && mFpm.getEnrolledFingerprints().size() > 0) {
if (mFingerprintCancelSignal == null) {
mFingerprintCancelSignal = new CancellationSignal();
} else {
mFingerprintCancelSignal.cancel();
}
mFpm.authenticate(null, mAuthenticationCallback, mFingerprintCancelSignal, 0);
+ setFingerprintRunningDetectionRunning(true);
}
}
@@ -750,6 +781,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
if (mFingerprintCancelSignal != null) {
mFingerprintCancelSignal.cancel();
}
+ setFingerprintRunningDetectionRunning(false);
}
private boolean isDeviceProvisionedInSettingsDb() {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 8d2562d..756a7a4 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -200,4 +200,9 @@ public class KeyguardUpdateMonitorCallback {
* Called when the state of face unlock changed.
*/
public void onFaceUnlockStateChanged(boolean running, int userId) { }
+
+ /**
+ * Called when the fingerprint running state changed.
+ */
+ public void onFingerprintRunningStateChanged(boolean running) { }
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 9d16501..35e9636 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -93,6 +93,7 @@
<uses-permission android:name="android.permission.BIND_APPWIDGET" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
<uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
+ <uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS" />
<application android:label="@string/app_label">
<provider
diff --git a/packages/StatementService/Android.mk b/packages/StatementService/Android.mk
index f0adb1c..470d824 100644
--- a/packages/StatementService/Android.mk
+++ b/packages/StatementService/Android.mk
@@ -24,6 +24,8 @@ LOCAL_PROGUARD_FLAG_FILES := proguard.flags
LOCAL_PACKAGE_NAME := StatementService
LOCAL_PRIVILEGED_MODULE := true
+LOCAL_JAVA_LIBRARIES += org.apache.http.legacy
+
LOCAL_STATIC_JAVA_LIBRARIES := \
libprotobuf-java-nano \
volley
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 47ef42a..51fea2a 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -20,6 +20,11 @@ LOCAL_RESOURCE_DIR := \
$(LOCAL_PATH)/res
LOCAL_AAPT_FLAGS := --auto-add-overlay --extra-packages com.android.keyguard
+ifneq ($(SYSTEM_UI_INCREMENTAL_BUILDS),)
+ LOCAL_PROGUARD_ENABLED := disabled
+ LOCAL_JACK_ENABLED := incremental
+endif
+
include frameworks/base/packages/SettingsLib/common.mk
include $(BUILD_PACKAGE)
diff --git a/packages/SystemUI/res/drawable/ic_fingerprint.xml b/packages/SystemUI/res/drawable/ic_fingerprint.xml
new file mode 100644
index 0000000..ee2cf03
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_fingerprint.xml
@@ -0,0 +1,36 @@
+<!--
+ ~ 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="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0">
+ <path
+ android:fillColor="#80ffffff"
+ android:pathData="M23.7,5.9c-0.1,0.0 -0.2,0.0 -0.3,-0.1C21.0,4.5 18.6,3.9 16.0,3.9c-2.5,0.0 -4.6,0.6 -6.9,1.9C8.8,6.0 8.3,5.9 8.1,5.5C7.9,5.2 8.0,4.7 8.4,4.5c2.5,-1.4 4.9,-2.1 7.7,-2.1c2.8,0.0 5.4,0.7 8.0,2.1c0.4,0.2 0.5,0.6 0.3,1.0C24.2,5.7 24.0,5.9 23.7,5.9z"/>
+ <path
+ android:fillColor="#80ffffff"
+ android:pathData="M5.3,13.2c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,-0.2 -0.4,-0.7 -0.2,-1.0c1.3,-1.9 2.9,-3.4 4.9,-4.5c4.1,-2.2 9.3,-2.2 13.4,0.0c1.9,1.1 3.6,2.5 4.9,4.4c0.2,0.3 0.1,0.8 -0.2,1.0c-0.3,0.2 -0.8,0.1 -1.0,-0.2c-1.2,-1.7 -2.6,-3.0 -4.3,-4.0c-3.7,-2.0 -8.3,-2.0 -12.0,0.0c-1.7,0.9 -3.2,2.3 -4.3,4.0C5.7,13.1 5.5,13.2 5.3,13.2z"/>
+ <path
+ android:fillColor="#80ffffff"
+ android:pathData="M13.3,29.6c-0.2,0.0 -0.4,-0.1 -0.5,-0.2c-1.1,-1.2 -1.7,-2.0 -2.6,-3.6c-0.9,-1.7 -1.4,-3.7 -1.4,-5.9c0.0,-4.1 3.3,-7.4 7.4,-7.4c4.1,0.0 7.4,3.3 7.4,7.4c0.0,0.4 -0.3,0.7 -0.7,0.7s-0.7,-0.3 -0.7,-0.7c0.0,-3.3 -2.7,-5.9 -5.9,-5.9c-3.3,0.0 -5.9,2.7 -5.9,5.9c0.0,2.0 0.4,3.8 1.2,5.2c0.8,1.6 1.4,2.2 2.4,3.3c0.3,0.3 0.3,0.8 0.0,1.0C13.7,29.5 13.5,29.6 13.3,29.6z"/>
+ <path
+ android:fillColor="#80ffffff"
+ android:pathData="M22.6,27.1c-1.6,0.0 -2.9,-0.4 -4.1,-1.2c-1.9,-1.4 -3.1,-3.6 -3.1,-6.0c0.0,-0.4 0.3,-0.7 0.7,-0.7s0.7,0.3 0.7,0.7c0.0,1.9 0.9,3.7 2.5,4.8c0.9,0.6 1.9,1.0 3.2,1.0c0.3,0.0 0.8,0.0 1.3,-0.1c0.4,-0.1 0.8,0.2 0.8,0.6c0.1,0.4 -0.2,0.8 -0.6,0.8C23.4,27.1 22.8,27.1 22.6,27.1z"/>
+ <path
+ android:fillColor="#80ffffff"
+ android:pathData="M20.0,29.9c-0.1,0.0 -0.1,0.0 -0.2,0.0c-2.1,-0.6 -3.4,-1.4 -4.8,-2.9c-1.8,-1.9 -2.8,-4.4 -2.8,-7.1c0.0,-2.2 1.8,-4.1 4.1,-4.1c2.2,0.0 4.1,1.8 4.1,4.1c0.0,1.4 1.2,2.6 2.6,2.6c1.4,0.0 2.6,-1.2 2.6,-2.6c0.0,-5.1 -4.2,-9.3 -9.3,-9.3c-3.6,0.0 -6.9,2.1 -8.4,5.4C7.3,17.1 7.0,18.4 7.0,19.8c0.0,1.1 0.1,2.7 0.9,4.9c0.1,0.4 -0.1,0.8 -0.4,0.9c-0.4,0.1 -0.8,-0.1 -0.9,-0.4c-0.6,-1.8 -0.9,-3.6 -0.9,-5.4c0.0,-1.6 0.3,-3.1 0.9,-4.4c1.7,-3.8 5.6,-6.3 9.8,-6.3c5.9,0.0 10.7,4.8 10.7,10.7c0.0,2.2 -1.8,4.1 -4.1,4.1s-4.0,-1.8 -4.0,-4.1c0.0,-1.4 -1.2,-2.6 -2.6,-2.6c-1.4,0.0 -2.6,1.2 -2.6,2.6c0.0,2.3 0.9,4.5 2.4,6.1c1.2,1.3 2.4,2.0 4.2,2.5c0.4,0.1 0.6,0.5 0.5,0.9C20.6,29.7 20.3,29.9 20.0,29.9z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_fingerprint_error.xml b/packages/SystemUI/res/drawable/ic_fingerprint_error.xml
new file mode 100644
index 0000000..11e83a1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_fingerprint_error.xml
@@ -0,0 +1,30 @@
+<!--
+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="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0">
+ <path
+ android:fillColor="@color/system_warning_color"
+ android:pathData="M15.99,2.5C8.53,2.5 2.5,8.54 2.5,16.0s6.03,13.5 13.49,13.5S29.5,23.46 29.5,16.0S23.45,2.5 15.99,2.5zM16.0,26.8c-5.97,0.0 -10.8,-4.83 -10.8,-10.8S10.03,5.2 16.0,5.2S26.8,10.03 26.8,16.0S21.97,26.8 16.0,26.8z"/>
+ <path
+ android:fillColor="@color/system_warning_color"
+ android:pathData="M14.65,20.05l2.7,0.0l0.0,2.7l-2.7,0.0z"/>
+ <path
+ android:fillColor="@color/system_warning_color"
+ android:pathData="M14.65,9.25l2.7,0.0l0.0,8.1l-2.7,0.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_volume_bt.xml b/packages/SystemUI/res/drawable/ic_volume_media_bt.xml
index bce407a..3364d9c 100644
--- a/packages/SystemUI/res/drawable/ic_volume_bt.xml
+++ b/packages/SystemUI/res/drawable/ic_volume_media_bt.xml
@@ -14,13 +14,16 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="24dp"
- android:viewportHeight="48.0"
- android:viewportWidth="48.0"
- android:width="24dp" >
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
<path
android:fillColor="@color/volume_icon_color"
- android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z" />
+ android:pathData="M17.0,3.0l-7.0,0.0l0.0,9.3C9.5,12.1 9.0,12.0 8.5,12.0C6.0,12.0 4.0,14.0 4.0,16.5S6.0,21.0 8.5,21.0s4.5,-2.3 4.5,-4.5C13.0,14.7 13.0,6.0 13.0,6.0l4.0,0.0L17.0,3.0z"/>
+ <path
+ android:fillColor="@color/volume_icon_color"
+ android:pathData="M23.4,9.9L20.5,7.0L20.0,7.0l0.0,3.8l-2.3,-2.3L17.0,9.2l2.8,2.8L17.0,14.8l0.7,0.7l2.3,-2.3L20.0,17.0l0.5,0.0l2.8,-2.8L21.2,12.0L23.4,9.9zM21.0,8.9l0.9,0.9l-0.9,1.0L21.0,8.9zM21.9,14.2L21.0,15.1l0.0,-1.9L21.9,14.2z"/>
-</vector> \ No newline at end of file
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_volume_bt_mute.xml b/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml
index 98a8137..39f54f1 100644
--- a/packages/SystemUI/res/drawable/ic_volume_bt_mute.xml
+++ b/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml
@@ -15,12 +15,18 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
- android:viewportHeight="48.0"
- android:viewportWidth="48.0"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0"
android:width="24dp" >
<path
android:fillColor="@color/volume_icon_color"
- android:pathData="M26.0,11.8l3.8,3.8l-3.2,3.2l2.8,2.8l6.0,-6.0L24.0,4.2l-2.0,0.0l0.0,10.1l4.0,4.0L26.0,11.8zM10.8,8.2L8.0,11.0l13.2,13.2L10.0,35.3l2.8,2.8L22.0,29.0l0.0,15.2l2.0,0.0l8.6,-8.6l4.6,4.6l2.8,-2.8L10.8,8.2zM26.0,36.5L26.0,29.0l3.8,3.8L26.0,36.5z" />
+ android:pathData="M13.0,6.0l4.0,0.0L17.0,3.0l-7.0,0.0l0.0,5.6l3.0,3.0C13.0,8.8 13.0,6.0 13.0,6.0z"/>
+ <path
+ android:fillColor="@color/volume_icon_color"
+ android:pathData="M2.1,5.7L8.4,12.0C6.0,12.1 4.0,14.0 4.0,16.5S6.0,21.0 8.5,21.0c2.7,0.0 4.5,-2.3 4.5,-4.3l0.0,-0.1l3.9,3.9l1.3,-1.3L3.4,4.5L2.1,5.7z"/>
+ <path
+ android:fillColor="@color/volume_icon_color"
+ android:pathData="M23.4,9.9L20.5,7.0L20.0,7.0l0.0,3.8l-2.3,-2.3L17.0,9.2l2.8,2.8L17.0,14.8l0.7,0.7l2.3,-2.3L20.0,17.0l0.5,0.0l2.8,-2.8L21.2,12.0L23.4,9.9zM21.0,8.9l0.9,0.9l-0.9,1.0L21.0,8.9zM21.9,14.2L21.0,15.1l0.0,-1.9L21.9,14.2z"/>
</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 01b2713..fca8231 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -68,7 +68,6 @@
android:layout_gravity="bottom|center_horizontal"
android:src="@drawable/ic_lock_24dp"
android:scaleType="center"
- android:tint="#ffffffff"
android:contentDescription="@string/accessibility_unlock_button" />
</com.android.systemui.statusbar.phone.KeyguardBottomAreaView>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c9e1fee..b6ff1ce 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -573,4 +573,7 @@
<!-- Screen pinning inner nav bar outer circle size -->
<dimen name="screen_pinning_nav_highlight_outer_size">84dp</dimen>
+ <!-- Padding to be used on the bottom of the fingerprint icon on Keyguard so it better aligns
+ with the other icons. -->
+ <dimen name="fingerprint_icon_additional_padding">12dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3705157..779b55e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -981,5 +981,5 @@
<string name="volumeui_notification_text">Touch to restore the original.</string>
<!-- Volume dialog zen toggle switch title -->
- <string name="volume_zen_switch_text">Block interruptions</string>
+ <string name="volume_zen_switch_text">@*android:string/zen_mode_feature_name</string>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
new file mode 100644
index 0000000..9df67fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -0,0 +1,94 @@
+/*
+ * 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;
+
+import android.annotation.StringDef;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+
+public final class Prefs {
+ private Prefs() {} // no instantation
+
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ Key.SEARCH_APP_WIDGET_ID,
+ Key.DEBUG_MODE_ENABLED,
+ Key.HOTSPOT_TILE_LAST_USED,
+ Key.COLOR_INVERSION_TILE_LAST_USED,
+ Key.DND_TILE_VISIBLE,
+ Key.DND_TILE_COMBINED_ICON
+ })
+ public @interface Key {
+ String SEARCH_APP_WIDGET_ID = "searchAppWidgetId";
+ String DEBUG_MODE_ENABLED = "debugModeEnabled";
+ String HOTSPOT_TILE_LAST_USED = "HotspotTileLastUsed";
+ String COLOR_INVERSION_TILE_LAST_USED = "ColorInversionTileLastUsed";
+ String DND_TILE_VISIBLE = "DndTileVisible";
+ String DND_TILE_COMBINED_ICON = "DndTileCombinedIcon";
+ }
+
+ public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
+ return get(context).getBoolean(key, defaultValue);
+ }
+
+ public static void putBoolean(Context context, @Key String key, boolean value) {
+ get(context).edit().putBoolean(key, value).apply();
+ }
+
+ public static int getInt(Context context, @Key String key, int defaultValue) {
+ return get(context).getInt(key, defaultValue);
+ }
+
+ public static void putInt(Context context, @Key String key, int value) {
+ get(context).edit().putInt(key, value).apply();
+ }
+
+ public static long getLong(Context context, @Key String key, long defaultValue) {
+ return get(context).getLong(key, defaultValue);
+ }
+
+ public static void putLong(Context context, @Key String key, long value) {
+ get(context).edit().putLong(key, value).apply();
+ }
+
+ public static Map<String, ?> getAll(Context context) {
+ return get(context).getAll();
+ }
+
+ public static void remove(Context context, @Key String key) {
+ get(context).edit().remove(key).apply();
+ }
+
+ public static void registerListener(Context context,
+ OnSharedPreferenceChangeListener listener) {
+ get(context).registerOnSharedPreferenceChangeListener(listener);
+ }
+
+ public static void unregisterListener(Context context,
+ OnSharedPreferenceChangeListener listener) {
+ get(context).unregisterOnSharedPreferenceChangeListener(listener);
+ }
+
+ private static SharedPreferences get(Context context) {
+ return context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
index e60aa53..f36019b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
@@ -23,6 +23,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.Listenable;
@@ -32,14 +33,15 @@ public class UsageTracker implements Listenable {
private final Context mContext;
private final long mTimeToShowTile;
- private final String mPrefKey;
+ @Prefs.Key private final String mPrefKey;
private final String mResetAction;
private boolean mRegistered;
- public UsageTracker(Context context, Class<?> tile, int timeoutResource) {
+ public UsageTracker(Context context, @Prefs.Key String prefKey, Class<?> tile,
+ int timeoutResource) {
mContext = context;
- mPrefKey = tile.getSimpleName() + "LastUsed";
+ mPrefKey = prefKey;
mTimeToShowTile = MILLIS_PER_DAY * mContext.getResources().getInteger(timeoutResource);
mResetAction = "com.android.systemui.qs." + tile.getSimpleName() + ".usage_reset";
}
@@ -56,16 +58,16 @@ public class UsageTracker implements Listenable {
}
public boolean isRecentlyUsed() {
- long lastUsed = getSharedPrefs().getLong(mPrefKey, 0);
+ long lastUsed = Prefs.getLong(mContext, mPrefKey, 0L /* defaultValue */);
return (System.currentTimeMillis() - lastUsed) < mTimeToShowTile;
}
public void trackUsage() {
- getSharedPrefs().edit().putLong(mPrefKey, System.currentTimeMillis()).commit();
+ Prefs.putLong(mContext, mPrefKey, System.currentTimeMillis());
}
public void reset() {
- getSharedPrefs().edit().remove(mPrefKey).commit();
+ Prefs.remove(mContext, mPrefKey);
}
public void showResetConfirmation(String title, final Runnable onConfirmed) {
@@ -87,10 +89,6 @@ public class UsageTracker implements Listenable {
d.show();
}
- private SharedPreferences getSharedPrefs() {
- return mContext.getSharedPreferences(mContext.getPackageName(), 0);
- }
-
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 5963a45..4a33f55 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles;
import android.provider.Settings.Secure;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.SecureSetting;
@@ -50,7 +51,8 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
}
}
};
- mUsageTracker = new UsageTracker(host.getContext(), ColorInversionTile.class,
+ mUsageTracker = new UsageTracker(host.getContext(),
+ Prefs.Key.COLOR_INVERSION_TILE_LAST_USED, ColorInversionTile.class,
R.integer.days_to_show_color_inversion_tile);
if (mSetting.getValue() != 0 && !mUsageTracker.isRecentlyUsed()) {
mUsageTracker.trackUsage();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 64730c2..6ce63d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -29,6 +29,7 @@ import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -40,8 +41,6 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
private static final String ACTION_SET_VISIBLE = "com.android.systemui.dndtile.SET_VISIBLE";
private static final String EXTRA_VISIBLE = "visible";
- private static final String PREF_KEY_VISIBLE = "DndTileVisible";
- private static final String PREF_KEY_COMBINED_ICON = "DndTileCombinedIcon";
private final ZenModeController mController;
private final DndDetailAdapter mDetailAdapter;
@@ -57,19 +56,20 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
}
public static void setVisible(Context context, boolean visible) {
- getSharedPrefs(context).edit().putBoolean(PREF_KEY_VISIBLE, visible).commit();
+ Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible);
}
public static boolean isVisible(Context context) {
- return getSharedPrefs(context).getBoolean(PREF_KEY_VISIBLE, false);
+ return Prefs.getBoolean(context, Prefs.Key.DND_TILE_VISIBLE, false /* defaultValue */);
}
public static void setCombinedIcon(Context context, boolean combined) {
- getSharedPrefs(context).edit().putBoolean(PREF_KEY_COMBINED_ICON, combined).commit();
+ Prefs.putBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON, combined);
}
public static boolean isCombinedIcon(Context context) {
- return getSharedPrefs(context).getBoolean(PREF_KEY_COMBINED_ICON, false);
+ return Prefs.getBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON,
+ false /* defaultValue */);
}
@Override
@@ -85,9 +85,9 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
@Override
public void handleClick() {
if (mState.value) {
- mController.setZen(Global.ZEN_MODE_OFF);
+ mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
} else {
- mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
showDetail(true);
}
}
@@ -143,22 +143,20 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
mListening = listening;
if (mListening) {
mController.addCallback(mZenCallback);
- getSharedPrefs(mContext).registerOnSharedPreferenceChangeListener(mPrefListener);
+ Prefs.registerListener(mContext, mPrefListener);
} else {
mController.removeCallback(mZenCallback);
- getSharedPrefs(mContext).unregisterOnSharedPreferenceChangeListener(mPrefListener);
+ Prefs.unregisterListener(mContext, mPrefListener);
}
}
- private static SharedPreferences getSharedPrefs(Context context) {
- return context.getSharedPreferences(context.getPackageName(), 0);
- }
-
private final OnSharedPreferenceChangeListener mPrefListener
= new OnSharedPreferenceChangeListener() {
@Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (PREF_KEY_COMBINED_ICON.equals(key) || PREF_KEY_VISIBLE.equals(key)) {
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+ @Prefs.Key String key) {
+ if (Prefs.Key.DND_TILE_COMBINED_ICON.equals(key) ||
+ Prefs.Key.DND_TILE_VISIBLE.equals(key)) {
refreshState();
}
}
@@ -199,7 +197,7 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
@Override
public void setToggleState(boolean state) {
if (!state) {
- mController.setZen(Global.ZEN_MODE_OFF);
+ mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
showDetail(false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index fcc517f..6063f80 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -20,6 +20,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.qs.UsageTracker;
import com.android.systemui.qs.QSTile;
@@ -105,7 +106,8 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> {
}
private static UsageTracker newUsageTracker(Context context) {
- return new UsageTracker(context, HotspotTile.class, R.integer.days_to_show_hotspot_tile);
+ return new UsageTracker(context, Prefs.Key.HOTSPOT_TILE_LAST_USED, HotspotTile.class,
+ R.integer.days_to_show_hotspot_tile);
}
private final class Callback implements HotspotController.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 192acc6..c7f8919 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -59,8 +59,6 @@ public class Constants {
public static class Values {
public static class App {
public static int AppWidgetHostId = 1024;
- public static String Key_SearchAppWidgetId = "searchAppWidgetId";
- public static String Key_DebugModeEnabled = "debugModeEnabled";
public static String DebugModeVersion = "A";
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 011c02e..1001feb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -25,7 +25,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -34,6 +33,8 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.ViewStub;
import android.widget.Toast;
+
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.misc.DebugTrigger;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
@@ -565,10 +566,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
/** Called when debug mode is triggered */
public void onDebugModeTriggered() {
if (mConfig.developerOptionsEnabled) {
- SharedPreferences settings = getSharedPreferences(getPackageName(), 0);
- if (settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false)) {
+ if (Prefs.getBoolean(this, Prefs.Key.DEBUG_MODE_ENABLED, false /* boolean */)) {
// Disable the debug mode
- settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply();
+ Prefs.remove(this, Prefs.Key.DEBUG_MODE_ENABLED);
mConfig.debugModeEnabled = false;
inflateDebugOverlay();
if (mDebugOverlay != null) {
@@ -576,7 +576,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
}
} else {
// Enable the debug mode
- settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply();
+ Prefs.putBoolean(this, Prefs.Key.DEBUG_MODE_ENABLED, true);
mConfig.debugModeEnabled = true;
inflateDebugOverlay();
if (mDebugOverlay != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index abeb2b0..244fada 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -18,7 +18,6 @@ package com.android.systemui.recents;
import android.app.ActivityManager;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -26,6 +25,8 @@ import android.provider.Settings;
import android.util.DisplayMetrics;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -177,12 +178,12 @@ public class RecentsConfiguration {
/** Updates the state, given the specified context */
void update(Context context) {
- SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
Resources res = context.getResources();
DisplayMetrics dm = res.getDisplayMetrics();
// Debug mode
- debugModeEnabled = settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false);
+ debugModeEnabled = Prefs.getBoolean(context, Prefs.Key.DEBUG_MODE_ENABLED,
+ false /* defaultValue */);
if (debugModeEnabled) {
Console.Enabled = true;
}
@@ -206,7 +207,8 @@ public class RecentsConfiguration {
// Search Bar
searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height);
- searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1);
+ searchBarAppWidgetId = Prefs.getInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID,
+ -1 /* defaultValue */);
// Task stack
taskStackScrollDuration =
@@ -280,9 +282,7 @@ public class RecentsConfiguration {
/** Updates the search bar app widget */
public void updateSearchBarAppWidgetId(Context context, int appWidgetId) {
searchBarAppWidgetId = appWidgetId;
- SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
- settings.edit().putInt(Constants.Values.App.Key_SearchAppWidgetId,
- appWidgetId).apply();
+ Prefs.putInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, appWidgetId);
}
/** Updates the states that need to be re-read whenever we re-initialize. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index f75dd73..d2837b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1628,7 +1628,7 @@ public abstract class BaseStatusBar extends SystemUI implements
// close the shade if it was open
animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */);
+ true /* force */, true /* delayed */);
visibilityChanged(false);
return mIntent != null && mIntent.isActivity();
@@ -1640,6 +1640,9 @@ public abstract class BaseStatusBar extends SystemUI implements
public void animateCollapsePanels(int flags, boolean force) {
}
+ public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
+ }
+
public void overrideActivityPendingAppTransition(boolean keyguardShowing) {
if (keyguardShowing) {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
index e2464c2..583184f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
@@ -34,6 +34,7 @@ import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.KeyguardAffordanceHelper;
/**
* An ImageView which does not have overlapping renderings commands and therefore does not need a
@@ -75,6 +76,7 @@ public class KeyguardAffordanceView extends ImageView {
private float mCircleStartRadius;
private float mMaxCircleSize;
private Animator mPreviewClipper;
+ private float mRestingAlpha = KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT;
private AnimatorListenerAdapter mClipEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -394,6 +396,17 @@ public class KeyguardAffordanceView extends ImageView {
}
}
+ public void setRestingAlpha(float alpha) {
+ mRestingAlpha = alpha;
+
+ // TODO: Handle the case an animation is playing.
+ setImageAlpha(alpha, false);
+ }
+
+ public float getRestingAlpha() {
+ return mRestingAlpha;
+ }
+
public void setImageAlpha(float alpha, boolean animate) {
setImageAlpha(alpha, animate, -1, null, null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 58067c3..07a055c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -26,6 +26,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Color;
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Handler;
@@ -53,6 +54,7 @@ public class KeyguardIndicationController {
private String mRestingIndication;
private String mTransientIndication;
+ private int mTransientTextColor;
private boolean mVisible;
private boolean mPowerPluggedIn;
@@ -105,7 +107,15 @@ public class KeyguardIndicationController {
* Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
*/
public void showTransientIndication(String transientIndication) {
+ showTransientIndication(transientIndication, Color.WHITE);
+ }
+
+ /**
+ * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
+ */
+ public void showTransientIndication(String transientIndication, int textColor) {
mTransientIndication = transientIndication;
+ mTransientTextColor = textColor;
mHandler.removeMessages(MSG_HIDE_TRANSIENT);
updateIndication();
}
@@ -124,7 +134,15 @@ public class KeyguardIndicationController {
private void updateIndication() {
if (mVisible) {
mTextView.switchIndication(computeIndication());
+ mTextView.setTextColor(computeColor());
+ }
+ }
+
+ private int computeColor() {
+ if (!TextUtils.isEmpty(mTransientIndication)) {
+ return mTransientTextColor;
}
+ return Color.WHITE;
}
private String computeIndication() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index cab152f..9d892f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -83,9 +83,9 @@ public class KeyguardAffordanceHelper {
mContext = context;
mCallback = callback;
initIcons();
- updateIcon(mLeftIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false, false);
- updateIcon(mCenterIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false, false);
- updateIcon(mRightIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false, false);
+ updateIcon(mLeftIcon, 0.0f, mLeftIcon.getRestingAlpha(), false, false);
+ updateIcon(mCenterIcon, 0.0f, mCenterIcon.getRestingAlpha(), false, false);
+ updateIcon(mRightIcon, 0.0f, mRightIcon.getRestingAlpha(), false, false);
initDimens();
}
@@ -344,22 +344,23 @@ public class KeyguardAffordanceHelper {
float alpha = absTranslation / getMinTranslationAmount();
// We interpolate the alpha of the other icons to 0
- float fadeOutAlpha = SWIPE_RESTING_ALPHA_AMOUNT * (1.0f - alpha);
- fadeOutAlpha = Math.max(0.0f, fadeOutAlpha);
-
- // We interpolate the alpha of the targetView to 1
- alpha = fadeOutAlpha + alpha;
+ float fadeOutAlpha = 1.0f - alpha;
+ fadeOutAlpha = Math.min(1.0f, Math.max(0.0f, fadeOutAlpha));
boolean animateIcons = isReset && animateReset;
float radius = getRadiusFromTranslation(absTranslation);
boolean slowAnimation = isReset && isBelowFalsingThreshold();
if (!isReset) {
- updateIcon(targetView, radius, alpha, false, false);
+ updateIcon(targetView, radius, alpha + fadeOutAlpha * targetView.getRestingAlpha(),
+ false, false);
} else {
- updateIcon(targetView, 0.0f, fadeOutAlpha, animateIcons, slowAnimation);
+ updateIcon(targetView, 0.0f, fadeOutAlpha * targetView.getRestingAlpha(),
+ animateIcons, slowAnimation);
}
- updateIcon(otherView, 0.0f, fadeOutAlpha, animateIcons, slowAnimation);
- updateIcon(mCenterIcon, 0.0f, fadeOutAlpha, animateIcons, slowAnimation);
+ updateIcon(otherView, 0.0f, fadeOutAlpha * otherView.getRestingAlpha(),
+ animateIcons, slowAnimation);
+ updateIcon(mCenterIcon, 0.0f, fadeOutAlpha * mCenterIcon.getRestingAlpha(),
+ animateIcons, slowAnimation);
mTranslation = translation;
}
@@ -369,15 +370,14 @@ public class KeyguardAffordanceHelper {
float alpha = newRadius / mMinBackgroundRadius;
// We interpolate the alpha of the other icons to 0
- float fadeOutAlpha = SWIPE_RESTING_ALPHA_AMOUNT * (1.0f - alpha);
+ float fadeOutAlpha = 1.0f - alpha;
fadeOutAlpha = Math.max(0.0f, fadeOutAlpha);
// We interpolate the alpha of the targetView to 1
- alpha = fadeOutAlpha + alpha;
KeyguardAffordanceView otherView = targetView == mRightIcon ? mLeftIcon : mRightIcon;
- updateIconAlpha(targetView, alpha, false);
- updateIconAlpha(otherView, fadeOutAlpha, false);
- updateIconAlpha(mCenterIcon, fadeOutAlpha, false);
+ updateIconAlpha(targetView, alpha + fadeOutAlpha * targetView.getRestingAlpha(), false);
+ updateIconAlpha(otherView, fadeOutAlpha * otherView.getRestingAlpha(), false);
+ updateIconAlpha(mCenterIcon, fadeOutAlpha * mCenterIcon.getRestingAlpha(), false);
}
private float getTranslationFromRadius(float circleSize) {
@@ -404,14 +404,14 @@ public class KeyguardAffordanceHelper {
}
private void updateIconAlpha(KeyguardAffordanceView view, float alpha, boolean animate) {
- float scale = getScale(alpha);
+ float scale = getScale(alpha, view);
alpha = Math.min(1.0f, alpha);
view.setImageAlpha(alpha, animate);
view.setImageScale(scale, animate);
}
- private float getScale(float alpha) {
- float scale = alpha / SWIPE_RESTING_ALPHA_AMOUNT * 0.2f +
+ private float getScale(float alpha, KeyguardAffordanceView icon) {
+ float scale = alpha / icon.getRestingAlpha() * 0.2f +
KeyguardAffordanceView.MIN_ICON_SCALE_AMOUNT;
return Math.min(scale, KeyguardAffordanceView.MAX_ICON_SCALE_AMOUNT);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 628ae84..410a7e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -25,13 +25,16 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.Vibrator;
import android.provider.MediaStore;
+import android.provider.Settings;
import android.telecom.TelecomManager;
import android.util.AttributeSet;
import android.util.Log;
@@ -78,6 +81,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
+ private static final long TRANSIENT_FP_ERROR_TIMEOUT = 1300;
+ private static final long[] FP_ERROR_VIBRATE_PATTERN = new long[] {0, 30, 100, 30};
+ private static final long[] FP_SUCCESS_VIBRATE_PATTERN = new long[] {0, 30};
private KeyguardAffordanceView mCameraImageView;
private KeyguardAffordanceView mPhoneImageView;
@@ -101,6 +107,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private final Interpolator mLinearOutSlowInInterpolator;
private int mLastUnlockIconRes = 0;
private boolean mPrewarmSent;
+ private boolean mTransientFpError;
public KeyguardBottomAreaView(Context context) {
this(context, null);
@@ -431,28 +438,41 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
return;
}
// TODO: Real icon for facelock.
- int iconRes = mUnlockMethodCache.isFaceUnlockRunning()
- ? com.android.internal.R.drawable.ic_account_circle
+ boolean isFingerprintIcon =
+ KeyguardUpdateMonitor.getInstance(mContext).isFingerprintDetectionRunning();
+ boolean anyFingerprintIcon = isFingerprintIcon || mTransientFpError;
+ int iconRes = mTransientFpError ? R.drawable.ic_fingerprint_error
+ : isFingerprintIcon ? R.drawable.ic_fingerprint
+ : mUnlockMethodCache.isFaceUnlockRunning()
+ ? com.android.internal.R.drawable.ic_account_circle
: mUnlockMethodCache.isCurrentlyInsecure() ? R.drawable.ic_lock_open_24dp
: R.drawable.ic_lock_24dp;
+
if (mLastUnlockIconRes != iconRes) {
Drawable icon = mContext.getDrawable(iconRes);
int iconHeight = getResources().getDimensionPixelSize(
R.dimen.keyguard_affordance_icon_height);
int iconWidth = getResources().getDimensionPixelSize(
R.dimen.keyguard_affordance_icon_width);
- if (icon.getIntrinsicHeight() != iconHeight || icon.getIntrinsicWidth() != iconWidth) {
+ if (!anyFingerprintIcon && (icon.getIntrinsicHeight() != iconHeight
+ || icon.getIntrinsicWidth() != iconWidth)) {
icon = new IntrinsicSizeDrawable(icon, iconWidth, iconHeight);
}
mLockIcon.setImageDrawable(icon);
+ mLockIcon.setPaddingRelative(0, 0, 0, anyFingerprintIcon
+ ? getResources().getDimensionPixelSize(
+ R.dimen.fingerprint_icon_additional_padding)
+ : 0);
+ mLockIcon.setRestingAlpha(
+ anyFingerprintIcon ? 1f : KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT);
}
- boolean trustManaged = mUnlockMethodCache.isTrustManaged();
+
+ // Hide trust circle when fingerprint is running.
+ boolean trustManaged = mUnlockMethodCache.isTrustManaged() && !anyFingerprintIcon;
mTrustDrawable.setTrustManaged(trustManaged);
updateLockIconClickability();
}
-
-
public KeyguardAffordanceView getPhoneView() {
return mPhoneImageView;
}
@@ -530,6 +550,14 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
.setDuration(DOZE_ANIMATION_ELEMENT_DURATION);
}
+ private void vibrateFingerprintError() {
+ mContext.getSystemService(Vibrator.class).vibrate(FP_ERROR_VIBRATE_PATTERN, -1);
+ }
+
+ private void vibrateFingerprintSuccess() {
+ mContext.getSystemService(Vibrator.class).vibrate(FP_SUCCESS_VIBRATE_PATTERN, -1);
+ }
+
private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
post(new Runnable() {
@@ -541,6 +569,15 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
};
+ private final Runnable mTransientFpErrorClearRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mTransientFpError = false;
+ mIndicationController.hideTransientIndication();
+ updateLockIcon();
+ }
+ };
+
private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
@Override
@@ -562,6 +599,33 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
public void onKeyguardVisibilityChanged(boolean showing) {
updateLockIcon();
}
+
+ @Override
+ public void onFingerprintAuthenticated(int userId) {
+ vibrateFingerprintSuccess();
+ }
+
+ @Override
+ public void onFingerprintRunningStateChanged(boolean running) {
+ updateLockIcon();
+ }
+
+ @Override
+ public void onFingerprintHelp(int msgId, String helpString) {
+ vibrateFingerprintError();
+ mTransientFpError = true;
+ mIndicationController.showTransientIndication(helpString,
+ getResources().getColor(R.color.system_warning_color, null));
+ removeCallbacks(mTransientFpErrorClearRunnable);
+ postDelayed(mTransientFpErrorClearRunnable, TRANSIENT_FP_ERROR_TIMEOUT);
+ updateLockIcon();
+ }
+
+ @Override
+ public void onFingerprintError(int msgId, String errString) {
+ // TODO: Go to bouncer if this is "too many attempts" (lockout) error.
+ Log.i(TAG, "FP Error: " + errString);
+ }
};
public void setKeyguardIndicationController(
@@ -569,7 +633,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
mIndicationController = keyguardIndicationController;
}
-
/**
* A wrapper around another Drawable that overrides the intrinsic size.
*/
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 216730b..02b6c19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -1505,7 +1505,7 @@ public class NotificationPanelView extends PanelView implements
lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150,
mFastOutLinearInterpolator);
} else if (!active && mUnlockIconActive && mTracking) {
- lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true,
+ lockIcon.setImageAlpha(lockIcon.getRestingAlpha(), true /* animate */,
150, mFastOutLinearInterpolator, null);
lockIcon.setImageScale(1.0f, true, 150,
mFastOutLinearInterpolator);
@@ -1796,8 +1796,8 @@ public class NotificationPanelView extends PanelView implements
mFastOutSlowInInterpolator, new Runnable() {
@Override
public void run() {
- icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT,
- true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
+ icon.setImageAlpha(icon.getRestingAlpha(),
+ true /* animate */, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
mFastOutSlowInInterpolator, null);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index 3efaaff..c6e1be9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -187,11 +187,11 @@ public class PanelBar extends FrameLayout {
(fullyOpenedPanel!=null)?" fullyOpened":"", fullyClosed?" fullyClosed":"");
}
- public void collapseAllPanels(boolean animate) {
+ public void collapseAllPanels(boolean animate, boolean delayed) {
boolean waiting = false;
for (PanelView pv : mPanels) {
if (animate && !pv.isFullyCollapsed()) {
- pv.collapse(true /* delayed */);
+ pv.collapse(delayed);
waiting = true;
} else {
pv.resetViews();
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 ad78f6a..7c199cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -113,6 +113,7 @@ import com.android.systemui.BatteryMeterView;
import com.android.systemui.DemoMode;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
@@ -1991,10 +1992,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
public void animateCollapsePanels(int flags) {
- animateCollapsePanels(flags, false /* force */);
+ animateCollapsePanels(flags, false /* force */, false /* delayed */);
}
public void animateCollapsePanels(int flags, boolean force) {
+ animateCollapsePanels(flags, force, false /* delayed*/);
+ }
+
+ public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
if (!force &&
(mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)) {
runPostCollapseRunnables();
@@ -2018,7 +2023,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mStatusBarWindowManager.setStatusBarFocusable(false);
mStatusBarWindow.cancelExpandHelper();
- mStatusBarView.collapseAllPanels(true);
+ mStatusBarView.collapseAllPanels(true /* animate */, delayed);
}
}
@@ -2061,7 +2066,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
public void animateCollapseQuickSettings() {
if (mState == StatusBarState.SHADE) {
- mStatusBarView.collapseAllPanels(true);
+ mStatusBarView.collapseAllPanels(true, false /* delayed */);
}
}
@@ -2074,7 +2079,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
// Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
- mStatusBarView.collapseAllPanels(/*animate=*/ false);
+ mStatusBarView.collapseAllPanels(/*animate=*/ false, false /* delayed*/);
// reset things to their proper state
mStackScroller.setVisibility(View.VISIBLE);
@@ -2168,7 +2173,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mStatusBarWindowState = state;
if (DEBUG_WINDOW_STATE) Log.d(TAG, "Status bar " + windowStateToString(state));
if (!showing && mState == StatusBarState.SHADE) {
- mStatusBarView.collapseAllPanels(false);
+ mStatusBarView.collapseAllPanels(false /* animate */, false /* delayed */);
}
}
if (mNavigationBarView != null
@@ -2573,8 +2578,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
pw.println("SharedPreferences:");
- for (Map.Entry<String, ?> entry : mContext.getSharedPreferences(mContext.getPackageName(),
- Context.MODE_PRIVATE).getAll().entrySet()) {
+ for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) {
pw.print(" "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue());
}
}
@@ -2643,8 +2647,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
});
if (dismissShade) {
- animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+ true /* delayed*/);
}
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
index 0e21457..67cc788 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
@@ -17,16 +17,19 @@
package com.android.systemui.statusbar.policy;
import android.content.ComponentName;
+import android.net.Uri;
import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
public interface ZenModeController {
void addCallback(Callback callback);
void removeCallback(Callback callback);
- void setZen(int zen);
+ void setZen(int zen, Uri conditionId, String reason);
int getZen();
void requestConditions(boolean request);
- void setExitCondition(Condition exitCondition);
- Condition getExitCondition();
+ ZenRule getManualRule();
+ ZenModeConfig getConfig();
long getNextAlarm();
void setUserId(int userId);
boolean isZenAvailable();
@@ -35,10 +38,11 @@ public interface ZenModeController {
public static class Callback {
public void onZenChanged(int zen) {}
- public void onExitConditionChanged(Condition exitCondition) {}
public void onConditionsChanged(Condition[] conditions) {}
public void onNextAlarmChanged() {}
public void onZenAvailableChanged(boolean available) {}
public void onEffectsSupressorChanged() {}
+ public void onManualRuleChanged(ZenRule rule) {}
+ public void onConfigChanged(ZenModeConfig config) {}
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index bea0c86..830a197 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -33,6 +33,7 @@ import android.provider.Settings.Secure;
import android.service.notification.Condition;
import android.service.notification.IConditionListener;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
import android.util.Log;
import android.util.Slog;
@@ -40,6 +41,7 @@ import com.android.systemui.qs.GlobalSetting;
import java.util.ArrayList;
import java.util.LinkedHashMap;
+import java.util.Objects;
/** Platform implementation of the zen mode controller. **/
public class ZenModeControllerImpl implements ZenModeController {
@@ -58,6 +60,7 @@ public class ZenModeControllerImpl implements ZenModeController {
private int mUserId;
private boolean mRequesting;
private boolean mRegistered;
+ private ZenModeConfig mConfig;
public ZenModeControllerImpl(Context context, Handler handler) {
mContext = context;
@@ -70,12 +73,13 @@ public class ZenModeControllerImpl implements ZenModeController {
mConfigSetting = new GlobalSetting(mContext, handler, Global.ZEN_MODE_CONFIG_ETAG) {
@Override
protected void handleValueChanged(int value) {
- fireExitConditionChanged();
+ updateZenModeConfig();
}
};
+ mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ mConfig = mNoMan.getZenModeConfig();
mModeSetting.setListening(true);
mConfigSetting.setListening(true);
- mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mSetupObserver = new SetupObserver(handler);
mSetupObserver.register();
@@ -97,8 +101,8 @@ public class ZenModeControllerImpl implements ZenModeController {
}
@Override
- public void setZen(int zen) {
- mModeSetting.setValue(zen);
+ public void setZen(int zen, Uri conditionId, String reason) {
+ mNoMan.setZenMode(zen, conditionId, reason);
}
@Override
@@ -116,13 +120,13 @@ public class ZenModeControllerImpl implements ZenModeController {
}
@Override
- public void setExitCondition(Condition exitCondition) {
- mNoMan.setZenModeCondition(exitCondition);
+ public ZenRule getManualRule() {
+ return mConfig == null ? null : mConfig.manualRule;
}
@Override
- public Condition getExitCondition() {
- return mNoMan.getZenModeCondition();
+ public ZenModeConfig getConfig() {
+ return mConfig;
}
@Override
@@ -185,11 +189,15 @@ public class ZenModeControllerImpl implements ZenModeController {
}
}
- private void fireExitConditionChanged() {
- final Condition exitCondition = getExitCondition();
- if (DEBUG) Slog.d(TAG, "exitCondition changed: " + exitCondition);
+ private void fireManualRuleChanged(ZenRule rule) {
+ for (Callback cb : mCallbacks) {
+ cb.onManualRuleChanged(rule);
+ }
+ }
+
+ private void fireConfigChanged(ZenModeConfig config) {
for (Callback cb : mCallbacks) {
- cb.onExitConditionChanged(exitCondition);
+ cb.onConfigChanged(config);
}
}
@@ -203,6 +211,17 @@ public class ZenModeControllerImpl implements ZenModeController {
mConditions.values().toArray(new Condition[mConditions.values().size()]));
}
+ private void updateZenModeConfig() {
+ final ZenModeConfig config = mNoMan.getZenModeConfig();
+ if (Objects.equals(config, mConfig)) return;
+ final ZenRule oldRule = mConfig != null ? mConfig.manualRule : null;
+ mConfig = config;
+ fireConfigChanged(config);
+ final ZenRule newRule = config != null ? config.manualRule : null;
+ if (Objects.equals(oldRule, newRule)) return;
+ fireManualRuleChanged(newRule);
+ }
+
private final IConditionListener mListener = new IConditionListener.Stub() {
@Override
public void onConditionsReceived(Condition[] conditions) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Util.java b/packages/SystemUI/src/com/android/systemui/volume/Util.java
index 78baf67..216a4da 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Util.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Util.java
@@ -21,7 +21,6 @@ import android.media.MediaMetadata;
import android.media.VolumeProvider;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.PlaybackState;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
import android.view.View;
import android.widget.TextView;
@@ -145,10 +144,6 @@ class Util {
return HMMAA.format(new Date(millis));
}
- public static String getShortTime(DowntimeInfo info) {
- return ((info.endHour + 1) % 12) + ":" + (info.endMinute < 10 ? " " : "") + info.endMinute;
- }
-
public static void setText(TextView tv, CharSequence text) {
if (Objects.equals(tv.getText(), text)) return;
tv.setText(text);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index d8b3965..539fec8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -38,7 +38,6 @@ import android.os.Message;
import android.os.SystemClock;
import android.provider.Settings.Global;
import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseBooleanArray;
@@ -606,14 +605,6 @@ public class VolumeDialog {
text = mContext.getString(R.string.volume_dnd_ends_at,
Util.getShortTime(countdown));
action = mContext.getString(R.string.volume_end_now);
- } else {
- final DowntimeInfo info = ZenModeConfig.tryParseDowntimeConditionId(mState.
- exitCondition.id);
- if (info != null) {
- text = mContext.getString(R.string.volume_dnd_ends_at,
- Util.getShortTime(info));
- action = mContext.getString(R.string.volume_end_now);
- }
}
}
if (text == null) {
@@ -700,7 +691,8 @@ public class VolumeDialog {
final int iconRes =
isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
: ss.routedToBluetooth ?
- (ss.muted ? R.drawable.ic_volume_bt_mute : R.drawable.ic_volume_bt)
+ (ss.muted ? R.drawable.ic_volume_media_bt_mute
+ : R.drawable.ic_volume_media_bt)
: mAutomute && ss.level == 0 ? row.iconMuteRes
: (ss.muted ? row.iconMuteRes : row.iconRes);
if (iconRes != row.cachedIconRes) {
@@ -712,9 +704,9 @@ public class VolumeDialog {
}
row.iconState =
iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
- : (iconRes == R.drawable.ic_volume_bt_mute || iconRes == row.iconMuteRes)
+ : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
? Events.ICON_STATE_MUTE
- : (iconRes == R.drawable.ic_volume_bt || iconRes == row.iconRes)
+ : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes)
? Events.ICON_STATE_UNMUTE
: Events.ICON_STATE_UNKNOWN;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index 265e2c6..5bc8c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -41,6 +41,7 @@ import android.os.RemoteException;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
import android.util.Log;
import android.util.SparseArray;
@@ -393,8 +394,15 @@ public class VolumeDialogController {
return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
}
+ private Condition getExitCondition() {
+ final ZenModeConfig config = mNoMan.getZenModeConfig();
+ return config == null ? null
+ : config.manualRule == null ? null
+ : config.manualRule.condition;
+ }
+
private boolean updateExitConditionW() {
- final Condition exitCondition = mNoMan.getZenModeCondition();
+ final Condition exitCondition = getExitCondition();
if (Objects.equals(mState.exitCondition, exitCondition)) return false;
mState.exitCondition = exitCondition;
return true;
@@ -476,12 +484,12 @@ public class VolumeDialogController {
}
private void onSetExitConditionW(Condition condition) {
- mNoMan.setZenModeCondition(condition);
+ mNoMan.setZenMode(mState.zenMode, condition != null ? condition.id : null, TAG);
}
private void onSetZenModeW(int mode) {
if (D.BUG) Log.d(TAG, "onSetZenModeW " + mode);
- mNoMan.setZenMode(mode);
+ mNoMan.setZenMode(mode, null, TAG);
}
private void onDismissRequestedW(int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
index f99eb6d..ef8257c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
@@ -17,10 +17,11 @@ package com.android.systemui.volume;
import android.animation.LayoutTransition;
import android.animation.ValueAnimator;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.provider.Settings.Global;
-import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
@@ -34,6 +35,8 @@ import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.ZenModeController;
+import java.util.Objects;
+
/**
* Switch bar + zen mode panel (conditions) attached to the bottom of the volume dialog.
*/
@@ -57,6 +60,7 @@ public class ZenFooter extends LinearLayout {
private TextView mSummaryLine2;
private boolean mFooterExpanded;
private int mZen = -1;
+ private ZenModeConfig mConfig;
private Callback mCallback;
public ZenFooter(Context context, AttributeSet attrs) {
@@ -102,8 +106,8 @@ public class ZenFooter extends LinearLayout {
setZen(zen);
}
@Override
- public void onExitConditionChanged(Condition exitCondition) {
- update();
+ public void onConfigChanged(ZenModeConfig config) {
+ setConfig(config);
}
});
mSwitchBar.setOnClickListener(new OnClickListener() {
@@ -129,6 +133,7 @@ public class ZenFooter extends LinearLayout {
}
});
mZen = mController.getZen();
+ mConfig = mController.getConfig();
update();
}
@@ -138,6 +143,12 @@ public class ZenFooter extends LinearLayout {
update();
}
+ private void setConfig(ZenModeConfig config) {
+ if (Objects.equals(mConfig, config)) return;
+ mConfig = config;
+ update();
+ }
+
public boolean isZen() {
return isZenPriority() || isZenAlarms() || isZenNone();
}
@@ -196,7 +207,9 @@ public class ZenFooter extends LinearLayout {
: isZenNone() ? mContext.getString(R.string.interruption_level_none)
: null;
Util.setText(mSummaryLine1, line1);
- Util.setText(mSummaryLine2, mZenModePanel.getExitConditionText());
+ final String line2 = ZenModeConfig.getConditionSummary(mContext, mConfig,
+ ActivityManager.getCurrentUser());
+ Util.setText(mSummaryLine2, line2);
}
private final OnCheckedChangeListener mCheckedListener = new OnCheckedChangeListener() {
@@ -208,7 +221,7 @@ public class ZenFooter extends LinearLayout {
: Global.ZEN_MODE_OFF;
mZen = newZen; // this one's optimistic
setFooterExpanded(isChecked);
- mController.setZen(newZen);
+ mController.setZen(newZen, null, TAG);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index cb6c29f..f6d4c36 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -32,6 +32,7 @@ import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AttributeSet;
@@ -157,7 +158,6 @@ public class ZenModePanel extends LinearLayout {
}
mZenButtons.getChildAt(3).setVisibility(mEmbedded ? GONE : VISIBLE);
mZenEmbeddedDivider.setVisibility(mEmbedded ? VISIBLE : GONE);
- setExpanded(mEmbedded);
updateWidgets();
}
@@ -278,7 +278,7 @@ public class ZenModePanel extends LinearLayout {
if (expanded == mExpanded) return;
if (DEBUG) Log.d(mTag, "setExpanded " + expanded);
mExpanded = expanded;
- if (mExpanded) {
+ if (mExpanded && isShown()) {
ensureSelection();
}
updateWidgets();
@@ -299,7 +299,7 @@ public class ZenModePanel extends LinearLayout {
});
}
if (mRequestingConditions) {
- mTimeCondition = parseExistingTimeCondition(mExitCondition);
+ mTimeCondition = parseExistingTimeCondition(mContext, mExitCondition);
if (mTimeCondition != null) {
mBucketIndex = -1;
} else {
@@ -327,10 +327,9 @@ public class ZenModePanel extends LinearLayout {
for (int i = 0; i < mMaxConditions; i++) {
mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
}
- setExitCondition(mController.getExitCondition());
refreshExitConditionText();
mSessionZen = getSelectedZen(-1);
- handleUpdateZen(mController.getZen());
+ handleUpdateManualRule(mController.getManualRule());
if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
hideAllConditions();
mController.addCallback(mZenCallback);
@@ -352,6 +351,10 @@ public class ZenModePanel extends LinearLayout {
return condition != null ? condition.id : null;
}
+ private Uri getRealConditionId(Condition condition) {
+ return isForever(condition) ? null : getConditionId(condition);
+ }
+
private static boolean sameConditionId(Condition lhs, Condition rhs) {
return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id);
}
@@ -360,18 +363,18 @@ public class ZenModePanel extends LinearLayout {
return condition == null ? null : condition.copy();
}
- public String getExitConditionText() {
- return mExitConditionText;
+ private void refreshExitConditionText() {
+ mExitConditionText = getExitConditionText(mContext, mExitCondition);
}
- private void refreshExitConditionText() {
- if (mExitCondition == null) {
- mExitConditionText = foreverSummary();
- } else if (isCountdown(mExitCondition)) {
- final Condition condition = parseExistingTimeCondition(mExitCondition);
- mExitConditionText = condition != null ? condition.summary : foreverSummary();
+ public static String getExitConditionText(Context context, Condition exitCondition) {
+ if (exitCondition == null) {
+ return foreverSummary(context);
+ } else if (isCountdown(exitCondition)) {
+ final Condition condition = parseExistingTimeCondition(context, exitCondition);
+ return condition != null ? condition.summary : foreverSummary(context);
} else {
- mExitConditionText = mExitCondition.summary;
+ return exitCondition.summary;
}
}
@@ -386,9 +389,16 @@ public class ZenModePanel extends LinearLayout {
mIconPulser.start(noneButton);
}
+ private void handleUpdateManualRule(ZenRule rule) {
+ final int zen = rule != null ? rule.zenMode : Global.ZEN_MODE_OFF;
+ handleUpdateZen(zen);
+ final Condition c = rule != null ? rule.condition : null;
+ handleExitConditionChanged(c);
+ }
+
private void handleUpdateZen(int zen) {
if (mSessionZen != -1 && mSessionZen != zen) {
- setExpanded(mEmbedded || zen != Global.ZEN_MODE_OFF);
+ setExpanded(mEmbedded && isShown() || !mEmbedded && zen != Global.ZEN_MODE_OFF);
mSessionZen = zen;
}
mZenButtons.setSelectedValue(zen);
@@ -402,6 +412,20 @@ public class ZenModePanel extends LinearLayout {
}
}
+ private void handleExitConditionChanged(Condition exitCondition) {
+ setExitCondition(exitCondition);
+ if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
+ final int N = getVisibleConditions();
+ for (int i = 0; i < N; i++) {
+ final ConditionTag tag = getConditionTagAt(i);
+ if (tag != null) {
+ if (sameConditionId(tag.condition, mExitCondition)) {
+ bind(exitCondition, mZenConditions.getChildAt(i));
+ }
+ }
+ }
+ }
+
private Condition getSelectedCondition() {
final int N = getVisibleConditions();
for (int i = 0; i < N; i++) {
@@ -447,14 +471,14 @@ public class ZenModePanel extends LinearLayout {
? mSubheadWarningColor : mSubheadColor);
}
- private Condition parseExistingTimeCondition(Condition condition) {
+ private static Condition parseExistingTimeCondition(Context context, Condition condition) {
if (condition == null) return null;
final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id);
if (time == 0) return null;
final long now = System.currentTimeMillis();
final long span = time - now;
if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null;
- return ZenModeConfig.toTimeCondition(mContext,
+ return ZenModeConfig.toTimeCondition(context,
time, Math.round(span / (float) MINUTES_MS), now, ActivityManager.getCurrentUser());
}
@@ -514,18 +538,18 @@ public class ZenModePanel extends LinearLayout {
mZenConditions.getChildAt(i).setVisibility(GONE);
}
// ensure something is selected
- if (mExpanded) {
+ if (mExpanded && isShown()) {
ensureSelection();
}
}
private Condition forever() {
- return new Condition(mForeverId, foreverSummary(), "", "", 0 /*icon*/, Condition.STATE_TRUE,
- 0 /*flags*/);
+ return new Condition(mForeverId, foreverSummary(mContext), "", "", 0 /*icon*/,
+ Condition.STATE_TRUE, 0 /*flags*/);
}
- private String foreverSummary() {
- return mContext.getString(com.android.internal.R.string.zen_mode_forever);
+ private static String foreverSummary(Context context) {
+ return context.getString(com.android.internal.R.string.zen_mode_forever);
}
private ConditionTag getConditionTagAt(int index) {
@@ -574,21 +598,7 @@ public class ZenModePanel extends LinearLayout {
}
}
- private void handleExitConditionChanged(Condition exitCondition) {
- setExitCondition(exitCondition);
- if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
- final int N = getVisibleConditions();
- for (int i = 0; i < N; i++) {
- final ConditionTag tag = getConditionTagAt(i);
- if (tag != null) {
- if (sameConditionId(tag.condition, mExitCondition)) {
- bind(exitCondition, mZenConditions.getChildAt(i));
- }
- }
- }
- }
-
- private boolean isCountdown(Condition c) {
+ private static boolean isCountdown(Condition c) {
return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
}
@@ -770,17 +780,21 @@ public class ZenModePanel extends LinearLayout {
private void select(final Condition condition) {
if (DEBUG) Log.d(mTag, "select " + condition);
- final boolean isForever = isForever(condition);
+ if (mSessionZen == -1 || mSessionZen == Global.ZEN_MODE_OFF) {
+ if (DEBUG) Log.d(mTag, "Ignoring condition selection outside of manual zen");
+ return;
+ }
+ final Uri realConditionId = getRealConditionId(condition);
if (mController != null) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
- mController.setExitCondition(isForever ? null : condition);
+ mController.setZen(mSessionZen, realConditionId, TAG + ".selectCondition");
}
});
}
setExitCondition(condition);
- if (isForever) {
+ if (realConditionId == null) {
mPrefs.setMinuteIndex(-1);
} else if (isCountdown(condition) && mBucketIndex != -1) {
mPrefs.setMinuteIndex(mBucketIndex);
@@ -808,24 +822,19 @@ public class ZenModePanel extends LinearLayout {
private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
@Override
- public void onZenChanged(int zen) {
- mHandler.obtainMessage(H.UPDATE_ZEN, zen, 0).sendToTarget();
- }
- @Override
public void onConditionsChanged(Condition[] conditions) {
mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget();
}
@Override
- public void onExitConditionChanged(Condition exitCondition) {
- mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget();
+ public void onManualRuleChanged(ZenRule rule) {
+ mHandler.obtainMessage(H.MANUAL_RULE_CHANGED, rule).sendToTarget();
}
};
private final class H extends Handler {
private static final int UPDATE_CONDITIONS = 1;
- private static final int EXIT_CONDITION_CHANGED = 2;
- private static final int UPDATE_ZEN = 3;
+ private static final int MANUAL_RULE_CHANGED = 2;
private H() {
super(Looper.getMainLooper());
@@ -835,10 +844,8 @@ public class ZenModePanel extends LinearLayout {
public void handleMessage(Message msg) {
if (msg.what == UPDATE_CONDITIONS) {
handleUpdateConditions((Condition[]) msg.obj);
- } else if (msg.what == EXIT_CONDITION_CHANGED) {
- handleExitConditionChanged((Condition) msg.obj);
- } else if (msg.what == UPDATE_ZEN) {
- handleUpdateZen(msg.arg1);
+ } else if (msg.what == MANUAL_RULE_CHANGED) {
+ handleUpdateManualRule((ZenRule) msg.obj);
}
}
}
@@ -930,12 +937,13 @@ public class ZenModePanel extends LinearLayout {
private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
@Override
public void onSelected(final Object value) {
- if (value != null && mZenButtons.isShown()) {
+ if (value != null && mZenButtons.isShown() && isAttachedToWindow()) {
if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + value);
+ final Uri realConditionId = getRealConditionId(mSessionExitCondition);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
- mController.setZen((Integer) value);
+ mController.setZen((Integer) value, realConditionId, TAG + ".selectZen");
}
});
}
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index fd19d49..5fc1f93 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -16,6 +16,7 @@
package android.renderscript;
+import java.io.File;
import java.lang.reflect.Method;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -231,6 +232,11 @@ public class RenderScript {
validate();
rsnContextSetPriority(mContext, p);
}
+ native void rsnContextSetCacheDir(long con, String cacheDir);
+ synchronized void nContextSetCacheDir(String cacheDir) {
+ validate();
+ rsnContextSetCacheDir(mContext, cacheDir);
+ }
native void rsnContextDump(long con, int bits);
synchronized void nContextDump(int bits) {
validate();
@@ -1325,6 +1331,14 @@ public class RenderScript {
if (rs.mContext == 0) {
throw new RSDriverException("Failed to create RS context.");
}
+
+ // set up cache directory for entire context
+ final String CACHE_PATH = "com.android.renderscript.cache";
+ File f = new File(RenderScriptCacheDir.mCacheDir, CACHE_PATH);
+ String mCachePath = f.getAbsolutePath();
+ f.mkdirs();
+ rs.nContextSetCacheDir(mCachePath);
+
rs.mMessageThread = new MessageThread(rs);
rs.mMessageThread.start();
return rs;
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 40fad38..a40233a 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -684,6 +684,17 @@ nContextSetPriority(JNIEnv *_env, jobject _this, jlong con, jint p)
rsContextSetPriority((RsContext)con, p);
}
+static void
+nContextSetCacheDir(JNIEnv *_env, jobject _this, jlong con, jstring cacheDir)
+{
+ AutoJavaStringToUTF8 cacheDirUTF(_env, cacheDir);
+
+ if (kLogApi) {
+ ALOGD("ContextSetCacheDir, con(%p), cacheDir(%s)", (RsContext)con, cacheDirUTF.c_str());
+ }
+ rsContextSetCacheDir((RsContext)con, cacheDirUTF.c_str(), cacheDirUTF.length());
+}
+
static void
@@ -2307,6 +2318,7 @@ static JNINativeMethod methods[] = {
{"rsnContextCreateGL", "(JIIIIIIIIIIIIFI)J", (void*)nContextCreateGL },
{"rsnContextFinish", "(J)V", (void*)nContextFinish },
{"rsnContextSetPriority", "(JI)V", (void*)nContextSetPriority },
+{"rsnContextSetCacheDir", "(JLjava/lang/String;)V", (void*)nContextSetCacheDir },
{"rsnContextSetSurface", "(JIILandroid/view/Surface;)V", (void*)nContextSetSurface },
{"rsnContextDestroy", "(J)V", (void*)nContextDestroy },
{"rsnContextDump", "(JI)V", (void*)nContextDump },
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index da11dad..f42aef1 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -674,7 +674,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
@Override
public IntentSender createAppWidgetConfigIntentSender(String callingPackage, int appWidgetId,
- int intentFlags) {
+ final int intentFlags) {
final int userId = UserHandle.getCallingUserId();
if (DEBUG) {
@@ -701,18 +701,21 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
throw new IllegalArgumentException("Widget not bound " + appWidgetId);
}
+ // Make sure only safe flags can be passed it.
+ final int secureFlags = intentFlags & ~Intent.IMMUTABLE_FLAGS;
+
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.setComponent(provider.info.configure);
- intent.setFlags(intentFlags);
+ intent.setFlags(secureFlags);
// All right, create the sender.
final long identity = Binder.clearCallingIdentity();
try {
return PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
- | PendingIntent.FLAG_CANCEL_CURRENT, null,
- new UserHandle(provider.getUserId()))
+ | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT,
+ null, new UserHandle(provider.getUserId()))
.getIntentSender();
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 96840a2..1bed4f3 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -2209,7 +2209,10 @@ public class BackupManagerService {
// Get the restore-set token for the best-available restore set for this package:
// the active set if possible, else the ancestral one. Returns zero if none available.
- long getAvailableRestoreToken(String packageName) {
+ public long getAvailableRestoreToken(String packageName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "getAvailableRestoreToken");
+
long token = mAncestralToken;
synchronized (mQueueLock) {
if (mEverStoredApps.contains(packageName)) {
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 99bbdae..5859c6a 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -317,6 +317,12 @@ public class Trampoline extends IBackupManager.Stub {
}
@Override
+ public long getAvailableRestoreToken(String packageName) {
+ BackupManagerService svc = mService;
+ return (svc != null) ? svc.getAvailableRestoreToken(packageName) : 0;
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 7d156df..34e8b78 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -34,6 +34,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
+import android.os.Process;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -61,6 +62,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Locale;
+import java.util.Random;
import java.util.TimeZone;
import static android.app.AlarmManager.RTC_WAKEUP;
@@ -128,6 +130,7 @@ class AlarmManagerService extends SystemService {
final ResultReceiver mResultReceiver = new ResultReceiver();
PendingIntent mTimeTickSender;
PendingIntent mDateChangeSender;
+ Random mRandom;
boolean mInteractive = true;
long mNonInteractiveStartTime;
long mNonInteractiveTime;
@@ -185,18 +188,20 @@ class AlarmManagerService extends SystemService {
final class Batch {
long start; // These endpoints are always in ELAPSED
long end;
- boolean standalone; // certain "batches" don't participate in coalescing
+ int flags; // Flags for alarms, such as FLAG_STANDALONE.
final ArrayList<Alarm> alarms = new ArrayList<Alarm>();
Batch() {
start = 0;
end = Long.MAX_VALUE;
+ flags = 0;
}
Batch(Alarm seed) {
start = seed.whenElapsed;
- end = seed.maxWhen;
+ end = seed.maxWhenElapsed;
+ flags = seed.flags;
alarms.add(seed);
}
@@ -227,9 +232,10 @@ class AlarmManagerService extends SystemService {
start = alarm.whenElapsed;
newStart = true;
}
- if (alarm.maxWhen < end) {
- end = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < end) {
+ end = alarm.maxWhenElapsed;
}
+ flags |= alarm.flags;
if (DEBUG_BATCH) {
Slog.v(TAG, " => now " + this);
@@ -241,6 +247,7 @@ class AlarmManagerService extends SystemService {
boolean didRemove = false;
long newStart = 0; // recalculate endpoints as we go
long newEnd = Long.MAX_VALUE;
+ int newFlags = 0;
for (int i = 0; i < alarms.size(); ) {
Alarm alarm = alarms.get(i);
if (alarm.operation.equals(operation)) {
@@ -253,9 +260,10 @@ class AlarmManagerService extends SystemService {
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
}
- if (alarm.maxWhen < newEnd) {
- newEnd = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < newEnd) {
+ newEnd = alarm.maxWhenElapsed;
}
+ newFlags |= alarm.flags;
i++;
}
}
@@ -263,6 +271,7 @@ class AlarmManagerService extends SystemService {
// commit the new batch bounds
start = newStart;
end = newEnd;
+ flags = newFlags;
}
return didRemove;
}
@@ -271,6 +280,7 @@ class AlarmManagerService extends SystemService {
boolean didRemove = false;
long newStart = 0; // recalculate endpoints as we go
long newEnd = Long.MAX_VALUE;
+ int newFlags = 0;
for (int i = 0; i < alarms.size(); ) {
Alarm alarm = alarms.get(i);
if (alarm.operation.getTargetPackage().equals(packageName)) {
@@ -283,9 +293,10 @@ class AlarmManagerService extends SystemService {
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
}
- if (alarm.maxWhen < newEnd) {
- newEnd = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < newEnd) {
+ newEnd = alarm.maxWhenElapsed;
}
+ newFlags |= alarm.flags;
i++;
}
}
@@ -293,6 +304,7 @@ class AlarmManagerService extends SystemService {
// commit the new batch bounds
start = newStart;
end = newEnd;
+ flags = newFlags;
}
return didRemove;
}
@@ -313,8 +325,8 @@ class AlarmManagerService extends SystemService {
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
}
- if (alarm.maxWhen < newEnd) {
- newEnd = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < newEnd) {
+ newEnd = alarm.maxWhenElapsed;
}
i++;
}
@@ -357,8 +369,9 @@ class AlarmManagerService extends SystemService {
b.append(" num="); b.append(size());
b.append(" start="); b.append(start);
b.append(" end="); b.append(end);
- if (standalone) {
- b.append(" STANDALONE");
+ if (flags != 0) {
+ b.append(" flgs=0x");
+ b.append(Integer.toHexString(flags));
}
b.append('}');
return b.toString();
@@ -441,7 +454,12 @@ class AlarmManagerService extends SystemService {
// minimum recurrence period or alarm futurity for us to be able to fuzz it
static final long MIN_FUZZABLE_INTERVAL = 10000;
static final BatchTimeOrder sBatchOrder = new BatchTimeOrder();
- final ArrayList<Batch> mAlarmBatches = new ArrayList<Batch>();
+ final ArrayList<Batch> mAlarmBatches = new ArrayList<>();
+
+ // set to null if in idle mode; while in this mode, any alarms we don't want
+ // to run during this time are placed in mPendingWhileIdleAlarms
+ Alarm mPendingIdleUntil = null;
+ final ArrayList<Alarm> mPendingWhileIdleAlarms = new ArrayList<>();
public AlarmManagerService(Context context) {
super(context);
@@ -486,7 +504,7 @@ class AlarmManagerService extends SystemService {
final int N = mAlarmBatches.size();
for (int i = 0; i < N; i++) {
Batch b = mAlarmBatches.get(i);
- if (!b.standalone && b.canHold(whenElapsed, maxWhen)) {
+ if ((b.flags&AlarmManager.FLAG_STANDALONE) == 0 && b.canHold(whenElapsed, maxWhen)) {
return i;
}
}
@@ -503,31 +521,56 @@ class AlarmManagerService extends SystemService {
void rebatchAllAlarmsLocked(boolean doValidate) {
ArrayList<Batch> oldSet = (ArrayList<Batch>) mAlarmBatches.clone();
mAlarmBatches.clear();
+ Alarm oldPendingIdleUntil = mPendingIdleUntil;
final long nowElapsed = SystemClock.elapsedRealtime();
final int oldBatches = oldSet.size();
for (int batchNum = 0; batchNum < oldBatches; batchNum++) {
Batch batch = oldSet.get(batchNum);
final int N = batch.size();
for (int i = 0; i < N; i++) {
- Alarm a = batch.get(i);
- long whenElapsed = convertToElapsed(a.when, a.type);
- final long maxElapsed;
- if (a.whenElapsed == a.maxWhen) {
- // Exact
- maxElapsed = whenElapsed;
- } else {
- // Not exact. Preserve any explicit window, otherwise recalculate
- // the window based on the alarm's new futurity. Note that this
- // reflects a policy of preferring timely to deferred delivery.
- maxElapsed = (a.windowLength > 0)
- ? (whenElapsed + a.windowLength)
- : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
- }
- setImplLocked(a.type, a.when, whenElapsed, a.windowLength, maxElapsed,
- a.repeatInterval, a.operation, batch.standalone, doValidate, a.workSource,
- a.alarmClock, a.userId);
+ reAddAlarmLocked(batch.get(i), nowElapsed, doValidate);
+ }
+ }
+ if (oldPendingIdleUntil != null && oldPendingIdleUntil != mPendingIdleUntil) {
+ Slog.wtf(TAG, "Rebatching: idle until changed from " + oldPendingIdleUntil
+ + " to " + mPendingIdleUntil);
+ if (mPendingIdleUntil == null) {
+ // Somehow we lost this... we need to restore all of the pending alarms.
+ restorePendingWhileIdleAlarmsLocked();
}
}
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+ }
+
+ void reAddAlarmLocked(Alarm a, long nowElapsed, boolean doValidate) {
+ a.when = a.origWhen;
+ long whenElapsed = convertToElapsed(a.when, a.type);
+ final long maxElapsed;
+ if (a.whenElapsed == a.maxWhenElapsed) {
+ // Exact
+ maxElapsed = whenElapsed;
+ } else {
+ // Not exact. Preserve any explicit window, otherwise recalculate
+ // the window based on the alarm's new futurity. Note that this
+ // reflects a policy of preferring timely to deferred delivery.
+ maxElapsed = (a.windowLength > 0)
+ ? (whenElapsed + a.windowLength)
+ : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
+ }
+ a.whenElapsed = whenElapsed;
+ a.maxWhenElapsed = maxElapsed;
+ setImplLocked(a, true, doValidate);
+ }
+
+ void restorePendingWhileIdleAlarmsLocked() {
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ for (int i=mPendingWhileIdleAlarms.size() - 1; i >= 0 && mPendingIdleUntil != null; i --) {
+ Alarm a = mPendingWhileIdleAlarms.remove(i);
+ reAddAlarmLocked(a, nowElapsed, false);
+ }
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
}
static final class InFlight extends Intent {
@@ -687,7 +730,7 @@ class AlarmManagerService extends SystemService {
}
void setImpl(int type, long triggerAtTime, long windowLength, long interval,
- PendingIntent operation, boolean isStandalone, WorkSource workSource,
+ PendingIntent operation, int flags, WorkSource workSource,
AlarmManager.AlarmClockInfo alarmClock) {
if (operation == null) {
Slog.w(TAG, "set/setRepeating ignored because there is no intent");
@@ -745,25 +788,66 @@ class AlarmManagerService extends SystemService {
Slog.v(TAG, "set(" + operation + ") : type=" + type
+ " triggerAtTime=" + triggerAtTime + " win=" + windowLength
+ " tElapsed=" + triggerElapsed + " maxElapsed=" + maxElapsed
- + " interval=" + interval + " standalone=" + isStandalone);
+ + " interval=" + interval + " flags=0x" + Integer.toHexString(flags));
}
setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
- interval, operation, isStandalone, true, workSource, alarmClock, userId);
+ interval, operation, flags, true, workSource, alarmClock, userId);
}
}
private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
- long maxWhen, long interval, PendingIntent operation, boolean isStandalone,
+ long maxWhen, long interval, PendingIntent operation, int flags,
boolean doValidate, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
int userId) {
Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
- operation, workSource, alarmClock, userId);
+ operation, workSource, flags, alarmClock, userId);
removeLocked(operation);
+ setImplLocked(a, false, doValidate);
+ }
+
+ private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) {
+ if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+ // This is a special alarm that will put the system idle until it goes off.
+ // The caller has given the time they want this to happen at, however we need
+ // to pull that earlier if there are existing alarms that have requested to
+ // bring us out of idle.
+ final int N = mAlarmBatches.size();
+ for (int i = 0; i < N; i++) {
+ Batch b = mAlarmBatches.get(i);
+ if (a.whenElapsed > b.end) {
+ // There are no interesting things happening before our idle until,
+ // so keep the requested time.
+ break;
+ }
+ if ((b.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
+ a.when = a.whenElapsed = a.maxWhenElapsed = b.end;
+ break;
+ }
+ }
+ // Add fuzz to make the alarm go off some time before the actual desired time.
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ long fuzz = fuzzForDuration(a.whenElapsed-nowElapsed);
+ if (fuzz > 0) {
+ if (mRandom == null) {
+ mRandom = new Random();
+ }
+ a.whenElapsed -= mRandom.nextLong() % fuzz;
+ }
- int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen);
+ } else if (mPendingIdleUntil != null) {
+ // We currently have an idle until alarm scheduled; if the new alarm has
+ // not explicitly stated it wants to run while idle, then put it on hold.
+ if ((a.flags&(AlarmManager.FLAG_ALLOW_WHILE_IDLE|AlarmManager.FLAG_WAKE_FROM_IDLE))
+ == 0) {
+ mPendingWhileIdleAlarms.add(a);
+ return;
+ }
+ }
+
+ int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0)
+ ? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed);
if (whichBatch < 0) {
Batch batch = new Batch(a);
- batch.standalone = isStandalone;
addBatchLocked(mAlarmBatches, batch);
} else {
Batch batch = mAlarmBatches.get(whichBatch);
@@ -775,28 +859,48 @@ class AlarmManagerService extends SystemService {
}
}
- if (alarmClock != null) {
+ if (a.alarmClock != null) {
mNextAlarmClockMayChange = true;
- updateNextAlarmClockLocked();
}
- if (DEBUG_VALIDATE) {
- if (doValidate && !validateConsistencyLocked()) {
- Slog.v(TAG, "Tipping-point operation: type=" + type + " when=" + when
- + " when(hex)=" + Long.toHexString(when)
- + " whenElapsed=" + whenElapsed + " maxWhen=" + maxWhen
- + " interval=" + interval + " op=" + operation
- + " standalone=" + isStandalone);
+ boolean needRebatch = false;
+
+ if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+ mPendingIdleUntil = a;
+ needRebatch = true;
+ } else if ((a.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0 && mPendingIdleUntil != null) {
+ // If we are adding an alarm that asks to wake from idle, and we are currently
+ // idling, then we need to rebatch alarms in case the idle until time needs to
+ // be updated.
+ needRebatch = true;
+ }
+
+ if (!rebatching) {
+ if (DEBUG_VALIDATE) {
+ if (doValidate && !validateConsistencyLocked()) {
+ Slog.v(TAG, "Tipping-point operation: type=" + a.type + " when=" + a.when
+ + " when(hex)=" + Long.toHexString(a.when)
+ + " whenElapsed=" + a.whenElapsed
+ + " maxWhenElapsed=" + a.maxWhenElapsed
+ + " interval=" + a.repeatInterval + " op=" + a.operation
+ + " flags=0x" + Integer.toHexString(a.flags));
+ rebatchAllAlarmsLocked(false);
+ needRebatch = false;
+ }
+ }
+
+ if (needRebatch) {
rebatchAllAlarmsLocked(false);
}
- }
- rescheduleKernelAlarmsLocked();
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+ }
}
private final IBinder mService = new IAlarmManager.Stub() {
@Override
- public void set(int type, long triggerAtTime, long windowLength, long interval,
+ public void set(int type, long triggerAtTime, long windowLength, long interval, int flags,
PendingIntent operation, WorkSource workSource,
AlarmManager.AlarmClockInfo alarmClock) {
if (workSource != null) {
@@ -805,8 +909,17 @@ class AlarmManagerService extends SystemService {
"AlarmManager.set");
}
+ if (windowLength == AlarmManager.WINDOW_EXACT) {
+ flags |= AlarmManager.FLAG_STANDALONE;
+ }
+ if (alarmClock != null) {
+ flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
+ }
+ if (Binder.getCallingUid() < Process.FIRST_APPLICATION_UID) {
+ flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+ }
setImpl(type, triggerAtTime, windowLength, interval, operation,
- windowLength == AlarmManager.WINDOW_EXACT, workSource, alarmClock);
+ flags, workSource, alarmClock);
}
@Override
@@ -912,6 +1025,14 @@ class AlarmManagerService extends SystemService {
dumpAlarmList(pw, b.alarms, " ", nowELAPSED, nowRTC, sdf);
}
}
+ if (mPendingIdleUntil != null) {
+ pw.println();
+ pw.println("Idle mode state:");
+ pw.print(" Idling until: "); pw.println(mPendingIdleUntil);
+ mPendingIdleUntil.dump(pw, " ", nowELAPSED, nowRTC, sdf);
+ pw.println(" Pending alarms:");
+ dumpAlarmList(pw, mPendingWhileIdleAlarms, " ", nowELAPSED, nowRTC, sdf);
+ }
pw.println();
pw.print("Past-due non-wakeup alarms: ");
@@ -1224,6 +1345,15 @@ class AlarmManagerService extends SystemService {
}
void rescheduleKernelAlarmsLocked() {
+ if (mPendingIdleUntil != null) {
+ // If we have a pending "idle until" alarm, we will just blindly wait until
+ // it is time for that alarm to go off. We don't want to wake up for any
+ // other reasons.
+ mNextWakeup = mNextNonWakeup = mPendingIdleUntil.whenElapsed;
+ setLocked(ELAPSED_REALTIME_WAKEUP, mNextWakeup);
+ setLocked(ELAPSED_REALTIME, mNextNonWakeup);
+ return;
+ }
// Schedule the next upcoming wakeup alarm. If there is a deliverable batch
// prior to that which contains no wakeups, we schedule that as well.
long nextNonWakeup = 0;
@@ -1260,13 +1390,26 @@ class AlarmManagerService extends SystemService {
mAlarmBatches.remove(i);
}
}
+ for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+ if (mPendingWhileIdleAlarms.get(i).operation.equals(operation)) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingWhileIdleAlarms.remove(i);
+ }
+ }
if (didRemove) {
if (DEBUG_BATCH) {
Slog.v(TAG, "remove(operation) changed bounds; rebatching");
}
+ boolean restorePending = false;
+ if (mPendingIdleUntil != null && mPendingIdleUntil.operation.equals(operation)) {
+ mPendingIdleUntil = null;
+ restorePending = true;
+ }
rebatchAllAlarmsLocked(true);
- rescheduleKernelAlarmsLocked();
+ if (restorePending) {
+ restorePendingWhileIdleAlarmsLocked();
+ }
updateNextAlarmClockLocked();
}
}
@@ -1280,6 +1423,12 @@ class AlarmManagerService extends SystemService {
mAlarmBatches.remove(i);
}
}
+ for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+ if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingWhileIdleAlarms.remove(i);
+ }
+ }
if (didRemove) {
if (DEBUG_BATCH) {
@@ -1300,6 +1449,13 @@ class AlarmManagerService extends SystemService {
mAlarmBatches.remove(i);
}
}
+ for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(mPendingWhileIdleAlarms.get(i).operation.getCreatorUid())
+ == userHandle) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingWhileIdleAlarms.remove(i);
+ }
+ }
if (didRemove) {
if (DEBUG_BATCH) {
@@ -1344,6 +1500,11 @@ class AlarmManagerService extends SystemService {
return true;
}
}
+ for (int i = 0; i < mPendingWhileIdleAlarms.size(); i++) {
+ if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+ return true;
+ }
+ }
return false;
}
@@ -1413,6 +1574,13 @@ class AlarmManagerService extends SystemService {
boolean triggerAlarmsLocked(ArrayList<Alarm> triggerList, final long nowELAPSED,
final long nowRTC) {
boolean hasWakeup = false;
+ if (mPendingIdleUntil != null) {
+ // If we have a pending "idle until" alarm, don't trigger any alarms
+ // until we are past the idle period.
+ if (nowELAPSED < mPendingIdleUntil.whenElapsed) {
+ return false;
+ }
+ }
// batches are temporally sorted, so we need only pull from the
// start of the list until we either empty it or hit a batch
// that is not yet deliverable
@@ -1432,6 +1600,11 @@ class AlarmManagerService extends SystemService {
Alarm alarm = batch.get(i);
alarm.count = 1;
triggerList.add(alarm);
+ if (mPendingIdleUntil == alarm) {
+ mPendingIdleUntil = null;
+ rebatchAllAlarmsLocked(false);
+ restorePendingWhileIdleAlarmsLocked();
+ }
// Recurring alarms may have passed several alarm intervals while the
// phone was asleep or off, so pass a trigger count when sending them.
@@ -1445,7 +1618,7 @@ class AlarmManagerService extends SystemService {
final long nextElapsed = alarm.whenElapsed + delta;
setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
- alarm.repeatInterval, alarm.operation, batch.standalone, true,
+ alarm.repeatInterval, alarm.operation, alarm.flags, true,
alarm.workSource, alarm.alarmClock, alarm.userId);
}
@@ -1494,34 +1667,38 @@ class AlarmManagerService extends SystemService {
private static class Alarm {
public final int type;
+ public final long origWhen;
public final boolean wakeup;
public final PendingIntent operation;
public final String tag;
public final WorkSource workSource;
+ public final int flags;
public int count;
public long when;
public long windowLength;
public long whenElapsed; // 'when' in the elapsed time base
- public long maxWhen; // also in the elapsed time base
+ public long maxWhenElapsed; // also in the elapsed time base
public long repeatInterval;
public final AlarmManager.AlarmClockInfo alarmClock;
public final int userId;
public PriorityClass priorityClass;
public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen,
- long _interval, PendingIntent _op, WorkSource _ws,
+ long _interval, PendingIntent _op, WorkSource _ws, int _flags,
AlarmManager.AlarmClockInfo _info, int _userId) {
type = _type;
+ origWhen = _when;
wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP
|| _type == AlarmManager.RTC_WAKEUP;
when = _when;
whenElapsed = _whenElapsed;
windowLength = _windowLength;
- maxWhen = _maxWhen;
+ maxWhenElapsed = _maxWhen;
repeatInterval = _interval;
operation = _op;
tag = makeTag(_op, _type);
workSource = _ws;
+ flags = _flags;
alarmClock = _info;
userId = _userId;
}
@@ -1561,7 +1738,8 @@ class AlarmManagerService extends SystemService {
pw.println();
pw.print(prefix); pw.print("window="); pw.print(windowLength);
pw.print(" repeatInterval="); pw.print(repeatInterval);
- pw.print(" count="); pw.println(count);
+ pw.print(" count="); pw.print(count);
+ pw.print(" flags=0x"); pw.println(Integer.toHexString(flags));
pw.print(prefix); pw.print("operation="); pw.println(operation);
}
}
@@ -1599,6 +1777,20 @@ class AlarmManagerService extends SystemService {
}
}
+ static long fuzzForDuration(long duration) {
+ if (duration < 15*60*1000) {
+ // If the duration until the time is less than 15 minutes, the maximum fuzz
+ // is the duration.
+ return duration;
+ } else if (duration < 90*60*1000) {
+ // If duration is less than 1 1/2 hours, the maximum fuzz is 15 minutes,
+ return 15*60*1000;
+ } else {
+ // Otherwise, we will fuzz by at most half an hour.
+ return 30*60*1000;
+ }
+ }
+
boolean checkAllowNonWakeupDelayLocked(long nowELAPSED) {
if (mInteractive) {
return false;
@@ -1886,7 +2078,7 @@ class AlarmManagerService extends SystemService {
final WorkSource workSource = null; // Let system take blame for time tick events.
setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
- 0, mTimeTickSender, true, workSource, null);
+ 0, mTimeTickSender, AlarmManager.FLAG_STANDALONE, workSource, null);
}
public void scheduleDateChangedEvent() {
@@ -1899,8 +2091,8 @@ class AlarmManagerService extends SystemService {
calendar.add(Calendar.DAY_OF_MONTH, 1);
final WorkSource workSource = null; // Let system take blame for date change events.
- setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource,
- null);
+ setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender,
+ AlarmManager.FLAG_STANDALONE, workSource, null);
}
}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 4677f65..d92a89f 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -15,6 +15,7 @@
package com.android.server;
+import android.annotation.NonNull;
import com.android.internal.content.PackageMonitor;
import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController;
import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
@@ -1285,13 +1286,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
}
- InputBindResult startInputUncheckedLocked(ClientState cs,
+ InputBindResult startInputUncheckedLocked(@NonNull ClientState cs,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
// If no method is currently selected, do nothing.
if (mCurMethodId == null) {
return mNoBinding;
}
+ if (attribute != null) {
+ // We accept an empty package name as a valid data.
+ if (!TextUtils.isEmpty(attribute.packageName) &&
+ !InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
+ attribute.packageName)) {
+ Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
+ + " uid=" + cs.uid + " package=" + attribute.packageName);
+ return mNoBinding;
+ }
+ }
+
if (mCurClient != cs) {
// Was the keyguard locked when switching over to the new client?
mCurClientInKeyguard = isKeyguardLocked();
@@ -1855,16 +1867,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
if (mCurClient != null && mCurAttribute != null) {
- final int uid = mCurClient.uid;
- final String packageName = mCurAttribute.packageName;
- if (SystemConfig.getInstance().getFixedImeApps().contains(packageName)) {
- if (InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, uid, packageName)) {
- return;
- }
- // TODO: Do we need to lock the input method when the application reported an
- // incorrect package name?
- Slog.e(TAG, "Ignoring FixedImeApps due to the validation failure. uid=" + uid
- + " package=" + packageName);
+ // We have already made sure that the package name belongs to the application's UID.
+ // No further UID check is required.
+ if (SystemConfig.getInstance().getFixedImeApps().contains(mCurAttribute.packageName)) {
+ return;
}
}
@@ -2148,7 +2154,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// more quickly (not get stuck behind it initializing itself for the
// new focused input, even if its window wants to hide the IME).
boolean didStart = false;
-
+
switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
if (!isTextEditor || !doAutoShow) {
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index b36f515..56f9942 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -18,14 +18,18 @@ package com.android.server;
import android.Manifest;
import android.app.ActivityManager;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.service.persistentdata.IPersistentDataBlockService;
+import android.service.persistentdata.PersistentDataBlockManager;
import android.util.Slog;
import com.android.internal.R;
@@ -428,6 +432,29 @@ public class PersistentDataBlockService extends SystemService {
}
@Override
+ public void wipeIfAllowed(Bundle bundle, PendingIntent pi) {
+ // Should only be called by owner
+ if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) {
+ throw new SecurityException("Only the Owner is allowed to wipe");
+ }
+ // Caller must be able to query the the state of the PersistentDataBlock
+ enforcePersistentDataBlockAccess();
+ String allowedPackage = mContext.getResources()
+ .getString(R.string.config_persistentDataPackageName);
+ Intent intent = new Intent();
+ intent.setPackage(allowedPackage);
+ intent.setAction(PersistentDataBlockManager.ACTION_WIPE_IF_ALLOWED);
+ intent.putExtras(bundle);
+ intent.putExtra(PersistentDataBlockManager.EXTRA_WIPE_IF_ALLOWED_CALLBACK, pi);
+ long id = Binder.clearCallingIdentity();
+ try {
+ mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ }
+
+ @Override
public void setOemUnlockEnabled(boolean enabled) {
// do not allow monkey to flip the flag
if (ActivityManager.isUserAMonkey()) {
@@ -450,10 +477,7 @@ public class PersistentDataBlockService extends SystemService {
@Override
public int getDataBlockSize() {
- if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
- != PackageManager.PERMISSION_GRANTED) {
- enforceUid(Binder.getCallingUid());
- }
+ enforcePersistentDataBlockAccess();
DataInputStream inputStream;
try {
@@ -475,6 +499,13 @@ public class PersistentDataBlockService extends SystemService {
}
}
+ private void enforcePersistentDataBlockAccess() {
+ if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ enforceUid(Binder.getCallingUid());
+ }
+ }
+
@Override
public long getMaximumDataBlockSize() {
long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d2f52b4..0581e22 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -100,6 +100,7 @@ import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -986,6 +987,12 @@ public final class ActivityManagerService extends ActivityManagerNative
private boolean mSleeping = false;
/**
+ * The process state used for processes that are running the top activities.
+ * This changes between TOP and TOP_SLEEPING to following mSleeping.
+ */
+ int mTopProcessState = ActivityManager.PROCESS_STATE_TOP;
+
+ /**
* Set while we are running a voice interaction. This overrides
* sleeping while it is active.
*/
@@ -2466,6 +2473,13 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ @Override
+ public void batterySendBroadcast(Intent intent) {
+ broadcastIntentLocked(null, null, intent, null,
+ null, 0, null, null, null, AppOpsManager.OP_NONE, false, false, -1,
+ Process.SYSTEM_UID, UserHandle.USER_ALL);
+ }
+
/**
* Initialize the application bind args. These are passed to each
* process when the bindApplication() IPC is sent to the process. They're
@@ -9726,10 +9740,14 @@ public final class ActivityManagerService extends ActivityManagerNative
void updateSleepIfNeededLocked() {
if (mSleeping && !shouldSleepLocked()) {
mSleeping = false;
+ mTopProcessState = ActivityManager.PROCESS_STATE_TOP;
mStackSupervisor.comeOutOfSleepIfNeededLocked();
+ updateOomAdjLocked();
} else if (!mSleeping && shouldSleepLocked()) {
mSleeping = true;
+ mTopProcessState = ActivityManager.PROCESS_STATE_TOP_SLEEPING;
mStackSupervisor.goingToSleepLocked();
+ updateOomAdjLocked();
// Initialize the wake times of all processes.
checkExcessivePowerUsageLocked(false);
@@ -10700,7 +10718,8 @@ public final class ActivityManagerService extends ActivityManagerNative
for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
ProcessRecord proc = mLruProcesses.get(i);
if (proc.notCachedSinceIdle) {
- if (proc.setProcState > ActivityManager.PROCESS_STATE_TOP
+ if (proc.setProcState != ActivityManager.PROCESS_STATE_TOP
+ && proc.setProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
&& proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
if (doKilling && proc.initialIdlePss != 0
&& proc.lastPss > ((proc.initialIdlePss*3)/2)) {
@@ -12893,7 +12912,7 @@ public final class ActivityManagerService extends ActivityManagerNative
StringBuilder sb = new StringBuilder();
sb.append(" ").append(proc).append('/');
UserHandle.formatUid(sb, uids.keyAt(j));
- Pair<Long, String> val = uids.valueAt(i);
+ Pair<Long, String> val = uids.valueAt(j);
sb.append(": "); DebugUtils.sizeValueToString(val.first, sb);
if (val.second != null) {
sb.append(", report to ").append(val.second);
@@ -13929,7 +13948,7 @@ public final class ActivityManagerService extends ActivityManagerNative
} else if ("-h".equals(opt)) {
pw.println("meminfo dump options: [-a] [-d] [-c] [--oom] [process]");
pw.println(" -a: include all available information for each process.");
- pw.println(" -d: include dalvik details when dumping process details.");
+ pw.println(" -d: include dalvik details.");
pw.println(" -c: dump in a compact machine-parseable representation.");
pw.println(" --oom: only show processes organized by oom adj.");
pw.println(" --local: only collect details locally, don't call process.");
@@ -14016,6 +14035,8 @@ public final class ActivityManagerService extends ActivityManagerNative
final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>();
long nativePss = 0;
long dalvikPss = 0;
+ long[] dalvikSubitemPss = dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+ EmptyArray.LONG;
long otherPss = 0;
long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
@@ -14093,6 +14114,9 @@ public final class ActivityManagerService extends ActivityManagerNative
nativePss += mi.nativePss;
dalvikPss += mi.dalvikPss;
+ for (int j=0; j<dalvikSubitemPss.length; j++) {
+ dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ }
otherPss += mi.otherPss;
for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
long mem = mi.getOtherPss(j);
@@ -14151,6 +14175,10 @@ public final class ActivityManagerService extends ActivityManagerNative
nativePss += mi.nativePss;
dalvikPss += mi.dalvikPss;
+ for (int j=0; j<dalvikSubitemPss.length; j++) {
+ dalvikSubitemPss[j] += mi.getOtherPss(
+ Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ }
otherPss += mi.otherPss;
for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
long mem = mi.getOtherPss(j);
@@ -14169,7 +14197,16 @@ public final class ActivityManagerService extends ActivityManagerNative
ArrayList<MemItem> catMems = new ArrayList<MemItem>();
catMems.add(new MemItem("Native", "Native", nativePss, -1));
- catMems.add(new MemItem("Dalvik", "Dalvik", dalvikPss, -2));
+ final MemItem dalvikItem = new MemItem("Dalvik", "Dalvik", dalvikPss, -2);
+ if (dalvikSubitemPss.length > 0) {
+ dalvikItem.subitems = new ArrayList<MemItem>();
+ for (int j=0; j<dalvikSubitemPss.length; j++) {
+ final String name = Debug.MemoryInfo.getOtherLabel(
+ Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ dalvikItem.subitems.add(new MemItem(name, name, dalvikSubitemPss[j], j));
+ }
+ }
+ catMems.add(dalvikItem);
catMems.add(new MemItem("Unknown", "Unknown", otherPss, -3));
for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
String label = Debug.MemoryInfo.getOtherLabel(j);
@@ -15461,8 +15498,9 @@ public final class ActivityManagerService extends ActivityManagerNative
}
synchronized (this) {
- if (callerApp != null && callerApp.pid == 0) {
- // Caller already died
+ if (callerApp != null && (callerApp.thread == null
+ || callerApp.thread.asBinder() != caller.asBinder())) {
+ // Original caller already died
return null;
}
ReceiverList rl
@@ -16824,6 +16862,8 @@ public final class ActivityManagerService extends ActivityManagerNative
app.systemNoUi = false;
+ final int PROCESS_STATE_TOP = mTopProcessState;
+
// Determine the importance of the process, starting with most
// important to least, and assign an appropriate OOM adjustment.
int adj;
@@ -16837,7 +16877,7 @@ public final class ActivityManagerService extends ActivityManagerNative
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "top-activity";
foregroundActivities = true;
- procState = ActivityManager.PROCESS_STATE_TOP;
+ procState = PROCESS_STATE_TOP;
} else if (app.instrumentationClass != null) {
// Don't want to kill running instrumentation.
adj = ProcessList.FOREGROUND_APP_ADJ;
@@ -16890,8 +16930,8 @@ public final class ActivityManagerService extends ActivityManagerNative
adj = ProcessList.VISIBLE_APP_ADJ;
app.adjType = "visible";
}
- if (procState > ActivityManager.PROCESS_STATE_TOP) {
- procState = ActivityManager.PROCESS_STATE_TOP;
+ if (procState > PROCESS_STATE_TOP) {
+ procState = PROCESS_STATE_TOP;
}
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.cached = false;
@@ -16903,8 +16943,8 @@ public final class ActivityManagerService extends ActivityManagerNative
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
app.adjType = "pausing";
}
- if (procState > ActivityManager.PROCESS_STATE_TOP) {
- procState = ActivityManager.PROCESS_STATE_TOP;
+ if (procState > PROCESS_STATE_TOP) {
+ procState = PROCESS_STATE_TOP;
}
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.cached = false;
@@ -16943,7 +16983,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app.foregroundServices) {
// The user is aware of this app, so make it visible.
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
- procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
app.cached = false;
app.adjType = "fg-service";
schedGroup = Process.THREAD_GROUP_DEFAULT;
@@ -17472,7 +17512,7 @@ public final class ActivityManagerService extends ActivityManagerNative
IApplicationThread thread = myProc.thread;
if (thread != null) {
try {
- if (true || DEBUG_PSS) Slog.d(TAG_PSS,
+ if (DEBUG_PSS) Slog.d(TAG_PSS,
"Requesting dump heap from "
+ myProc + " to " + heapdumpFile);
thread.dumpHeap(true, heapdumpFile.toString(), fd);
@@ -18757,7 +18797,7 @@ public final class ActivityManagerService extends ActivityManagerNative
+ " does not match last path " + mMemWatchDumpFile);
return;
}
- if (true || DEBUG_PSS) Slog.d(TAG_PSS, "Dump heap finished for " + path);
+ if (DEBUG_PSS) Slog.d(TAG_PSS, "Dump heap finished for " + path);
mHandler.sendEmptyMessage(POST_DUMP_HEAP_NOTIFICATION_MSG);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index ddba1eb..2362d28 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1909,7 +1909,7 @@ final class ActivityStack {
next.sleeping = false;
mService.showAskCompatModeDialogLocked(next);
next.app.pendingUiClean = true;
- next.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
+ next.app.forceProcessStateUpTo(mService.mTopProcessState);
next.clearOptionsLocked();
next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
mService.isNextTransitionForward(), resumeAnimOptions);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d08cddc..c2f6bfd 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1229,7 +1229,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
app.hasShownUi = true;
app.pendingUiClean = true;
}
- app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
+ app.forceProcessStateUpTo(mService.mTopProcessState);
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage,
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index ac70d88..ed108c2 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -260,6 +260,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
+ public boolean isCharging() {
+ synchronized (mStats) {
+ return mStats.isCharging();
+ }
+ }
+
public long computeBatteryTimeRemaining() {
synchronized (mStats) {
long time = mStats.computeBatteryTimeRemaining(SystemClock.elapsedRealtime());
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 9c0db87..531de46 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -222,19 +222,26 @@ final class PendingIntentRecord extends IIntentSender.Stub {
owner.cancelIntentSenderLocked(this, true);
canceled = true;
}
+
Intent finalIntent = key.requestIntent != null
? new Intent(key.requestIntent) : new Intent();
- if (intent != null) {
- int changes = finalIntent.fillIn(intent, key.flags);
- if ((changes&Intent.FILL_IN_DATA) == 0) {
+
+ final boolean immutable = (key.flags & PendingIntent.FLAG_IMMUTABLE) != 0;
+ if (!immutable) {
+ if (intent != null) {
+ int changes = finalIntent.fillIn(intent, key.flags);
+ if ((changes & Intent.FILL_IN_DATA) == 0) {
+ resolvedType = key.requestResolvedType;
+ }
+ } else {
resolvedType = key.requestResolvedType;
}
+ flagsMask &= ~Intent.IMMUTABLE_FLAGS;
+ flagsValues &= flagsMask;
+ finalIntent.setFlags((finalIntent.getFlags() & ~flagsMask) | flagsValues);
} else {
resolvedType = key.requestResolvedType;
}
- flagsMask &= ~Intent.IMMUTABLE_FLAGS;
- flagsValues &= flagsMask;
- finalIntent.setFlags((finalIntent.getFlags()&~flagsMask) | flagsValues);
final long origId = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c7aa94c..cdfcd0c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -368,6 +368,12 @@ final class ProcessList {
case ActivityManager.PROCESS_STATE_TOP:
procState = "T ";
break;
+ case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
+ procState = "FS";
+ break;
+ case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
+ procState = "TS";
+ break;
case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
procState = "IF";
break;
@@ -475,6 +481,8 @@ final class ProcessList {
PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT
PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP
+ PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_BACKUP
@@ -492,6 +500,8 @@ final class ProcessList {
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
@@ -509,6 +519,8 @@ final class ProcessList {
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
@@ -526,6 +538,8 @@ final class ProcessList {
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
@@ -543,6 +557,8 @@ final class ProcessList {
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 191df2f..7866ddc 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -176,6 +176,7 @@ public class SyncManager {
volatile private PowerManager.WakeLock mSyncManagerWakeLock;
volatile private boolean mDataConnectionIsConnected = false;
volatile private boolean mStorageIsLow = false;
+ volatile private boolean mDeviceIsIdle = false;
private final NotificationManager mNotificationMgr;
private AlarmManager mAlarmService = null;
@@ -221,6 +222,20 @@ public class SyncManager {
}
};
+ private BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
+ @Override public void onReceive(Context context, Intent intent) {
+ boolean idle = mPowerManager.isDeviceIdleMode();
+ mDeviceIsIdle = idle;
+ if (idle) {
+ cancelActiveSync(
+ SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL,
+ null /* any sync */);
+ } else {
+ sendCheckAlarmsMessage();
+ }
+ }
+ };
+
private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -425,6 +440,9 @@ public class SyncManager {
intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
context.registerReceiver(mStorageIntentReceiver, intentFilter);
+ intentFilter = new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+ context.registerReceiver(mDeviceIdleReceiver, intentFilter);
+
intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
intentFilter.setPriority(100);
context.registerReceiver(mShutdownIntentReceiver, intentFilter);
@@ -1312,6 +1330,7 @@ public class SyncManager {
pw.println();
}
pw.print("memory low: "); pw.println(mStorageIsLow);
+ pw.print("device idle: "); pw.println(mDeviceIsIdle);
final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
@@ -2358,6 +2377,13 @@ public class SyncManager {
return Long.MAX_VALUE;
}
+ if (mDeviceIsIdle) {
+ if (isLoggable) {
+ Log.v(TAG, "maybeStartNextSync: device idle, skipping");
+ }
+ return Long.MAX_VALUE;
+ }
+
// If the accounts aren't known yet then we aren't ready to run. We will be kicked
// when the account lookup request does complete.
if (mRunningAccounts == INITIAL_ACCOUNTS_ARRAY) {
@@ -2984,6 +3010,7 @@ public class SyncManager {
// method to be called again
if (!mDataConnectionIsConnected) return;
if (mStorageIsLow) return;
+ if (mDeviceIsIdle) return;
// When the status bar notification should be raised
final long notificationTime =
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index c97bdb6..a3c9738 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -348,6 +348,10 @@ final class DisplayPowerState {
private int mActualBacklight = INITIAL_BACKLIGHT;
private boolean mChangeInProgress;
+ public PhotonicModulator() {
+ super("PhotonicModulator");
+ }
+
public boolean setState(int state, int backlight) {
synchronized (mLock) {
if (state != mPendingState || backlight != mPendingBacklight) {
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index 309e034..7c2aead 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -47,10 +47,6 @@ public class BatteryController extends StateController {
private static final Object sCreationLock = new Object();
private static volatile BatteryController sController;
- private static final String ACTION_CHARGING_STABLE =
- "com.android.server.task.controllers.BatteryController.ACTION_CHARGING_STABLE";
- /** Wait this long after phone is plugged in before doing any work. */
- private static final long STABLE_CHARGING_THRESHOLD_MILLIS = 2 * 60 * 1000; // 2 minutes.
private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
private ChargingTracker mChargeTracker;
@@ -91,9 +87,6 @@ public class BatteryController extends StateController {
taskStatus.chargingConstraintSatisfied.set(isOnStablePower);
}
}
- if (isOnStablePower) {
- mChargeTracker.setStableChargingAlarm();
- }
}
@Override
@@ -131,8 +124,6 @@ public class BatteryController extends StateController {
}
public class ChargingTracker extends BroadcastReceiver {
- private final AlarmManager mAlarm;
- private final PendingIntent mStableChargingTriggerIntent;
/**
* Track whether we're "charging", where charging means that we're ready to commit to
* doing work.
@@ -142,9 +133,6 @@ public class BatteryController extends StateController {
private boolean mBatteryHealthy;
public ChargingTracker() {
- mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- Intent intent = new Intent(ACTION_CHARGING_STABLE);
- mStableChargingTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
}
public void startTracking() {
@@ -154,10 +142,8 @@ public class BatteryController extends StateController {
filter.addAction(Intent.ACTION_BATTERY_LOW);
filter.addAction(Intent.ACTION_BATTERY_OKAY);
// Charging/not charging.
- filter.addAction(Intent.ACTION_POWER_CONNECTED);
- filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
- // Charging stable.
- filter.addAction(ACTION_CHARGING_STABLE);
+ filter.addAction(BatteryManager.ACTION_CHARGING);
+ filter.addAction(BatteryManager.ACTION_DISCHARGING);
mContext.registerReceiver(this, filter);
// Initialise tracker state.
@@ -195,44 +181,20 @@ public class BatteryController extends StateController {
}
mBatteryHealthy = true;
maybeReportNewChargingState();
- } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
+ } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
if (DEBUG) {
- Slog.d(TAG, "Received charging intent, setting alarm for "
- + STABLE_CHARGING_THRESHOLD_MILLIS);
+ Slog.d(TAG, "Received charging intent, fired @ "
+ + SystemClock.elapsedRealtime());
}
- // Set up an alarm for ACTION_CHARGING_STABLE - we don't want to kick off tasks
- // here if the user unplugs the phone immediately.
- setStableChargingAlarm();
mCharging = true;
- } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
+ maybeReportNewChargingState();
+ } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
if (DEBUG) {
- Slog.d(TAG, "Disconnected from power, cancelling any set alarms.");
+ Slog.d(TAG, "Disconnected from power.");
}
- // If an alarm is set, breathe a sigh of relief and cancel it - crisis averted.
- mAlarm.cancel(mStableChargingTriggerIntent);
mCharging = false;
maybeReportNewChargingState();
- }else if (ACTION_CHARGING_STABLE.equals(action)) {
- // Here's where we actually do the notify for a task being ready.
- if (DEBUG) {
- Slog.d(TAG, "Stable charging fired @ " + SystemClock.elapsedRealtime()
- + " charging: " + mCharging);
- }
- if (mCharging) { // Should never receive this intent if mCharging is false.
- maybeReportNewChargingState();
- }
- }
- }
-
- void setStableChargingAlarm() {
- final long alarmTriggerElapsed =
- SystemClock.elapsedRealtime() + STABLE_CHARGING_THRESHOLD_MILLIS;
- if (DEBUG) {
- Slog.d(TAG, "Setting stable alarm to go off in " +
- (STABLE_CHARGING_THRESHOLD_MILLIS / 1000) + "s");
}
- mAlarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTriggerElapsed,
- mStableChargingTriggerIntent);
}
}
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 9dcc529..2da9d8e 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -106,7 +106,8 @@ public class LightsService extends SystemService {
mMode = mode;
mOnMS = onMS;
mOffMS = offMS;
- Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", " + color + ")");
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", 0x"
+ + Integer.toHexString(color) + ")");
try {
setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);
} finally {
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index ab53fbc..fc2eced 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -25,12 +25,10 @@ import android.os.IInterface;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionListener;
import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -41,50 +39,44 @@ import com.android.server.notification.NotificationManagerService.DumpFilter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Objects;
public class ConditionProviders extends ManagedServices {
- private static final Condition[] NO_CONDITIONS = new Condition[0];
-
- private final ZenModeHelper mZenModeHelper;
- private final ArrayMap<IBinder, IConditionListener> mListeners
- = new ArrayMap<IBinder, IConditionListener>();
- private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
- private final ArraySet<String> mSystemConditionProviders;
- private final CountdownConditionProvider mCountdown;
- private final DowntimeConditionProvider mDowntime;
- private final NextAlarmConditionProvider mNextAlarm;
- private final NextAlarmTracker mNextAlarmTracker;
-
- private Condition mExitCondition;
- private ComponentName mExitConditionComponent;
-
- public ConditionProviders(Context context, Handler handler,
- UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
+ private final ArrayList<ConditionRecord> mRecords = new ArrayList<>();
+ private final ArrayMap<IBinder, IConditionListener> mListeners = new ArrayMap<>();
+ private final ArraySet<String> mSystemConditionProviderNames;
+ private final ArraySet<SystemConditionProviderService> mSystemConditionProviders
+ = new ArraySet<>();
+
+ private Callback mCallback;
+
+ public ConditionProviders(Context context, Handler handler, UserProfiles userProfiles) {
super(context, handler, new Object(), userProfiles);
- mZenModeHelper = zenModeHelper;
- mZenModeHelper.addCallback(new ZenModeHelperCallback());
- mSystemConditionProviders = safeSet(PropConfig.getStringArray(mContext,
+ mSystemConditionProviderNames = safeSet(PropConfig.getStringArray(mContext,
"system.condition.providers",
R.array.config_system_condition_providers));
- final boolean countdown = mSystemConditionProviders.contains(ZenModeConfig.COUNTDOWN_PATH);
- final boolean downtime = mSystemConditionProviders.contains(ZenModeConfig.DOWNTIME_PATH);
- final boolean nextAlarm = mSystemConditionProviders.contains(ZenModeConfig.NEXT_ALARM_PATH);
- mNextAlarmTracker = (downtime || nextAlarm) ? new NextAlarmTracker(mContext) : null;
- mCountdown = countdown ? new CountdownConditionProvider() : null;
- mDowntime = downtime ? new DowntimeConditionProvider(this, mNextAlarmTracker,
- mZenModeHelper) : null;
- mNextAlarm = nextAlarm ? new NextAlarmConditionProvider(mNextAlarmTracker) : null;
- loadZenConfig();
}
- public boolean isSystemConditionProviderEnabled(String path) {
- return mSystemConditionProviders.contains(path);
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ public boolean isSystemProviderEnabled(String path) {
+ return mSystemConditionProviderNames.contains(path);
+ }
+
+ public void addSystemProvider(SystemConditionProviderService service) {
+ mSystemConditionProviders.add(service);
+ service.attachBase(mContext);
+ registerService(service.asInterface(), service.getComponent(), UserHandle.USER_OWNER);
+ }
+
+ public Iterable<SystemConditionProviderService> getSystemProviders() {
+ return mSystemConditionProviders;
}
@Override
protected Config getConfig() {
- Config c = new Config();
+ final Config c = new Config();
c.caption = "condition provider";
c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
@@ -98,12 +90,6 @@ public class ConditionProviders extends ManagedServices {
public void dump(PrintWriter pw, DumpFilter filter) {
super.dump(pw, filter);
synchronized(mMutex) {
- if (filter == null) {
- pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):");
- for (int i = 0; i < mListeners.size(); i++) {
- pw.print(" "); pw.println(mListeners.keyAt(i));
- }
- }
pw.print(" mRecords("); pw.print(mRecords.size()); pw.println("):");
for (int i = 0; i < mRecords.size(); i++) {
final ConditionRecord r = mRecords.get(i);
@@ -115,18 +101,15 @@ public class ConditionProviders extends ManagedServices {
}
}
}
- pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviders);
- if (mCountdown != null) {
- mCountdown.dump(pw, filter);
- }
- if (mDowntime != null) {
- mDowntime.dump(pw, filter);
- }
- if (mNextAlarm != null) {
- mNextAlarm.dump(pw, filter);
+ if (filter == null) {
+ pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):");
+ for (int i = 0; i < mListeners.size(); i++) {
+ pw.print(" "); pw.println(mListeners.keyAt(i));
+ }
}
- if (mNextAlarmTracker != null) {
- mNextAlarmTracker.dump(pw, filter);
+ pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviderNames);
+ for (int i = 0; i < mSystemConditionProviders.size(); i++) {
+ mSystemConditionProviders.valueAt(i).dump(pw, filter);
}
}
@@ -138,31 +121,16 @@ public class ConditionProviders extends ManagedServices {
@Override
public void onBootPhaseAppsCanStart() {
super.onBootPhaseAppsCanStart();
- if (mNextAlarmTracker != null) {
- mNextAlarmTracker.init();
- }
- if (mCountdown != null) {
- mCountdown.attachBase(mContext);
- registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT,
- UserHandle.USER_OWNER);
- }
- if (mDowntime != null) {
- mDowntime.attachBase(mContext);
- registerService(mDowntime.asInterface(), DowntimeConditionProvider.COMPONENT,
- UserHandle.USER_OWNER);
- }
- if (mNextAlarm != null) {
- mNextAlarm.attachBase(mContext);
- registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT,
- UserHandle.USER_OWNER);
+ if (mCallback != null) {
+ mCallback.onBootComplete();
}
}
@Override
public void onUserSwitched() {
super.onUserSwitched();
- if (mNextAlarmTracker != null) {
- mNextAlarmTracker.onUserSwitched();
+ if (mCallback != null) {
+ mCallback.onUserSwitched();
}
}
@@ -174,24 +142,6 @@ public class ConditionProviders extends ManagedServices {
} catch (RemoteException e) {
// we tried
}
- synchronized (mMutex) {
- if (info.component.equals(mExitConditionComponent)) {
- // ensure record exists, we'll wire it up and subscribe below
- final ConditionRecord manualRecord =
- getRecordLocked(mExitCondition.id, mExitConditionComponent);
- manualRecord.isManual = true;
- }
- final int N = mRecords.size();
- for(int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- if (!r.component.equals(info.component)) continue;
- r.info = info;
- // if automatic or manual, auto-subscribe
- if (r.isAutomatic || r.isManual) {
- subscribeLocked(r);
- }
- }
- }
}
@Override
@@ -200,15 +150,6 @@ public class ConditionProviders extends ManagedServices {
for (int i = mRecords.size() - 1; i >= 0; i--) {
final ConditionRecord r = mRecords.get(i);
if (!r.component.equals(removed.component)) continue;
- if (r.isManual) {
- // removing the current manual condition, exit zen
- onManualConditionClearing();
- mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "manualServiceRemoved");
- }
- if (r.isAutomatic) {
- // removing an automatic condition, exit zen
- mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "automaticServiceRemoved");
- }
mRecords.remove(i);
}
}
@@ -219,9 +160,9 @@ public class ConditionProviders extends ManagedServices {
}
}
- public void requestZenModeConditions(IConditionListener callback, int relevance) {
+ public void requestConditions(IConditionListener callback, int relevance) {
synchronized(mMutex) {
- if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
+ if (DEBUG) Slog.d(TAG, "requestConditions callback=" + callback
+ " relevance=" + Condition.relevanceToString(relevance));
if (callback == null) return;
relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
@@ -262,7 +203,8 @@ public class ConditionProviders extends ManagedServices {
return rt;
}
- private ConditionRecord getRecordLocked(Uri id, ComponentName component) {
+ private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) {
+ if (id == null || component == null) return null;
final int N = mRecords.size();
for (int i = 0; i < N; i++) {
final ConditionRecord r = mRecords.get(i);
@@ -270,9 +212,12 @@ public class ConditionProviders extends ManagedServices {
return r;
}
}
- final ConditionRecord r = new ConditionRecord(id, component);
- mRecords.add(r);
- return r;
+ if (create) {
+ final ConditionRecord r = new ConditionRecord(id, component);
+ mRecords.add(r);
+ return r;
+ }
+ return null;
}
public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
@@ -291,99 +236,48 @@ public class ConditionProviders extends ManagedServices {
}
for (int i = 0; i < N; i++) {
final Condition c = conditions[i];
- final ConditionRecord r = getRecordLocked(c.id, info.component);
- final Condition oldCondition = r.condition;
- final boolean conditionUpdate = oldCondition != null && !oldCondition.equals(c);
+ final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/);
r.info = info;
r.condition = c;
- // if manual, exit zen if false (or failed), update if true (and changed)
- if (r.isManual) {
- if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
- final boolean failed = c.state == Condition.STATE_ERROR;
- if (failed) {
- Slog.w(TAG, "Exit zen: manual condition failed: " + c);
- } else if (DEBUG) {
- Slog.d(TAG, "Exit zen: manual condition false: " + c);
- }
- onManualConditionClearing();
- mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
- "manualConditionExit");
- unsubscribeLocked(r);
- r.isManual = false;
- } else if (c.state == Condition.STATE_TRUE && conditionUpdate) {
- if (DEBUG) Slog.d(TAG, "Current condition updated, still true. old="
- + oldCondition + " new=" + c);
- setZenModeCondition(c, "conditionUpdate");
- }
- }
- // if automatic, exit zen if false (or failed), enter zen if true
- if (r.isAutomatic) {
- if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
- final boolean failed = c.state == Condition.STATE_ERROR;
- if (failed) {
- Slog.w(TAG, "Exit zen: automatic condition failed: " + c);
- } else if (DEBUG) {
- Slog.d(TAG, "Exit zen: automatic condition false: " + c);
- }
- mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
- "automaticConditionExit");
- } else if (c.state == Condition.STATE_TRUE) {
- Slog.d(TAG, "Enter zen: automatic condition true: " + c);
- mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- "automaticConditionEnter");
- }
+ if (mCallback != null) {
+ mCallback.onConditionChanged(c.id, c);
}
}
}
}
- private void ensureRecordExists(Condition condition, IConditionProvider provider,
- ComponentName component) {
+ public void ensureRecordExists(ComponentName component, Uri conditionId,
+ IConditionProvider provider) {
// constructed by convention, make sure the record exists...
- final ConditionRecord r = getRecordLocked(condition.id, component);
+ final ConditionRecord r = getRecordLocked(conditionId, component, true /*create*/);
if (r.info == null) {
// ... and is associated with the in-process service
r.info = checkServiceTokenLocked(provider);
}
}
- public void setZenModeCondition(Condition condition, String reason) {
- if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition + " reason=" + reason);
- synchronized(mMutex) {
- ComponentName conditionComponent = null;
- if (condition != null) {
- if (mCountdown != null && ZenModeConfig.isValidCountdownConditionId(condition.id)) {
- ensureRecordExists(condition, mCountdown.asInterface(),
- CountdownConditionProvider.COMPONENT);
- }
- if (mDowntime != null && ZenModeConfig.isValidDowntimeConditionId(condition.id)) {
- ensureRecordExists(condition, mDowntime.asInterface(),
- DowntimeConditionProvider.COMPONENT);
- }
- }
- final int N = mRecords.size();
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- final boolean idEqual = condition != null && r.id.equals(condition.id);
- if (r.isManual && !idEqual) {
- // was previous manual condition, unsubscribe
- unsubscribeLocked(r);
- r.isManual = false;
- } else if (idEqual && !r.isManual) {
- // is new manual condition, subscribe
- subscribeLocked(r);
- r.isManual = true;
- }
- if (idEqual) {
- conditionComponent = r.component;
- }
+ public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
+ synchronized (mMutex) {
+ final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
+ if (r == null) {
+ Slog.w(TAG, "Unable to subscribe to " + component + " " + conditionId);
+ return false;
}
- if (!Objects.equals(mExitCondition, condition)) {
- mExitCondition = condition;
- mExitConditionComponent = conditionComponent;
- ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, reason);
- saveZenConfigLocked();
+ if (r.subscribed) return true;
+ subscribeLocked(r);
+ return r.subscribed;
+ }
+ }
+
+ public void unsubscribeIfNecessary(ComponentName component, Uri conditionId) {
+ synchronized (mMutex) {
+ final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
+ if (r == null) {
+ Slog.w(TAG, "Unable to unsubscribe to " + component + " " + conditionId);
+ return;
}
+ if (!r.subscribed) return;
+ unsubscribeLocked(r);;
}
}
@@ -393,8 +287,9 @@ public class ConditionProviders extends ManagedServices {
RemoteException re = null;
if (provider != null) {
try {
- Slog.d(TAG, "Subscribing to " + r.id + " with " + provider);
+ Slog.d(TAG, "Subscribing to " + r.id + " with " + r.component);
provider.onSubscribe(r.id);
+ r.subscribed = true;
} catch (RemoteException e) {
Slog.w(TAG, "Error subscribing to " + r, e);
re = e;
@@ -417,53 +312,6 @@ public class ConditionProviders extends ManagedServices {
return rt;
}
- public void setAutomaticZenModeConditions(Uri[] conditionIds) {
- setAutomaticZenModeConditions(conditionIds, true /*save*/);
- }
-
- private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) {
- if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions "
- + (conditionIds == null ? null : Arrays.asList(conditionIds)));
- synchronized(mMutex) {
- final ArraySet<Uri> newIds = safeSet(conditionIds);
- final int N = mRecords.size();
- boolean changed = false;
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- final boolean automatic = newIds.contains(r.id);
- if (!r.isAutomatic && automatic) {
- // subscribe to new automatic
- subscribeLocked(r);
- r.isAutomatic = true;
- changed = true;
- } else if (r.isAutomatic && !automatic) {
- // unsubscribe from old automatic
- unsubscribeLocked(r);
- r.isAutomatic = false;
- changed = true;
- }
- }
- if (save && changed) {
- saveZenConfigLocked();
- }
- }
- }
-
- public Condition[] getAutomaticZenModeConditions() {
- synchronized(mMutex) {
- final int N = mRecords.size();
- ArrayList<Condition> rt = null;
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- if (r.isAutomatic && r.condition != null) {
- if (rt == null) rt = new ArrayList<Condition>();
- rt.add(r.condition);
- }
- }
- return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]);
- }
- }
-
private void unsubscribeLocked(ConditionRecord r) {
if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
final IConditionProvider provider = provider(r);
@@ -475,6 +323,7 @@ public class ConditionProviders extends ManagedServices {
Slog.w(TAG, "Error unsubscribing to " + r, e);
re = e;
}
+ r.subscribed = false;
}
ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re);
}
@@ -495,7 +344,7 @@ public class ConditionProviders extends ManagedServices {
for (int i = mRecords.size() - 1; i >= 0; i--) {
final ConditionRecord r = mRecords.get(i);
if (r.info != info) continue;
- if (r.isManual || r.isAutomatic) continue;
+ if (r.subscribed) continue;
mRecords.remove(i);
}
try {
@@ -506,103 +355,12 @@ public class ConditionProviders extends ManagedServices {
}
}
- private void loadZenConfig() {
- final ZenModeConfig config = mZenModeHelper.getConfig();
- if (config == null) {
- if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
- return;
- }
- synchronized (mMutex) {
- final boolean changingExit = !Objects.equals(mExitCondition, config.exitCondition);
- mExitCondition = config.exitCondition;
- mExitConditionComponent = config.exitConditionComponent;
- if (changingExit) {
- ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "config");
- }
- if (mDowntime != null) {
- mDowntime.setConfig(config);
- }
- if (config.conditionComponents == null || config.conditionIds == null
- || config.conditionComponents.length != config.conditionIds.length) {
- if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
- setAutomaticZenModeConditions(null, false /*save*/);
- return;
- }
- final ArraySet<Uri> newIds = new ArraySet<Uri>();
- final int N = config.conditionComponents.length;
- for (int i = 0; i < N; i++) {
- final ComponentName component = config.conditionComponents[i];
- final Uri id = config.conditionIds[i];
- if (component != null && id != null) {
- getRecordLocked(id, component); // ensure record exists
- newIds.add(id);
- }
- }
- if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N);
- setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/);
- }
- }
-
- private void saveZenConfigLocked() {
- ZenModeConfig config = mZenModeHelper.getConfig();
- if (config == null) return;
- config = config.copy();
- final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>();
- final int automaticN = mRecords.size();
- for (int i = 0; i < automaticN; i++) {
- final ConditionRecord r = mRecords.get(i);
- if (r.isAutomatic) {
- automatic.add(r);
- }
- }
- if (automatic.isEmpty()) {
- config.conditionComponents = null;
- config.conditionIds = null;
- } else {
- final int N = automatic.size();
- config.conditionComponents = new ComponentName[N];
- config.conditionIds = new Uri[N];
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = automatic.get(i);
- config.conditionComponents[i] = r.component;
- config.conditionIds[i] = r.id;
- }
- }
- config.exitCondition = mExitCondition;
- config.exitConditionComponent = mExitConditionComponent;
- if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
- mZenModeHelper.setConfig(config);
- }
-
- private void onManualConditionClearing() {
- if (mDowntime != null) {
- mDowntime.onManualConditionClearing();
- }
- }
-
- private class ZenModeHelperCallback extends ZenModeHelper.Callback {
- @Override
- void onConfigChanged() {
- loadZenConfig();
- }
-
- @Override
- void onZenModeChanged() {
- final int mode = mZenModeHelper.getZenMode();
- if (mode == Global.ZEN_MODE_OFF) {
- // ensure any manual condition is cleared
- setZenModeCondition(null, "zenOff");
- }
- }
- }
-
private static class ConditionRecord {
public final Uri id;
public final ComponentName component;
public Condition condition;
public ManagedServiceInfo info;
- public boolean isAutomatic;
- public boolean isManual;
+ public boolean subscribed;
private ConditionRecord(Uri id, ComponentName component) {
this.id = id;
@@ -612,10 +370,16 @@ public class ConditionProviders extends ManagedServices {
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
- .append(id).append(",component=").append(component);
- if (isAutomatic) sb.append(",automatic");
- if (isManual) sb.append(",manual");
+ .append(id).append(",component=").append(component)
+ .append(",subscribed=").append(subscribed);
return sb.append(']').toString();
}
}
+
+ public interface Callback {
+ void onBootComplete();
+ void onConditionChanged(Uri id, Condition condition);
+ void onUserSwitched();
+ }
+
}
diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
index 37aacaa..d223353 100644
--- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java
+++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
@@ -25,7 +25,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionProvider;
import android.service.notification.ZenModeConfig;
import android.text.format.DateUtils;
@@ -38,7 +37,7 @@ import java.io.PrintWriter;
import java.util.Date;
/** Built-in zen condition provider for simple time-based conditions */
-public class CountdownConditionProvider extends ConditionProviderService {
+public class CountdownConditionProvider extends SystemConditionProviderService {
private static final String TAG = "CountdownConditions";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -59,6 +58,27 @@ public class CountdownConditionProvider extends ConditionProviderService {
if (DEBUG) Slog.d(TAG, "new CountdownConditionProvider()");
}
+ @Override
+ public ComponentName getComponent() {
+ return COMPONENT;
+ }
+
+ @Override
+ public boolean isValidConditionid(Uri id) {
+ return ZenModeConfig.isValidCountdownConditionId(id);
+ }
+
+ @Override
+ public void attachBase(Context base) {
+ attachBaseContext(base);
+ }
+
+ @Override
+ public IConditionProvider asInterface() {
+ return (IConditionProvider) onBind(null);
+ }
+
+ @Override
public void dump(PrintWriter pw, DumpFilter filter) {
pw.println(" CountdownConditionProvider:");
pw.print(" mConnected="); pw.println(mConnected);
@@ -154,11 +174,4 @@ public class CountdownConditionProvider extends ConditionProviderService {
return new Date(time) + " (" + time + ")";
}
- public void attachBase(Context base) {
- attachBaseContext(base);
- }
-
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
}
diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
deleted file mode 100644
index df4ecfd..0000000
--- a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
+++ /dev/null
@@ -1,409 +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.server.notification;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.Uri;
-import android.provider.Settings.Global;
-import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
-import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
-import android.text.format.DateFormat;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.internal.R;
-import com.android.server.notification.NotificationManagerService.DumpFilter;
-
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.TimeZone;
-
-/** Built-in zen condition provider for managing downtime */
-public class DowntimeConditionProvider extends ConditionProviderService {
- private static final String TAG = "DowntimeConditions";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- public static final ComponentName COMPONENT =
- new ComponentName("android", DowntimeConditionProvider.class.getName());
-
- private static final String ENTER_ACTION = TAG + ".enter";
- private static final int ENTER_CODE = 100;
- private static final String EXIT_ACTION = TAG + ".exit";
- private static final int EXIT_CODE = 101;
- private static final String EXTRA_TIME = "time";
-
- private static final long SECONDS = 1000;
- private static final long MINUTES = 60 * SECONDS;
- private static final long HOURS = 60 * MINUTES;
-
- private final Context mContext = this;
- private final DowntimeCalendar mCalendar = new DowntimeCalendar();
- private final FiredAlarms mFiredAlarms = new FiredAlarms();
- private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
- private final ConditionProviders mConditionProviders;
- private final NextAlarmTracker mTracker;
- private final ZenModeHelper mZenModeHelper;
-
- private boolean mConnected;
- private long mLookaheadThreshold;
- private ZenModeConfig mConfig;
- private boolean mDowntimed;
- private boolean mConditionClearing;
- private boolean mRequesting;
-
- public DowntimeConditionProvider(ConditionProviders conditionProviders,
- NextAlarmTracker tracker, ZenModeHelper zenModeHelper) {
- if (DEBUG) Slog.d(TAG, "new DowntimeConditionProvider()");
- mConditionProviders = conditionProviders;
- mTracker = tracker;
- mZenModeHelper = zenModeHelper;
- }
-
- public void dump(PrintWriter pw, DumpFilter filter) {
- pw.println(" DowntimeConditionProvider:");
- pw.print(" mConnected="); pw.println(mConnected);
- pw.print(" mSubscriptions="); pw.println(mSubscriptions);
- pw.print(" mLookaheadThreshold="); pw.print(mLookaheadThreshold);
- pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")");
- pw.print(" mCalendar="); pw.println(mCalendar);
- pw.print(" mFiredAlarms="); pw.println(mFiredAlarms);
- pw.print(" mDowntimed="); pw.println(mDowntimed);
- pw.print(" mConditionClearing="); pw.println(mConditionClearing);
- pw.print(" mRequesting="); pw.println(mRequesting);
- }
-
- public void attachBase(Context base) {
- attachBaseContext(base);
- }
-
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
-
- @Override
- public void onConnected() {
- if (DEBUG) Slog.d(TAG, "onConnected");
- mConnected = true;
- mLookaheadThreshold = PropConfig.getInt(mContext, "downtime.condition.lookahead",
- R.integer.config_downtime_condition_lookahead_threshold_hrs) * HOURS;
- final IntentFilter filter = new IntentFilter();
- filter.addAction(ENTER_ACTION);
- filter.addAction(EXIT_ACTION);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- mContext.registerReceiver(mReceiver, filter);
- mTracker.addCallback(mTrackerCallback);
- mZenModeHelper.addCallback(mZenCallback);
- init();
- }
-
- @Override
- public void onDestroy() {
- if (DEBUG) Slog.d(TAG, "onDestroy");
- mTracker.removeCallback(mTrackerCallback);
- mZenModeHelper.removeCallback(mZenCallback);
- mConnected = false;
- }
-
- @Override
- public void onRequestConditions(int relevance) {
- if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
- if (!mConnected) return;
- mRequesting = (relevance & Condition.FLAG_RELEVANT_NOW) != 0;
- evaluateSubscriptions();
- }
-
- @Override
- public void onSubscribe(Uri conditionId) {
- if (DEBUG) Slog.d(TAG, "onSubscribe conditionId=" + conditionId);
- final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId);
- if (downtime == null) return;
- mFiredAlarms.clear();
- mSubscriptions.add(conditionId);
- notifyCondition(downtime);
- }
-
- private boolean shouldShowCondition() {
- final long now = System.currentTimeMillis();
- if (DEBUG) Slog.d(TAG, "shouldShowCondition now=" + mCalendar.isInDowntime(now)
- + " lookahead="
- + (mCalendar.nextDowntimeStart(now) <= (now + mLookaheadThreshold)));
- return mCalendar.isInDowntime(now)
- || mCalendar.nextDowntimeStart(now) <= (now + mLookaheadThreshold);
- }
-
- private void notifyCondition(DowntimeInfo downtime) {
- if (mConfig == null) {
- // we don't know yet
- notifyCondition(createCondition(downtime, Condition.STATE_UNKNOWN));
- return;
- }
- if (!downtime.equals(mConfig.toDowntimeInfo())) {
- // not the configured downtime, consider it false
- notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
- return;
- }
- if (!shouldShowCondition()) {
- // configured downtime, but not within the time range
- notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
- return;
- }
- if (isZenNone() && mFiredAlarms.findBefore(System.currentTimeMillis())) {
- // within the configured time range, but wake up if none and the next alarm is fired
- notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
- return;
- }
- // within the configured time range, condition still valid
- notifyCondition(createCondition(downtime, Condition.STATE_TRUE));
- }
-
- private boolean isZenNone() {
- return mZenModeHelper.getZenMode() == Global.ZEN_MODE_NO_INTERRUPTIONS;
- }
-
- private boolean isZenOff() {
- return mZenModeHelper.getZenMode() == Global.ZEN_MODE_OFF;
- }
-
- private void evaluateSubscriptions() {
- ArraySet<Uri> conditions = mSubscriptions;
- if (mConfig != null && mRequesting && shouldShowCondition()) {
- final Uri id = ZenModeConfig.toDowntimeConditionId(mConfig.toDowntimeInfo());
- if (!conditions.contains(id)) {
- conditions = new ArraySet<Uri>(conditions);
- conditions.add(id);
- }
- }
- for (Uri conditionId : conditions) {
- final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId);
- if (downtime != null) {
- notifyCondition(downtime);
- }
- }
- }
-
- @Override
- public void onUnsubscribe(Uri conditionId) {
- final boolean current = mSubscriptions.contains(conditionId);
- if (DEBUG) Slog.d(TAG, "onUnsubscribe conditionId=" + conditionId + " current=" + current);
- mSubscriptions.remove(conditionId);
- mFiredAlarms.clear();
- }
-
- public void setConfig(ZenModeConfig config) {
- if (Objects.equals(mConfig, config)) return;
- final boolean downtimeChanged = mConfig == null || config == null
- || !mConfig.toDowntimeInfo().equals(config.toDowntimeInfo());
- mConfig = config;
- if (DEBUG) Slog.d(TAG, "setConfig downtimeChanged=" + downtimeChanged);
- if (mConnected && downtimeChanged) {
- mDowntimed = false;
- init();
- }
- // when active, mark downtime as entered for today
- if (mConfig != null && mConfig.exitCondition != null
- && ZenModeConfig.isValidDowntimeConditionId(mConfig.exitCondition.id)) {
- mDowntimed = true;
- }
- }
-
- public void onManualConditionClearing() {
- mConditionClearing = true;
- }
-
- private Condition createCondition(DowntimeInfo downtime, int state) {
- if (downtime == null) return null;
- final Uri id = ZenModeConfig.toDowntimeConditionId(downtime);
- final String skeleton = DateFormat.is24HourFormat(mContext) ? "Hm" : "hma";
- final Locale locale = Locale.getDefault();
- final String pattern = DateFormat.getBestDateTimePattern(locale, skeleton);
- final long now = System.currentTimeMillis();
- long endTime = mCalendar.getNextTime(now, downtime.endHour, downtime.endMinute);
- if (isZenNone()) {
- final AlarmClockInfo nextAlarm = mTracker.getNextAlarm();
- final long nextAlarmTime = nextAlarm != null ? nextAlarm.getTriggerTime() : 0;
- if (nextAlarmTime > now && nextAlarmTime < endTime) {
- endTime = nextAlarmTime;
- }
- }
- final String formatted = new SimpleDateFormat(pattern, locale).format(new Date(endTime));
- final String summary = mContext.getString(R.string.downtime_condition_summary, formatted);
- final String line1 = mContext.getString(R.string.downtime_condition_line_one);
- return new Condition(id, summary, line1, formatted, 0, state, Condition.FLAG_RELEVANT_NOW);
- }
-
- private void init() {
- mCalendar.setDowntimeInfo(mConfig != null ? mConfig.toDowntimeInfo() : null);
- evaluateSubscriptions();
- updateAlarms();
- evaluateAutotrigger();
- }
-
- private void updateAlarms() {
- if (mConfig == null) return;
- updateAlarm(ENTER_ACTION, ENTER_CODE, mConfig.sleepStartHour, mConfig.sleepStartMinute);
- updateAlarm(EXIT_ACTION, EXIT_CODE, mConfig.sleepEndHour, mConfig.sleepEndMinute);
- }
-
-
- private void updateAlarm(String action, int requestCode, int hr, int min) {
- final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- final long now = System.currentTimeMillis();
- final long time = mCalendar.getNextTime(now, hr, min);
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode,
- new Intent(action)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_TIME, time),
- PendingIntent.FLAG_UPDATE_CURRENT);
- alarms.cancel(pendingIntent);
- if (mConfig.sleepMode != null) {
- if (DEBUG) Slog.d(TAG, String.format("Scheduling %s for %s, in %s, now=%s",
- action, ts(time), NextAlarmTracker.formatDuration(time - now), ts(now)));
- alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
- }
- }
-
- private static String ts(long time) {
- return new Date(time) + " (" + time + ")";
- }
-
- private void onEvaluateNextAlarm(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- if (!booted) return; // we don't know yet
- if (DEBUG) Slog.d(TAG, "onEvaluateNextAlarm " + mTracker.formatAlarmDebug(nextAlarm));
- if (nextAlarm != null && wakeupTime > 0 && System.currentTimeMillis() > wakeupTime) {
- if (DEBUG) Slog.d(TAG, "Alarm fired: " + mTracker.formatAlarmDebug(wakeupTime));
- mFiredAlarms.add(wakeupTime);
- }
- evaluateSubscriptions();
- }
-
- private void evaluateAutotrigger() {
- String skipReason = null;
- if (mConfig == null) {
- skipReason = "no config";
- } else if (mDowntimed) {
- skipReason = "already downtimed";
- } else if (mZenModeHelper.getZenMode() != Global.ZEN_MODE_OFF) {
- skipReason = "already in zen";
- } else if (!mCalendar.isInDowntime(System.currentTimeMillis())) {
- skipReason = "not in downtime";
- }
- if (skipReason != null) {
- ZenLog.traceDowntimeAutotrigger("Autotrigger skipped: " + skipReason);
- return;
- }
- ZenLog.traceDowntimeAutotrigger("Autotrigger fired");
- mZenModeHelper.setZenMode(mConfig.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS
- : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, "downtime");
- final Condition condition = createCondition(mConfig.toDowntimeInfo(), Condition.STATE_TRUE);
- mConditionProviders.setZenModeCondition(condition, "downtime");
- }
-
- private BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final long now = System.currentTimeMillis();
- if (ENTER_ACTION.equals(action) || EXIT_ACTION.equals(action)) {
- final long schTime = intent.getLongExtra(EXTRA_TIME, 0);
- if (DEBUG) Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s",
- action, ts(schTime), ts(now), now - schTime));
- if (ENTER_ACTION.equals(action)) {
- evaluateAutotrigger();
- } else /*EXIT_ACTION*/ {
- mDowntimed = false;
- }
- mFiredAlarms.clear();
- } else if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
- if (DEBUG) Slog.d(TAG, "timezone changed to " + TimeZone.getDefault());
- mCalendar.setTimeZone(TimeZone.getDefault());
- mFiredAlarms.clear();
- } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
- if (DEBUG) Slog.d(TAG, "time changed to " + now);
- mFiredAlarms.clear();
- } else {
- if (DEBUG) Slog.d(TAG, action + " fired at " + now);
- }
- evaluateSubscriptions();
- updateAlarms();
- }
- };
-
- private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() {
- @Override
- public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- DowntimeConditionProvider.this.onEvaluateNextAlarm(nextAlarm, wakeupTime, booted);
- }
- };
-
- private final ZenModeHelper.Callback mZenCallback = new ZenModeHelper.Callback() {
- @Override
- void onZenModeChanged() {
- if (mConditionClearing && isZenOff()) {
- evaluateAutotrigger();
- }
- mConditionClearing = false;
- evaluateSubscriptions();
- }
- };
-
- private class FiredAlarms {
- private final ArraySet<Long> mFiredAlarms = new ArraySet<Long>();
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mFiredAlarms.size(); i++) {
- if (i > 0) sb.append(',');
- sb.append(mTracker.formatAlarmDebug(mFiredAlarms.valueAt(i)));
- }
- return sb.toString();
- }
-
- public void add(long firedAlarm) {
- mFiredAlarms.add(firedAlarm);
- }
-
- public void clear() {
- mFiredAlarms.clear();
- }
-
- public boolean findBefore(long time) {
- for (int i = 0; i < mFiredAlarms.size(); i++) {
- if (mFiredAlarms.valueAt(i) < time) {
- return true;
- }
- }
- return false;
- }
- }
-}
diff --git a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java b/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
deleted file mode 100644
index 1634c65..0000000
--- a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
+++ /dev/null
@@ -1,224 +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.server.notification;
-
-import android.app.AlarmManager;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.Uri;
-import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
-import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.internal.R;
-import com.android.server.notification.NotificationManagerService.DumpFilter;
-
-import java.io.PrintWriter;
-
-/**
- * Built-in zen condition provider for alarm-clock-based conditions.
- *
- * <p>If the user's next alarm is within a lookahead threshold (config, default 12hrs), advertise
- * it as an exit condition for zen mode.
- *
- * <p>The next alarm is defined as {@link AlarmManager#getNextAlarmClock(int)}, which does not
- * survive a reboot. Maintain the illusion of a consistent next alarm value by holding on to
- * a persisted condition until we receive the first value after reboot, or timeout with no value.
- */
-public class NextAlarmConditionProvider extends ConditionProviderService {
- private static final String TAG = "NextAlarmConditions";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final long SECONDS = 1000;
- private static final long MINUTES = 60 * SECONDS;
- private static final long HOURS = 60 * MINUTES;
-
- private static final long BAD_CONDITION = -1;
-
- public static final ComponentName COMPONENT =
- new ComponentName("android", NextAlarmConditionProvider.class.getName());
-
- private final Context mContext = this;
- private final NextAlarmTracker mTracker;
- private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
-
- private boolean mConnected;
- private long mLookaheadThreshold;
- private boolean mRequesting;
-
- public NextAlarmConditionProvider(NextAlarmTracker tracker) {
- if (DEBUG) Slog.d(TAG, "new NextAlarmConditionProvider()");
- mTracker = tracker;
- }
-
- public void dump(PrintWriter pw, DumpFilter filter) {
- pw.println(" NextAlarmConditionProvider:");
- pw.print(" mConnected="); pw.println(mConnected);
- pw.print(" mLookaheadThreshold="); pw.print(mLookaheadThreshold);
- pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")");
- pw.print(" mSubscriptions="); pw.println(mSubscriptions);
- pw.print(" mRequesting="); pw.println(mRequesting);
- }
-
- @Override
- public void onConnected() {
- if (DEBUG) Slog.d(TAG, "onConnected");
- mLookaheadThreshold = PropConfig.getInt(mContext, "nextalarm.condition.lookahead",
- R.integer.config_next_alarm_condition_lookahead_threshold_hrs) * HOURS;
- mConnected = true;
- mTracker.addCallback(mTrackerCallback);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (DEBUG) Slog.d(TAG, "onDestroy");
- mTracker.removeCallback(mTrackerCallback);
- mConnected = false;
- }
-
- @Override
- public void onRequestConditions(int relevance) {
- if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
- if (!mConnected) return;
- mRequesting = (relevance & Condition.FLAG_RELEVANT_NOW) != 0;
- mTracker.evaluate();
- }
-
- @Override
- public void onSubscribe(Uri conditionId) {
- if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
- if (tryParseNextAlarmCondition(conditionId) == BAD_CONDITION) {
- notifyCondition(conditionId, null, Condition.STATE_FALSE, "badCondition");
- return;
- }
- mSubscriptions.add(conditionId);
- mTracker.evaluate();
- }
-
- @Override
- public void onUnsubscribe(Uri conditionId) {
- if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
- mSubscriptions.remove(conditionId);
- }
-
- public void attachBase(Context base) {
- attachBaseContext(base);
- }
-
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
-
- private boolean isWithinLookaheadThreshold(AlarmClockInfo alarm) {
- if (alarm == null) return false;
- final long delta = NextAlarmTracker.getEarlyTriggerTime(alarm) - System.currentTimeMillis();
- return delta > 0 && (mLookaheadThreshold <= 0 || delta < mLookaheadThreshold);
- }
-
- private void notifyCondition(Uri id, AlarmClockInfo alarm, int state, String reason) {
- final String formattedAlarm = alarm == null ? "" : mTracker.formatAlarm(alarm);
- if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state)
- + " alarm=" + formattedAlarm + " reason=" + reason);
- notifyCondition(new Condition(id,
- mContext.getString(R.string.zen_mode_next_alarm_summary, formattedAlarm),
- mContext.getString(R.string.zen_mode_next_alarm_line_one),
- formattedAlarm, 0, state, Condition.FLAG_RELEVANT_NOW));
- }
-
- private Uri newConditionId(AlarmClockInfo nextAlarm) {
- return new Uri.Builder().scheme(Condition.SCHEME)
- .authority(ZenModeConfig.SYSTEM_AUTHORITY)
- .appendPath(ZenModeConfig.NEXT_ALARM_PATH)
- .appendPath(Integer.toString(mTracker.getCurrentUserId()))
- .appendPath(Long.toString(nextAlarm.getTriggerTime()))
- .build();
- }
-
- private long tryParseNextAlarmCondition(Uri conditionId) {
- return conditionId != null && conditionId.getScheme().equals(Condition.SCHEME)
- && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
- && conditionId.getPathSegments().size() == 3
- && conditionId.getPathSegments().get(0).equals(ZenModeConfig.NEXT_ALARM_PATH)
- && conditionId.getPathSegments().get(1)
- .equals(Integer.toString(mTracker.getCurrentUserId()))
- ? tryParseLong(conditionId.getPathSegments().get(2), BAD_CONDITION)
- : BAD_CONDITION;
- }
-
- private static long tryParseLong(String value, long defValue) {
- if (TextUtils.isEmpty(value)) return defValue;
- try {
- return Long.valueOf(value);
- } catch (NumberFormatException e) {
- return defValue;
- }
- }
-
- private void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- final boolean withinThreshold = isWithinLookaheadThreshold(nextAlarm);
- final long nextAlarmTime = nextAlarm != null ? nextAlarm.getTriggerTime() : 0;
- if (DEBUG) Slog.d(TAG, "onEvaluate mSubscriptions=" + mSubscriptions
- + " nextAlarmTime=" + mTracker.formatAlarmDebug(nextAlarmTime)
- + " nextAlarmWakeup=" + mTracker.formatAlarmDebug(wakeupTime)
- + " withinThreshold=" + withinThreshold
- + " booted=" + booted);
-
- ArraySet<Uri> conditions = mSubscriptions;
- if (mRequesting && nextAlarm != null && withinThreshold) {
- final Uri id = newConditionId(nextAlarm);
- if (!conditions.contains(id)) {
- conditions = new ArraySet<Uri>(conditions);
- conditions.add(id);
- }
- }
- for (Uri conditionId : conditions) {
- final long time = tryParseNextAlarmCondition(conditionId);
- if (time == BAD_CONDITION) {
- notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "badCondition");
- } else if (!booted) {
- // we don't know yet
- if (mSubscriptions.contains(conditionId)) {
- notifyCondition(conditionId, nextAlarm, Condition.STATE_UNKNOWN, "!booted");
- }
- } else if (time != nextAlarmTime) {
- // next alarm changed since subscription, consider obsolete
- notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "changed");
- } else if (!withinThreshold) {
- // next alarm outside threshold or in the past, condition = false
- notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "!within");
- } else {
- // next alarm within threshold and in the future, condition = true
- notifyCondition(conditionId, nextAlarm, Condition.STATE_TRUE, "within");
- }
- }
- }
-
- private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() {
- @Override
- public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- NextAlarmConditionProvider.this.onEvaluate(nextAlarm, wakeupTime, booted);
- }
- };
-}
diff --git a/services/core/java/com/android/server/notification/NextAlarmTracker.java b/services/core/java/com/android/server/notification/NextAlarmTracker.java
deleted file mode 100644
index 234f545..0000000
--- a/services/core/java/com/android/server/notification/NextAlarmTracker.java
+++ /dev/null
@@ -1,263 +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.server.notification;
-
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.text.format.DateFormat;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.server.notification.NotificationManagerService.DumpFilter;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Locale;
-
-/** Helper for tracking updates to the current user's next alarm. */
-public class NextAlarmTracker {
- private static final String TAG = "NextAlarmTracker";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final String ACTION_TRIGGER = TAG + ".trigger";
- private static final String EXTRA_TRIGGER = "trigger";
- private static final int REQUEST_CODE = 100;
-
- private static final long SECONDS = 1000;
- private static final long MINUTES = 60 * SECONDS;
- private static final long NEXT_ALARM_UPDATE_DELAY = 1 * SECONDS; // treat clear+set as update
- private static final long EARLY = 5 * SECONDS; // fire early, ensure alarm stream is unmuted
- private static final long WAIT_AFTER_INIT = 5 * MINUTES;// for initial alarm re-registration
- private static final long WAIT_AFTER_BOOT = 20 * SECONDS; // for initial alarm re-registration
-
- private final Context mContext;
- private final H mHandler = new H();
- private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
-
- private long mInit;
- private boolean mRegistered;
- private AlarmManager mAlarmManager;
- private int mCurrentUserId;
- private long mScheduledAlarmTime;
- private long mBootCompleted;
- private PowerManager.WakeLock mWakeLock;
-
- public NextAlarmTracker(Context context) {
- mContext = context;
- }
-
- public void dump(PrintWriter pw, DumpFilter filter) {
- pw.println(" NextAlarmTracker:");
- pw.print(" len(mCallbacks)="); pw.println(mCallbacks.size());
- pw.print(" mRegistered="); pw.println(mRegistered);
- pw.print(" mInit="); pw.println(mInit);
- pw.print(" mBootCompleted="); pw.println(mBootCompleted);
- pw.print(" mCurrentUserId="); pw.println(mCurrentUserId);
- pw.print(" mScheduledAlarmTime="); pw.println(formatAlarmDebug(mScheduledAlarmTime));
- pw.print(" mWakeLock="); pw.println(mWakeLock);
- }
-
- public void addCallback(Callback callback) {
- mCallbacks.add(callback);
- }
-
- public void removeCallback(Callback callback) {
- mCallbacks.remove(callback);
- }
-
- public int getCurrentUserId() {
- return mCurrentUserId;
- }
-
- public AlarmClockInfo getNextAlarm() {
- return mAlarmManager.getNextAlarmClock(mCurrentUserId);
- }
-
- public void onUserSwitched() {
- reset();
- }
-
- public void init() {
- mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- final PowerManager p = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = p.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
- mInit = System.currentTimeMillis();
- reset();
- }
-
- public void reset() {
- if (mRegistered) {
- mContext.unregisterReceiver(mReceiver);
- }
- mCurrentUserId = ActivityManager.getCurrentUser();
- final IntentFilter filter = new IntentFilter();
- filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
- filter.addAction(ACTION_TRIGGER);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- filter.addAction(Intent.ACTION_BOOT_COMPLETED);
- mContext.registerReceiverAsUser(mReceiver, new UserHandle(mCurrentUserId), filter, null,
- null);
- mRegistered = true;
- evaluate();
- }
-
- public void destroy() {
- if (mRegistered) {
- mContext.unregisterReceiver(mReceiver);
- mRegistered = false;
- }
- }
-
- public void evaluate() {
- mHandler.postEvaluate(0);
- }
-
- private void fireEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- for (Callback callback : mCallbacks) {
- callback.onEvaluate(nextAlarm, wakeupTime, booted);
- }
- }
-
- private void handleEvaluate() {
- final AlarmClockInfo nextAlarm = mAlarmManager.getNextAlarmClock(mCurrentUserId);
- final long triggerTime = getEarlyTriggerTime(nextAlarm);
- final long now = System.currentTimeMillis();
- final boolean alarmUpcoming = triggerTime > now;
- final boolean booted = isDoneWaitingAfterBoot(now);
- if (DEBUG) Slog.d(TAG, "handleEvaluate nextAlarm=" + formatAlarmDebug(triggerTime)
- + " alarmUpcoming=" + alarmUpcoming
- + " booted=" + booted);
- fireEvaluate(nextAlarm, triggerTime, booted);
- if (!booted) {
- // recheck after boot
- final long recheckTime = (mBootCompleted > 0 ? mBootCompleted : now) + WAIT_AFTER_BOOT;
- rescheduleAlarm(recheckTime);
- return;
- }
- if (alarmUpcoming) {
- // wake up just before the next alarm
- rescheduleAlarm(triggerTime);
- }
- }
-
- public static long getEarlyTriggerTime(AlarmClockInfo alarm) {
- return alarm != null ? (alarm.getTriggerTime() - EARLY) : 0;
- }
-
- private boolean isDoneWaitingAfterBoot(long time) {
- if (mBootCompleted > 0) return (time - mBootCompleted) > WAIT_AFTER_BOOT;
- if (mInit > 0) return (time - mInit) > WAIT_AFTER_INIT;
- return true;
- }
-
- public static String formatDuration(long millis) {
- final StringBuilder sb = new StringBuilder();
- TimeUtils.formatDuration(millis, sb);
- return sb.toString();
- }
-
- public String formatAlarm(AlarmClockInfo alarm) {
- return alarm != null ? formatAlarm(alarm.getTriggerTime()) : null;
- }
-
- private String formatAlarm(long time) {
- return formatAlarm(time, "Hm", "hma");
- }
-
- private String formatAlarm(long time, String skeleton24, String skeleton12) {
- final String skeleton = DateFormat.is24HourFormat(mContext) ? skeleton24 : skeleton12;
- final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
- return DateFormat.format(pattern, time).toString();
- }
-
- public String formatAlarmDebug(AlarmClockInfo alarm) {
- return formatAlarmDebug(alarm != null ? alarm.getTriggerTime() : 0);
- }
-
- public String formatAlarmDebug(long time) {
- if (time <= 0) return Long.toString(time);
- return String.format("%s (%s)", time, formatAlarm(time, "Hms", "hmsa"));
- }
-
- private void rescheduleAlarm(long time) {
- if (DEBUG) Slog.d(TAG, "rescheduleAlarm " + time);
- final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE,
- new Intent(ACTION_TRIGGER)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_TRIGGER, time),
- PendingIntent.FLAG_UPDATE_CURRENT);
- alarms.cancel(pendingIntent);
- mScheduledAlarmTime = time;
- if (time > 0) {
- if (DEBUG) Slog.d(TAG, String.format("Scheduling alarm for %s (in %s)",
- formatAlarmDebug(time), formatDuration(time - System.currentTimeMillis())));
- alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
- }
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (DEBUG) Slog.d(TAG, "onReceive " + action);
- long delay = 0;
- if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
- delay = NEXT_ALARM_UPDATE_DELAY;
- if (DEBUG) Slog.d(TAG, String.format(" next alarm for user %s: %s",
- mCurrentUserId,
- formatAlarmDebug(mAlarmManager.getNextAlarmClock(mCurrentUserId))));
- } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
- mBootCompleted = System.currentTimeMillis();
- }
- mHandler.postEvaluate(delay);
- mWakeLock.acquire(delay + 5000); // stay awake during evaluate
- }
- };
-
- private class H extends Handler {
- private static final int MSG_EVALUATE = 1;
-
- public void postEvaluate(long delay) {
- removeMessages(MSG_EVALUATE);
- sendEmptyMessageDelayed(MSG_EVALUATE, delay);
- }
-
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_EVALUATE) {
- handleEvaluate();
- }
- }
- }
-
- public interface Callback {
- void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted);
- }
-}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c330046..4cf2909 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -879,7 +879,8 @@ public class NotificationManagerService extends SystemService {
mRankingHelper = new RankingHelper(getContext(),
new RankingWorkerHandler(mRankingThread.getLooper()),
extractorNames);
- mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper());
+ mConditionProviders = new ConditionProviders(getContext(), mHandler, mUserProfiles);
+ mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders);
mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@Override
public void onConfigChanged() {
@@ -900,8 +901,6 @@ public class NotificationManagerService extends SystemService {
importOldBlockDb();
mListeners = new NotificationListeners();
- mConditionProviders = new ConditionProviders(getContext(),
- mHandler, mUserProfiles, mZenModeHelper);
mStatusBar = getLocalService(StatusBarManagerInternal.class);
mStatusBar.setNotificationDelegate(mNotificationDelegate);
@@ -936,7 +935,7 @@ public class NotificationManagerService extends SystemService {
Settings.Global.DEVICE_PROVISIONED, 0)) {
mDisableNotificationEffects = true;
}
- mZenModeHelper.readZenModeFromSetting();
+ mZenModeHelper.initZenMode();
mInterruptionFilter = mZenModeHelper.getZenModeListenerInterruptionFilter();
mUserProfiles.updateCache(getContext());
@@ -1490,23 +1489,28 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public int getZenMode() {
+ return mZenModeHelper.getZenMode();
+ }
+
+ @Override
public ZenModeConfig getZenModeConfig() {
enforceSystemOrSystemUIOrVolume("INotificationManager.getZenModeConfig");
return mZenModeHelper.getConfig();
}
@Override
- public boolean setZenModeConfig(ZenModeConfig config) {
+ public boolean setZenModeConfig(ZenModeConfig config, String reason) {
checkCallerIsSystem();
- return mZenModeHelper.setConfig(config);
+ return mZenModeHelper.setConfig(config, reason);
}
@Override
- public void setZenMode(int mode) throws RemoteException {
+ public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
enforceSystemOrSystemUIOrVolume("INotificationManager.setZenMode");
final long identity = Binder.clearCallingIdentity();
try {
- mZenModeHelper.setZenMode(mode, "NotificationManager");
+ mZenModeHelper.setManualZenMode(mode, conditionId, reason);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1528,30 +1532,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void requestZenModeConditions(IConditionListener callback, int relevance) {
enforceSystemOrSystemUIOrVolume("INotificationManager.requestZenModeConditions");
- mConditionProviders.requestZenModeConditions(callback, relevance);
- }
-
- @Override
- public void setZenModeCondition(Condition condition) {
- enforceSystemOrSystemUIOrVolume("INotificationManager.setZenModeCondition");
- final long identity = Binder.clearCallingIdentity();
- try {
- mConditionProviders.setZenModeCondition(condition, "binderCall");
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
- public void setAutomaticZenModeConditions(Uri[] conditionIds) {
- enforceSystemOrSystemUI("INotificationManager.setAutomaticZenModeConditions");
- mConditionProviders.setAutomaticZenModeConditions(conditionIds);
- }
-
- @Override
- public Condition[] getAutomaticZenModeConditions() {
- enforceSystemOrSystemUI("INotificationManager.getAutomaticZenModeConditions");
- return mConditionProviders.getAutomaticZenModeConditions();
+ mZenModeHelper.requestZenModeConditions(callback, relevance);
}
private void enforceSystemOrSystemUIOrVolume(String message) {
@@ -1603,7 +1584,7 @@ public class NotificationManagerService extends SystemService {
@Override
public boolean isSystemConditionProviderEnabled(String path) {
enforceSystemOrSystemUIOrVolume("INotificationManager.isSystemConditionProviderEnabled");
- return mConditionProviders.isSystemConditionProviderEnabled(path);
+ return mConditionProviders.isSystemProviderEnabled(path);
}
};
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index e029c58..4696771 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -28,6 +28,7 @@ import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
import android.util.Log;
+import com.android.internal.logging.MetricsLogger;
import com.android.server.notification.NotificationManagerService.DumpFilter;
import java.io.PrintWriter;
@@ -56,8 +57,10 @@ public class NotificationUsageStats {
// Guarded by synchronized(this).
private final Map<String, AggregatedStats> mStats = new HashMap<String, AggregatedStats>();
private final SQLiteLog mSQLiteLog;
+ private final Context mContext;
public NotificationUsageStats(Context context) {
+ mContext = context;
mSQLiteLog = ENABLE_SQLITE_LOG ? new SQLiteLog(context) : null;
}
@@ -103,6 +106,8 @@ public class NotificationUsageStats {
* Called when the user dismissed the notification via the UI.
*/
public synchronized void registerDismissedByUser(NotificationRecord notification) {
+ MetricsLogger.histogram(mContext, "note_dismiss_longevity",
+ (int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
notification.stats.onDismiss();
for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
stats.numDismissedByUser++;
@@ -117,6 +122,8 @@ public class NotificationUsageStats {
* Called when the user clicked the notification in the UI.
*/
public synchronized void registerClickedByUser(NotificationRecord notification) {
+ MetricsLogger.histogram(mContext, "note_click_longevity",
+ (int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
notification.stats.onClick();
for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
stats.numClickedByUser++;
diff --git a/services/core/java/com/android/server/notification/DowntimeCalendar.java b/services/core/java/com/android/server/notification/ScheduleCalendar.java
index d14fd40..cea611d 100644
--- a/services/core/java/com/android/server/notification/DowntimeCalendar.java
+++ b/services/core/java/com/android/server/notification/ScheduleCalendar.java
@@ -16,38 +16,36 @@
package com.android.server.notification;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.util.ArraySet;
+
import java.util.Calendar;
import java.util.Objects;
import java.util.TimeZone;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
-import android.util.ArraySet;
-
-public class DowntimeCalendar {
-
+public class ScheduleCalendar {
private final ArraySet<Integer> mDays = new ArraySet<Integer>();
private final Calendar mCalendar = Calendar.getInstance();
- private DowntimeInfo mInfo;
+ private ScheduleInfo mSchedule;
@Override
public String toString() {
- return "DowntimeCalendar[mDays=" + mDays + "]";
+ return "ScheduleCalendar[mDays=" + mDays + "]";
}
- public void setDowntimeInfo(DowntimeInfo info) {
- if (Objects.equals(mInfo, info)) return;
- mInfo = info;
+ public void setSchedule(ScheduleInfo schedule) {
+ if (Objects.equals(mSchedule, schedule)) return;
+ mSchedule = schedule;
updateDays();
}
- public long nextDowntimeStart(long time) {
- if (mInfo == null || mDays.size() == 0) return Long.MAX_VALUE;
- final long start = getTime(time, mInfo.startHour, mInfo.startMinute);
+ public long nextScheduleStart(long time) {
+ if (mSchedule == null || mDays.size() == 0) return Long.MAX_VALUE;
+ final long start = getTime(time, mSchedule.startHour, mSchedule.startMinute);
for (int i = 0; i < Calendar.SATURDAY; i++) {
final long t = addDays(start, i);
- if (t > time && isInDowntime(t)) {
+ if (t > time && isInSchedule(t)) {
return t;
}
}
@@ -58,7 +56,14 @@ public class DowntimeCalendar {
mCalendar.setTimeZone(tz);
}
- public long getNextTime(long now, int hr, int min) {
+ public long getNextChangeTime(long now) {
+ if (mSchedule == null) return 0;
+ final long nextStart = getNextTime(now, mSchedule.startHour, mSchedule.startMinute);
+ final long nextEnd = getNextTime(now, mSchedule.endHour, mSchedule.endMinute);
+ return Math.min(nextStart, nextEnd);
+ }
+
+ private long getNextTime(long now, int hr, int min) {
final long time = getTime(now, hr, min);
return time <= now ? addDays(time, 1) : time;
}
@@ -72,17 +77,17 @@ public class DowntimeCalendar {
return mCalendar.getTimeInMillis();
}
- public boolean isInDowntime(long time) {
- if (mInfo == null || mDays.size() == 0) return false;
- final long start = getTime(time, mInfo.startHour, mInfo.startMinute);
- long end = getTime(time, mInfo.endHour, mInfo.endMinute);
+ public boolean isInSchedule(long time) {
+ if (mSchedule == null || mDays.size() == 0) return false;
+ final long start = getTime(time, mSchedule.startHour, mSchedule.startMinute);
+ long end = getTime(time, mSchedule.endHour, mSchedule.endMinute);
if (end <= start) {
end = addDays(end, 1);
}
- return isInDowntime(-1, time, start, end) || isInDowntime(0, time, start, end);
+ return isInSchedule(-1, time, start, end) || isInSchedule(0, time, start, end);
}
- private boolean isInDowntime(int daysOffset, long time, long start, long end) {
+ private boolean isInSchedule(int daysOffset, long time, long start, long end) {
final int n = Calendar.SATURDAY;
final int day = ((getDayOfWeek(time) - 1) + (daysOffset % n) + n) % n + 1;
start = addDays(start, daysOffset);
@@ -97,10 +102,9 @@ public class DowntimeCalendar {
private void updateDays() {
mDays.clear();
- if (mInfo != null) {
- final int[] days = ZenModeConfig.tryParseDays(mInfo.mode);
- for (int i = 0; days != null && i < days.length; i++) {
- mDays.add(days[i]);
+ if (mSchedule != null && mSchedule.days != null) {
+ for (int i = 0; i < mSchedule.days.length; i++) {
+ mDays.add(mSchedule.days[i]);
}
}
}
@@ -110,4 +114,4 @@ public class DowntimeCalendar {
mCalendar.add(Calendar.DATE, days);
return mCalendar.getTimeInMillis();
}
-}
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
new file mode 100644
index 0000000..c997e45
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
@@ -0,0 +1,238 @@
+/*
+ * 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.server.notification;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.IConditionProvider;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.server.notification.NotificationManagerService.DumpFilter;
+
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Built-in zen condition provider for daily scheduled time-based conditions.
+ */
+public class ScheduleConditionProvider extends SystemConditionProviderService {
+ private static final String TAG = "ScheduleConditions";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ public static final ComponentName COMPONENT =
+ new ComponentName("android", ScheduleConditionProvider.class.getName());
+ private static final String NOT_SHOWN = "...";
+ private static final String ACTION_EVALUATE = TAG + ".EVALUATE";
+ private static final int REQUEST_CODE_EVALUATE = 1;
+ private static final String EXTRA_TIME = "time";
+
+ private final Context mContext = this;
+ private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
+
+ private boolean mConnected;
+ private boolean mRegistered;
+
+ public ScheduleConditionProvider() {
+ if (DEBUG) Slog.d(TAG, "new ScheduleConditionProvider()");
+ }
+
+ @Override
+ public ComponentName getComponent() {
+ return COMPONENT;
+ }
+
+ @Override
+ public boolean isValidConditionid(Uri id) {
+ return ZenModeConfig.isValidScheduleConditionId(id);
+ }
+
+ @Override
+ public void dump(PrintWriter pw, DumpFilter filter) {
+ pw.println(" ScheduleConditionProvider:");
+ pw.print(" mConnected="); pw.println(mConnected);
+ pw.print(" mRegistered="); pw.println(mRegistered);
+ pw.println(" mSubscriptions=");
+ final long now = System.currentTimeMillis();
+ for (Uri conditionId : mSubscriptions) {
+ pw.print(" ");
+ pw.print(meetsSchedule(conditionId, now) ? "* " : " ");
+ pw.println(conditionId);
+ }
+ }
+
+ @Override
+ public void onConnected() {
+ if (DEBUG) Slog.d(TAG, "onConnected");
+ mConnected = true;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (DEBUG) Slog.d(TAG, "onDestroy");
+ mConnected = false;
+ }
+
+ @Override
+ public void onRequestConditions(int relevance) {
+ if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
+ // does not advertise conditions
+ }
+
+ @Override
+ public void onSubscribe(Uri conditionId) {
+ if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
+ if (!ZenModeConfig.isValidScheduleConditionId(conditionId)) {
+ notifyCondition(conditionId, Condition.STATE_FALSE, "badCondition");
+ return;
+ }
+ mSubscriptions.add(conditionId);
+ evaluateSubscriptions();
+ }
+
+ @Override
+ public void onUnsubscribe(Uri conditionId) {
+ if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
+ mSubscriptions.remove(conditionId);
+ evaluateSubscriptions();
+ }
+
+ @Override
+ public void attachBase(Context base) {
+ attachBaseContext(base);
+ }
+
+ @Override
+ public IConditionProvider asInterface() {
+ return (IConditionProvider) onBind(null);
+ }
+
+ private void evaluateSubscriptions() {
+ setRegistered(!mSubscriptions.isEmpty());
+ final long now = System.currentTimeMillis();
+ long nextAlarmTime = 0;
+ for (Uri conditionId : mSubscriptions) {
+ final ScheduleCalendar cal = toScheduleCalendar(conditionId);
+ if (cal != null && cal.isInSchedule(now)) {
+ notifyCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule");
+ } else {
+ notifyCondition(conditionId, Condition.STATE_FALSE, "!meetsSchedule");
+ }
+ if (cal != null) {
+ final long nextChangeTime = cal.getNextChangeTime(now);
+ if (nextChangeTime > 0 && nextChangeTime > now) {
+ if (nextAlarmTime == 0 || nextChangeTime < nextAlarmTime) {
+ nextAlarmTime = nextChangeTime;
+ }
+ }
+ }
+ }
+ updateAlarm(now, nextAlarmTime);
+ }
+
+ private void updateAlarm(long now, long time) {
+ final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
+ REQUEST_CODE_EVALUATE,
+ new Intent(ACTION_EVALUATE)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(EXTRA_TIME, time),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ alarms.cancel(pendingIntent);
+ if (time > now) {
+ if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s",
+ ts(time), formatDuration(time - now), ts(now)));
+ alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
+ } else {
+ if (DEBUG) Slog.d(TAG, "Not scheduling evaluate");
+ }
+ }
+
+ private static String ts(long time) {
+ return new Date(time) + " (" + time + ")";
+ }
+
+ private static String formatDuration(long millis) {
+ final StringBuilder sb = new StringBuilder();
+ TimeUtils.formatDuration(millis, sb);
+ return sb.toString();
+ }
+
+ private static boolean meetsSchedule(Uri conditionId, long time) {
+ final ScheduleCalendar cal = toScheduleCalendar(conditionId);
+ return cal != null && cal.isInSchedule(time);
+ }
+
+ private static ScheduleCalendar toScheduleCalendar(Uri conditionId) {
+ final ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(conditionId);
+ if (schedule == null || schedule.days == null || schedule.days.length == 0) return null;
+ final ScheduleCalendar sc = new ScheduleCalendar();
+ sc.setSchedule(schedule);
+ sc.setTimeZone(TimeZone.getDefault());
+ return sc;
+ }
+
+ private void setRegistered(boolean registered) {
+ if (mRegistered == registered) return;
+ if (DEBUG) Slog.d(TAG, "setRegistered " + registered);
+ mRegistered = registered;
+ if (mRegistered) {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ filter.addAction(ACTION_EVALUATE);
+ registerReceiver(mReceiver, filter);
+ } else {
+ unregisterReceiver(mReceiver);
+ }
+ }
+
+ private void notifyCondition(Uri conditionId, int state, String reason) {
+ if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state)
+ + " reason=" + reason);
+ notifyCondition(createCondition(conditionId, state));
+ }
+
+ private Condition createCondition(Uri id, int state) {
+ final String summary = NOT_SHOWN;
+ final String line1 = NOT_SHOWN;
+ final String line2 = NOT_SHOWN;
+ return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS);
+ }
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction());
+ evaluateSubscriptions();
+ }
+ };
+
+}
diff --git a/services/core/java/com/android/server/notification/SystemConditionProviderService.java b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
new file mode 100644
index 0000000..a217623
--- /dev/null
+++ b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
@@ -0,0 +1,36 @@
+/*
+ * 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.server.notification;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.service.notification.ConditionProviderService;
+import android.service.notification.IConditionProvider;
+
+import com.android.server.notification.NotificationManagerService.DumpFilter;
+
+import java.io.PrintWriter;
+
+public abstract class SystemConditionProviderService extends ConditionProviderService {
+
+ abstract public void dump(PrintWriter pw, DumpFilter filter);
+ abstract public void attachBase(Context context);
+ abstract public IConditionProvider asInterface();
+ abstract public ComponentName getComponent();
+ abstract public boolean isValidConditionid(Uri id);
+}
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 5eb318b..10f1696 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -34,6 +34,7 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.LruCache;
import android.util.Slog;
+import com.android.internal.logging.MetricsLogger;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -244,6 +245,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
if (pendingLookups.isEmpty()) {
if (INFO) Slog.i(TAG, "final affinity: " + affinity);
+ if (affinity != NONE) MetricsLogger.count(mBaseContext, "note_with_people", 1);
return null;
}
@@ -453,6 +455,8 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
Slog.d(TAG, "Validation finished in " + (System.currentTimeMillis() - timeStartMs) +
"ms");
}
+
+ if (mContactAffinity != NONE) MetricsLogger.count(mBaseContext, "note_with_people", 1);
}
@Override
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
new file mode 100644
index 0000000..67a2a54
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -0,0 +1,144 @@
+/**
+ * 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.server.notification;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.IConditionListener;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+public class ZenModeConditions implements ConditionProviders.Callback {
+ private static final String TAG = ZenModeHelper.TAG;
+ private static final boolean DEBUG = ZenModeHelper.DEBUG;
+
+ private final ZenModeHelper mHelper;
+ private final ConditionProviders mConditionProviders;
+ private final ArrayMap<Uri, ComponentName> mSubscriptions = new ArrayMap<>();
+
+ private CountdownConditionProvider mCountdown;
+ private ScheduleConditionProvider mSchedule;
+
+ public ZenModeConditions(ZenModeHelper helper, ConditionProviders conditionProviders) {
+ mHelper = helper;
+ mConditionProviders = conditionProviders;
+ if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.COUNTDOWN_PATH)) {
+ mCountdown = new CountdownConditionProvider();
+ mConditionProviders.addSystemProvider(mCountdown);
+ }
+ if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.SCHEDULE_PATH)) {
+ mSchedule = new ScheduleConditionProvider();
+ mConditionProviders.addSystemProvider(mSchedule);
+ }
+ mConditionProviders.setCallback(this);
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("mSubscriptions="); pw.println(mSubscriptions);
+ }
+
+ public void requestConditions(IConditionListener callback, int relevance) {
+ mConditionProviders.requestConditions(callback, relevance);
+ }
+
+ public void evaluateConfig(ZenModeConfig config) {
+ if (config == null) return;
+ if (config.manualRule != null && !config.manualRule.isTrueOrUnknown()) {
+ if (DEBUG) Log.d(TAG, "evaluateConfig: clearing manual rule");
+ config.manualRule = null;
+ }
+ final ArraySet<Uri> current = new ArraySet<>();
+ evaluateRule(config.manualRule, current);
+ for (ZenRule automaticRule : config.automaticRules.values()) {
+ evaluateRule(automaticRule, current);
+ }
+ final int N = mSubscriptions.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final Uri id = mSubscriptions.keyAt(i);
+ final ComponentName component = mSubscriptions.valueAt(i);
+ if (!current.contains(id)) {
+ mConditionProviders.unsubscribeIfNecessary(component, id);
+ mSubscriptions.removeAt(i);
+ }
+ }
+ }
+
+ private void evaluateRule(ZenRule rule, ArraySet<Uri> current) {
+ if (rule == null || rule.conditionId == null) return;
+ final Uri id = rule.conditionId;
+ for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) {
+ if (sp.isValidConditionid(id)) {
+ mConditionProviders.ensureRecordExists(sp.getComponent(), id, sp.asInterface());
+ rule.component = sp.getComponent();
+ }
+ }
+ current.add(id);
+ if (mConditionProviders.subscribeIfNecessary(rule.component, rule.conditionId)) {
+ mSubscriptions.put(rule.conditionId, rule.component);
+ }
+ }
+
+ @Override
+ public void onBootComplete() {
+ // noop
+ }
+
+ @Override
+ public void onUserSwitched() {
+ // noop
+ }
+
+ @Override
+ public void onConditionChanged(Uri id, Condition condition) {
+ if (DEBUG) Log.d(TAG, "onConditionChanged " + id + " " + condition);
+ ZenModeConfig config = mHelper.getConfig();
+ if (config == null) return;
+ config = config.copy();
+ boolean updated = updateCondition(id, condition, config.manualRule);
+ for (ZenRule automaticRule : config.automaticRules.values()) {
+ updated |= updateCondition(id, condition, automaticRule);
+ updated |= updateSnoozing(automaticRule);
+ }
+ if (updated) {
+ mHelper.setConfig(config, "conditionChanged");
+ }
+ }
+
+ private boolean updateSnoozing(ZenRule rule) {
+ if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) {
+ rule.snoozing = false;
+ if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean updateCondition(Uri id, Condition condition, ZenRule rule) {
+ if (id == null || rule == null || rule.conditionId == null) return false;
+ if (!rule.conditionId.equals(id)) return false;
+ if (Objects.equals(condition, rule.condition)) return false;
+ rule.condition = condition;
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
new file mode 100644
index 0000000..2aaeb9d
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -0,0 +1,279 @@
+/**
+ * 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.server.notification;
+
+import android.app.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
+import android.service.notification.ZenModeConfig;
+import android.telecom.TelecomManager;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.Objects;
+
+public class ZenModeFiltering {
+ private static final String TAG = ZenModeHelper.TAG;
+ private static final boolean DEBUG = ZenModeHelper.DEBUG;
+
+ static final RepeatCallers REPEAT_CALLERS = new RepeatCallers();
+
+ private final Context mContext;
+
+ private ComponentName mDefaultPhoneApp;
+
+ public ZenModeFiltering(Context context) {
+ mContext = context;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp);
+ pw.print(prefix); pw.print("RepeatCallers.mThresholdMinutes=");
+ pw.println(REPEAT_CALLERS.mThresholdMinutes);
+ synchronized (REPEAT_CALLERS) {
+ if (!REPEAT_CALLERS.mCalls.isEmpty()) {
+ pw.print(prefix); pw.println("RepeatCallers.mCalls=");
+ for (int i = 0; i < REPEAT_CALLERS.mCalls.size(); i++) {
+ pw.print(prefix); pw.print(" ");
+ pw.print(REPEAT_CALLERS.mCalls.keyAt(i));
+ pw.print(" at ");
+ pw.println(ts(REPEAT_CALLERS.mCalls.valueAt(i)));
+ }
+ }
+ }
+ }
+
+ private static String ts(long time) {
+ return new Date(time) + " (" + time + ")";
+ }
+
+ /**
+ * @param extras extras of the notification with EXTRA_PEOPLE populated
+ * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response
+ * @param timeoutAffinity affinity to return when the timeout specified via
+ * <code>contactsTimeoutMs</code> is hit
+ */
+ public static boolean matchesCallFilter(Context context, int zen, ZenModeConfig config,
+ UserHandle userHandle, Bundle extras, ValidateNotificationPeople validator,
+ int contactsTimeoutMs, float timeoutAffinity) {
+ if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
+ if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm
+ if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
+ if (config.allowRepeatCallers && REPEAT_CALLERS.isRepeat(context, extras)) return true;
+ if (!config.allowCalls) return false; // no other calls get through
+ if (validator != null) {
+ final float contactAffinity = validator.getContactAffinity(userHandle, extras,
+ contactsTimeoutMs, timeoutAffinity);
+ return audienceMatches(config, contactAffinity);
+ }
+ }
+ return true;
+ }
+
+ private static Bundle extras(NotificationRecord record) {
+ return record != null && record.sbn != null && record.sbn.getNotification() != null
+ ? record.sbn.getNotification().extras : null;
+ }
+
+ public boolean shouldIntercept(int zen, ZenModeConfig config, NotificationRecord record) {
+ if (isSystem(record)) {
+ return false;
+ }
+ switch (zen) {
+ case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ // #notevenalarms
+ ZenLog.traceIntercepted(record, "none");
+ return true;
+ case Global.ZEN_MODE_ALARMS:
+ if (isAlarm(record)) {
+ // Alarms only
+ return false;
+ }
+ ZenLog.traceIntercepted(record, "alarmsOnly");
+ return true;
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ if (isAlarm(record)) {
+ // Alarms are always priority
+ return false;
+ }
+ // allow user-prioritized packages through in priority mode
+ if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
+ ZenLog.traceNotIntercepted(record, "priorityApp");
+ return false;
+ }
+ if (isCall(record)) {
+ if (config.allowRepeatCallers
+ && REPEAT_CALLERS.isRepeat(mContext, extras(record))) {
+ ZenLog.traceNotIntercepted(record, "repeatCaller");
+ return false;
+ }
+ if (!config.allowCalls) {
+ ZenLog.traceIntercepted(record, "!allowCalls");
+ return true;
+ }
+ return shouldInterceptAudience(config, record);
+ }
+ if (isMessage(record)) {
+ if (!config.allowMessages) {
+ ZenLog.traceIntercepted(record, "!allowMessages");
+ return true;
+ }
+ return shouldInterceptAudience(config, record);
+ }
+ if (isEvent(record)) {
+ if (!config.allowEvents) {
+ ZenLog.traceIntercepted(record, "!allowEvents");
+ return true;
+ }
+ return false;
+ }
+ if (isReminder(record)) {
+ if (!config.allowReminders) {
+ ZenLog.traceIntercepted(record, "!allowReminders");
+ return true;
+ }
+ return false;
+ }
+ ZenLog.traceIntercepted(record, "!priority");
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static boolean shouldInterceptAudience(ZenModeConfig config,
+ NotificationRecord record) {
+ if (!audienceMatches(config, record.getContactAffinity())) {
+ ZenLog.traceIntercepted(record, "!audienceMatches");
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean isSystem(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_SYSTEM);
+ }
+
+ private static boolean isAlarm(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_ALARM)
+ || record.isAudioStream(AudioManager.STREAM_ALARM)
+ || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM);
+ }
+
+ private static boolean isEvent(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_EVENT);
+ }
+
+ private static boolean isReminder(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_REMINDER);
+ }
+
+ public boolean isCall(NotificationRecord record) {
+ return record != null && (isDefaultPhoneApp(record.sbn.getPackageName())
+ || record.isCategory(Notification.CATEGORY_CALL));
+ }
+
+ private boolean isDefaultPhoneApp(String pkg) {
+ if (mDefaultPhoneApp == null) {
+ final TelecomManager telecomm =
+ (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+ mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null;
+ if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp);
+ }
+ return pkg != null && mDefaultPhoneApp != null
+ && pkg.equals(mDefaultPhoneApp.getPackageName());
+ }
+
+ @SuppressWarnings("deprecation")
+ private boolean isDefaultMessagingApp(NotificationRecord record) {
+ final int userId = record.getUserId();
+ if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
+ final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(),
+ Secure.SMS_DEFAULT_APPLICATION, userId);
+ return Objects.equals(defaultApp, record.sbn.getPackageName());
+ }
+
+ private boolean isMessage(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
+ }
+
+ private static boolean audienceMatches(ZenModeConfig config, float contactAffinity) {
+ switch (config.allowFrom) {
+ case ZenModeConfig.SOURCE_ANYONE:
+ return true;
+ case ZenModeConfig.SOURCE_CONTACT:
+ return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
+ case ZenModeConfig.SOURCE_STAR:
+ return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
+ default:
+ Slog.w(TAG, "Encountered unknown source: " + config.allowFrom);
+ return true;
+ }
+ }
+
+ private static class RepeatCallers {
+ private final ArrayMap<String, Long> mCalls = new ArrayMap<>();
+ private int mThresholdMinutes;
+
+ private synchronized boolean isRepeat(Context context, Bundle extras) {
+ if (mThresholdMinutes <= 0) {
+ mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer
+ .config_zen_repeat_callers_threshold);
+ }
+ if (mThresholdMinutes <= 0 || extras == null) return false;
+ final String peopleString = peopleString(extras);
+ if (peopleString == null) return false;
+ final long now = System.currentTimeMillis();
+ final int N = mCalls.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final long time = mCalls.valueAt(i);
+ if (time > now || (now - time) > mThresholdMinutes * 1000 * 60) {
+ mCalls.removeAt(i);
+ }
+ }
+ final boolean isRepeat = mCalls.containsKey(peopleString);
+ mCalls.put(peopleString, now);
+ return isRepeat;
+ }
+
+ private static String peopleString(Bundle extras) {
+ final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
+ if (extraPeople == null || extraPeople.length == 0) return null;
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < extraPeople.length; i++) {
+ String extraPerson = extraPeople[i];
+ if (extraPerson == null) continue;
+ extraPerson = extraPerson.trim();
+ if (extraPerson.isEmpty()) continue;
+ if (sb.length() > 0) {
+ sb.append('|');
+ }
+ sb.append(extraPerson);
+ }
+ return sb.length() == 0 ? null : sb.toString();
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 1775df2..e5925fe 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -21,14 +21,12 @@ import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import android.app.AppOpsManager;
-import android.app.Notification;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
-import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.VolumePolicy;
@@ -39,12 +37,13 @@ import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
import android.provider.Settings.Global;
-import android.provider.Settings.Secure;
+import android.service.notification.IConditionListener;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
-import android.telecom.TelecomManager;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.util.ArraySet;
import android.util.Log;
-import android.util.Slog;
import com.android.internal.R;
import com.android.server.LocalServices;
@@ -58,14 +57,13 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Objects;
/**
* NotificationManagerService helper for functionality related to zen mode.
*/
-public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
- private static final String TAG = "ZenModeHelper";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+public class ZenModeHelper {
+ static final String TAG = "ZenModeHelper";
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final Context mContext;
private final H mHandler;
@@ -73,38 +71,46 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
private final AppOpsManager mAppOps;
private final ZenModeConfig mDefaultConfig;
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+ private final ZenModeFiltering mFiltering;
+ private final RingerModeDelegate mRingerModeDelegate = new RingerModeDelegate();
+ private final ZenModeConditions mConditions;
- private ComponentName mDefaultPhoneApp;
private int mZenMode;
private ZenModeConfig mConfig;
private AudioManagerInternal mAudioManager;
private int mPreviousRingerMode = -1;
private boolean mEffectsSuppressed;
- public ZenModeHelper(Context context, Looper looper) {
+ public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders) {
mContext = context;
mHandler = new H(looper);
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mDefaultConfig = readDefaultConfig(context.getResources());
+ appendDefaultScheduleRules(mDefaultConfig);
mConfig = mDefaultConfig;
mSettingsObserver = new SettingsObserver(mHandler);
mSettingsObserver.observe();
+ mFiltering = new ZenModeFiltering(mContext);
+ mConditions = new ZenModeConditions(this, conditionProviders);
}
- public static ZenModeConfig readDefaultConfig(Resources resources) {
- XmlResourceParser parser = null;
- try {
- parser = resources.getXml(R.xml.default_zen_mode_config);
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- final ZenModeConfig config = ZenModeConfig.readXml(parser);
- if (config != null) return config;
- }
- } catch (Exception e) {
- Slog.w(TAG, "Error reading default zen mode config from resource", e);
- } finally {
- IoUtils.closeQuietly(parser);
- }
- return new ZenModeConfig();
+ @Override
+ public String toString() {
+ return TAG;
+ }
+
+ public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
+ ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
+ return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConfig, userHandle, extras,
+ validator, contactsTimeoutMs, timeoutAffinity);
+ }
+
+ public boolean isCall(NotificationRecord record) {
+ return mFiltering.isCall(record);
+ }
+
+ public boolean shouldIntercept(NotificationRecord record) {
+ return mFiltering.shouldIntercept(mZenMode, mConfig, record);
}
public void addCallback(Callback callback) {
@@ -115,48 +121,32 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
mCallbacks.remove(callback);
}
+ public void initZenMode() {
+ if (DEBUG) Log.d(TAG, "initZenMode");
+ evaluateZenMode("init", true /*setRingerMode*/);
+ }
+
public void onSystemReady() {
+ if (DEBUG) Log.d(TAG, "onSystemReady");
mAudioManager = LocalServices.getService(AudioManagerInternal.class);
if (mAudioManager != null) {
- mAudioManager.setRingerModeDelegate(this);
+ mAudioManager.setRingerModeDelegate(mRingerModeDelegate);
}
}
- public int getZenModeListenerInterruptionFilter() {
- switch (mZenMode) {
- case Global.ZEN_MODE_OFF:
- return NotificationListenerService.INTERRUPTION_FILTER_ALL;
- case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
- case Global.ZEN_MODE_ALARMS:
- return NotificationListenerService.INTERRUPTION_FILTER_ALARMS;
- case Global.ZEN_MODE_NO_INTERRUPTIONS:
- return NotificationListenerService.INTERRUPTION_FILTER_NONE;
- default:
- return 0;
- }
+ public void requestZenModeConditions(IConditionListener callback, int relevance) {
+ mConditions.requestConditions(callback, relevance);
}
- private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter,
- int defValue) {
- switch (listenerInterruptionFilter) {
- case NotificationListenerService.INTERRUPTION_FILTER_ALL:
- return Global.ZEN_MODE_OFF;
- case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY:
- return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- case NotificationListenerService.INTERRUPTION_FILTER_ALARMS:
- return Global.ZEN_MODE_ALARMS;
- case NotificationListenerService.INTERRUPTION_FILTER_NONE:
- return Global.ZEN_MODE_NO_INTERRUPTIONS;
- default:
- return defValue;
- }
+ public int getZenModeListenerInterruptionFilter() {
+ return getZenModeListenerInterruptionFilter(mZenMode);
}
public void requestFromListener(ComponentName name, int interruptionFilter) {
final int newZen = zenModeFromListenerInterruptionFilter(interruptionFilter, -1);
if (newZen != -1) {
- setZenMode(newZen, "listener:" + (name != null ? name.flattenToShortString() : null));
+ setManualZenMode(newZen, null,
+ "listener:" + (name != null ? name.flattenToShortString() : null));
}
}
@@ -166,100 +156,144 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
applyRestrictions();
}
- public boolean shouldIntercept(NotificationRecord record) {
- if (isSystem(record)) {
- return false;
- }
- switch (mZenMode) {
- case Global.ZEN_MODE_NO_INTERRUPTIONS:
- // #notevenalarms
- ZenLog.traceIntercepted(record, "none");
- return true;
- case Global.ZEN_MODE_ALARMS:
- if (isAlarm(record)) {
- // Alarms only
- return false;
- }
- ZenLog.traceIntercepted(record, "alarmsOnly");
- return true;
- case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- if (isAlarm(record)) {
- // Alarms are always priority
- return false;
- }
- // allow user-prioritized packages through in priority mode
- if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
- ZenLog.traceNotIntercepted(record, "priorityApp");
- return false;
- }
- if (isCall(record)) {
- if (!mConfig.allowCalls) {
- ZenLog.traceIntercepted(record, "!allowCalls");
- return true;
- }
- return shouldInterceptAudience(record);
- }
- if (isMessage(record)) {
- if (!mConfig.allowMessages) {
- ZenLog.traceIntercepted(record, "!allowMessages");
- return true;
- }
- return shouldInterceptAudience(record);
- }
- if (isEvent(record)) {
- if (!mConfig.allowEvents) {
- ZenLog.traceIntercepted(record, "!allowEvents");
- return true;
- }
- return false;
- }
- if (isReminder(record)) {
- if (!mConfig.allowReminders) {
- ZenLog.traceIntercepted(record, "!allowReminders");
- return true;
- }
- return false;
+ public int getZenMode() {
+ return mZenMode;
+ }
+
+ public void setManualZenMode(int zenMode, Uri conditionId, String reason) {
+ setManualZenMode(zenMode, conditionId, reason, true /*setRingerMode*/);
+ }
+
+ private void setManualZenMode(int zenMode, Uri conditionId, String reason,
+ boolean setRingerMode) {
+ if (mConfig == null) return;
+ if (!Global.isValidZenMode(zenMode)) return;
+ if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
+ + " conditionId=" + conditionId + " reason=" + reason
+ + " setRingerMode=" + setRingerMode);
+ final ZenModeConfig newConfig = mConfig.copy();
+ if (zenMode == Global.ZEN_MODE_OFF) {
+ newConfig.manualRule = null;
+ for (ZenRule automaticRule : newConfig.automaticRules.values()) {
+ if (automaticRule.isTrueOrUnknown()) {
+ automaticRule.snoozing = true;
}
- ZenLog.traceIntercepted(record, "!priority");
- return true;
- default:
- return false;
+ }
+ } else {
+ final ZenRule newRule = new ZenRule();
+ newRule.enabled = true;
+ newRule.zenMode = zenMode;
+ newRule.conditionId = conditionId;
+ newConfig.manualRule = newRule;
}
+ setConfig(newConfig, reason, setRingerMode);
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("mZenMode=");
+ pw.println(Global.zenModeToString(mZenMode));
+ dump(pw, prefix, "mConfig", mConfig);
+ dump(pw, prefix, "mDefaultConfig", mDefaultConfig);
+ pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
+ pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
+ mFiltering.dump(pw, prefix);
+ mConditions.dump(pw, prefix);
}
- private boolean shouldInterceptAudience(NotificationRecord record) {
- if (!audienceMatches(record.getContactAffinity())) {
- ZenLog.traceIntercepted(record, "!audienceMatches");
- return true;
+ private static void dump(PrintWriter pw, String prefix, String var, ZenModeConfig config) {
+ pw.print(prefix); pw.print(var); pw.print('=');
+ if (config == null) {
+ pw.println(config);
+ return;
+ }
+ pw.printf("allow(calls=%s,repeatCallers=%s,events=%s,from=%s,messages=%s,reminders=%s)\n",
+ config.allowCalls, config.allowRepeatCallers, config.allowEvents, config.allowFrom,
+ config.allowMessages, config.allowReminders);
+ pw.print(prefix); pw.print(" manualRule="); pw.println(config.manualRule);
+ if (config.automaticRules.isEmpty()) return;
+ final int N = config.automaticRules.size();
+ for (int i = 0; i < N; i++) {
+ pw.print(prefix); pw.print(i == 0 ? " automaticRules=" : " ");
+ pw.println(config.automaticRules.valueAt(i));
}
- return false;
}
- public int getZenMode() {
- return mZenMode;
+ public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
+ final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration);
+ if (config != null) {
+ if (DEBUG) Log.d(TAG, "readXml");
+ setConfig(config, "readXml");
+ }
}
- public void setZenMode(int zenMode, String reason) {
- setZenMode(zenMode, reason, true);
+ public void writeXml(XmlSerializer out) throws IOException {
+ mConfig.writeXml(out);
+ }
+
+ public ZenModeConfig getConfig() {
+ return mConfig;
+ }
+
+ public boolean setConfig(ZenModeConfig config, String reason) {
+ return setConfig(config, reason, true /*setRingerMode*/);
+ }
+
+ private boolean setConfig(ZenModeConfig config, String reason, boolean setRingerMode) {
+ if (config == null || !config.isValid()) {
+ Log.w(TAG, "Invalid config in setConfig; " + config);
+ return false;
+ }
+ mConditions.evaluateConfig(config); // may modify config
+ if (config.equals(mConfig)) return true;
+ if (DEBUG) Log.d(TAG, "setConfig reason=" + reason);
+ ZenLog.traceConfig(mConfig, config);
+ mConfig = config;
+ dispatchOnConfigChanged();
+ final String val = Integer.toString(mConfig.hashCode());
+ Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
+ if (!evaluateZenMode(reason, setRingerMode)) {
+ applyRestrictions(); // evaluateZenMode will also apply restrictions if changed
+ }
+ return true;
+ }
+
+ private int getZenModeSetting() {
+ return Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, Global.ZEN_MODE_OFF);
+ }
+
+ private void setZenModeSetting(int zen) {
+ Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen);
}
- private void setZenMode(int zenMode, String reason, boolean setRingerMode) {
- ZenLog.traceSetZenMode(zenMode, reason);
- if (mZenMode == zenMode) return;
- ZenLog.traceUpdateZenMode(mZenMode, zenMode);
- mZenMode = zenMode;
- Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, mZenMode);
+ private boolean evaluateZenMode(String reason, boolean setRingerMode) {
+ if (DEBUG) Log.d(TAG, "evaluateZenMode");
+ final ArraySet<ZenRule> automaticRules = new ArraySet<ZenRule>();
+ final int zen = computeZenMode(automaticRules);
+ if (zen == mZenMode) return false;
+ ZenLog.traceSetZenMode(zen, reason);
+ mZenMode = zen;
+ setZenModeSetting(mZenMode);
if (setRingerMode) {
applyZenToRingerMode();
}
applyRestrictions();
mHandler.postDispatchOnZenModeChanged();
+ return true;
}
- public void readZenModeFromSetting() {
- final int newMode = Global.getInt(mContext.getContentResolver(),
- Global.ZEN_MODE, Global.ZEN_MODE_OFF);
- setZenMode(newMode, "setting");
+ private int computeZenMode(ArraySet<ZenRule> automaticRulesOut) {
+ if (mConfig == null) return Global.ZEN_MODE_OFF;
+ if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
+ int zen = Global.ZEN_MODE_OFF;
+ for (ZenRule automaticRule : mConfig.automaticRules.values()) {
+ if (automaticRule.enabled && !automaticRule.snoozing
+ && automaticRule.isTrueOrUnknown()) {
+ if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
+ zen = automaticRule.zenMode;
+ }
+ }
+ }
+ return zen;
}
private void applyRestrictions() {
@@ -270,7 +304,8 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
applyRestrictions(muteNotifications, USAGE_NOTIFICATION);
// call restrictions
- final boolean muteCalls = zen && !mConfig.allowCalls || mEffectsSuppressed;
+ final boolean muteCalls = zen && !mConfig.allowCalls && !mConfig.allowRepeatCallers
+ || mEffectsSuppressed;
applyRestrictions(muteCalls, USAGE_NOTIFICATION_RINGTONE);
// alarm restrictions
@@ -288,43 +323,6 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
exceptionPackages);
}
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("mZenMode=");
- pw.println(Global.zenModeToString(mZenMode));
- pw.print(prefix); pw.print("mConfig="); pw.println(mConfig);
- pw.print(prefix); pw.print("mDefaultConfig="); pw.println(mDefaultConfig);
- pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
- pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp);
- pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
- }
-
- public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
- final ZenModeConfig config = ZenModeConfig.readXml(parser);
- if (config != null) {
- setConfig(config);
- }
- }
-
- public void writeXml(XmlSerializer out) throws IOException {
- mConfig.writeXml(out);
- }
-
- public ZenModeConfig getConfig() {
- return mConfig;
- }
-
- public boolean setConfig(ZenModeConfig config) {
- if (config == null || !config.isValid()) return false;
- if (config.equals(mConfig)) return true;
- ZenLog.traceConfig(mConfig, config);
- mConfig = config;
- dispatchOnConfigChanged();
- final String val = Integer.toString(mConfig.hashCode());
- Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
- applyRestrictions();
- return true;
- }
-
private void applyZenToRingerMode() {
if (mAudioManager == null) return;
// force the ringer mode into compliance
@@ -352,81 +350,6 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
}
}
- @Override // RingerModeDelegate
- public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
- int ringerModeExternal, VolumePolicy policy) {
- final boolean isChange = ringerModeOld != ringerModeNew;
-
- int ringerModeExternalOut = ringerModeNew;
-
- int newZen = -1;
- switch (ringerModeNew) {
- case AudioManager.RINGER_MODE_SILENT:
- if (isChange && policy.doNotDisturbWhenSilent) {
- if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS
- && mZenMode != Global.ZEN_MODE_ALARMS) {
- newZen = Global.ZEN_MODE_NO_INTERRUPTIONS;
- }
- }
- break;
- case AudioManager.RINGER_MODE_VIBRATE:
- case AudioManager.RINGER_MODE_NORMAL:
- if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT
- && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
- || mZenMode == Global.ZEN_MODE_ALARMS)) {
- newZen = Global.ZEN_MODE_OFF;
- } else if (mZenMode != Global.ZEN_MODE_OFF) {
- ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
- }
- break;
- }
- if (newZen != -1) {
- setZenMode(newZen, "ringerModeInternal", false /*setRingerMode*/);
- }
-
- if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
- ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
- ringerModeExternal, ringerModeExternalOut);
- }
- return ringerModeExternalOut;
- }
-
- @Override // RingerModeDelegate
- public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
- int ringerModeInternal, VolumePolicy policy) {
- int ringerModeInternalOut = ringerModeNew;
- final boolean isChange = ringerModeOld != ringerModeNew;
- final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
-
- int newZen = -1;
- switch (ringerModeNew) {
- case AudioManager.RINGER_MODE_SILENT:
- if (isChange) {
- if (mZenMode == Global.ZEN_MODE_OFF) {
- newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- }
- ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE
- : AudioManager.RINGER_MODE_NORMAL;
- } else {
- ringerModeInternalOut = ringerModeInternal;
- }
- break;
- case AudioManager.RINGER_MODE_VIBRATE:
- case AudioManager.RINGER_MODE_NORMAL:
- if (mZenMode != Global.ZEN_MODE_OFF) {
- newZen = Global.ZEN_MODE_OFF;
- }
- break;
- }
- if (newZen != -1) {
- setZenMode(newZen, "ringerModeExternal", false /*setRingerMode*/);
- }
-
- ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller, ringerModeInternal,
- ringerModeInternalOut);
- return ringerModeInternalOut;
- }
-
private void dispatchOnConfigChanged() {
for (Callback callback : mCallbacks) {
callback.onConfigChanged();
@@ -439,94 +362,210 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
}
}
- private static boolean isSystem(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_SYSTEM);
- }
-
- private static boolean isAlarm(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_ALARM)
- || record.isAudioStream(AudioManager.STREAM_ALARM)
- || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM);
+ private static int getZenModeListenerInterruptionFilter(int zen) {
+ switch (zen) {
+ case Global.ZEN_MODE_OFF:
+ return NotificationListenerService.INTERRUPTION_FILTER_ALL;
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
+ case Global.ZEN_MODE_ALARMS:
+ return NotificationListenerService.INTERRUPTION_FILTER_ALARMS;
+ case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ return NotificationListenerService.INTERRUPTION_FILTER_NONE;
+ default:
+ return 0;
+ }
}
- private static boolean isEvent(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_EVENT);
+ private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter,
+ int defValue) {
+ switch (listenerInterruptionFilter) {
+ case NotificationListenerService.INTERRUPTION_FILTER_ALL:
+ return Global.ZEN_MODE_OFF;
+ case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY:
+ return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ case NotificationListenerService.INTERRUPTION_FILTER_ALARMS:
+ return Global.ZEN_MODE_ALARMS;
+ case NotificationListenerService.INTERRUPTION_FILTER_NONE:
+ return Global.ZEN_MODE_NO_INTERRUPTIONS;
+ default:
+ return defValue;
+ }
}
- private static boolean isReminder(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_REMINDER);
+ private ZenModeConfig readDefaultConfig(Resources resources) {
+ XmlResourceParser parser = null;
+ try {
+ parser = resources.getXml(R.xml.default_zen_mode_config);
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration);
+ if (config != null) return config;
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Error reading default zen mode config from resource", e);
+ } finally {
+ IoUtils.closeQuietly(parser);
+ }
+ return new ZenModeConfig();
}
- public boolean isCall(NotificationRecord record) {
- return record != null && (isDefaultPhoneApp(record.sbn.getPackageName())
- || record.isCategory(Notification.CATEGORY_CALL));
+ private void appendDefaultScheduleRules(ZenModeConfig config) {
+ if (config == null) return;
+
+ final ScheduleInfo weeknights = new ScheduleInfo();
+ weeknights.days = ZenModeConfig.WEEKNIGHT_DAYS;
+ weeknights.startHour = 22;
+ weeknights.endHour = 7;
+ final ZenRule rule1 = new ZenRule();
+ rule1.enabled = false;
+ rule1.name = mContext.getResources()
+ .getString(R.string.zen_mode_default_weeknights_name);
+ rule1.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
+ rule1.zenMode = Global.ZEN_MODE_ALARMS;
+ config.automaticRules.put(config.newRuleId(), rule1);
+
+ final ScheduleInfo weekends = new ScheduleInfo();
+ weekends.days = ZenModeConfig.WEEKEND_DAYS;
+ weekends.startHour = 23;
+ weekends.startMinute = 30;
+ weekends.endHour = 10;
+ final ZenRule rule2 = new ZenRule();
+ rule2.enabled = false;
+ rule2.name = mContext.getResources()
+ .getString(R.string.zen_mode_default_weekends_name);
+ rule2.conditionId = ZenModeConfig.toScheduleConditionId(weekends);
+ rule2.zenMode = Global.ZEN_MODE_ALARMS;
+ config.automaticRules.put(config.newRuleId(), rule2);
+ }
+
+ private static int zenSeverity(int zen) {
+ switch (zen) {
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return 1;
+ case Global.ZEN_MODE_ALARMS: return 2;
+ case Global.ZEN_MODE_NO_INTERRUPTIONS: return 3;
+ default: return 0;
+ }
}
- private boolean isDefaultPhoneApp(String pkg) {
- if (mDefaultPhoneApp == null) {
- final TelecomManager telecomm =
- (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
- mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null;
- if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp);
+ private final ZenModeConfig.Migration mConfigMigration = new ZenModeConfig.Migration() {
+ @Override
+ public ZenModeConfig migrate(ZenModeConfig.XmlV1 v1) {
+ if (v1 == null) return null;
+ final ZenModeConfig rt = new ZenModeConfig();
+ rt.allowCalls = v1.allowCalls;
+ rt.allowEvents = v1.allowEvents;
+ rt.allowFrom = v1.allowFrom;
+ rt.allowMessages = v1.allowMessages;
+ rt.allowReminders = v1.allowReminders;
+ // don't migrate current exit condition
+ final int[] days = ZenModeConfig.XmlV1.tryParseDays(v1.sleepMode);
+ if (days != null && days.length > 0) {
+ Log.i(TAG, "Migrating existing V1 downtime to single schedule");
+ final ScheduleInfo schedule = new ScheduleInfo();
+ schedule.days = days;
+ schedule.startHour = v1.sleepStartHour;
+ schedule.startMinute = v1.sleepStartMinute;
+ schedule.endHour = v1.sleepEndHour;
+ schedule.endMinute = v1.sleepEndMinute;
+ final ZenRule rule = new ZenRule();
+ rule.enabled = true;
+ rule.name = mContext.getResources()
+ .getString(R.string.zen_mode_downtime_feature_name);
+ rule.conditionId = ZenModeConfig.toScheduleConditionId(schedule);
+ rule.zenMode = v1.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS
+ : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ rt.automaticRules.put(rt.newRuleId(), rule);
+ } else {
+ Log.i(TAG, "No existing V1 downtime found, generating default schedules");
+ appendDefaultScheduleRules(rt);
+ }
+ return rt;
}
- return pkg != null && mDefaultPhoneApp != null
- && pkg.equals(mDefaultPhoneApp.getPackageName());
- }
+ };
- private boolean isDefaultMessagingApp(NotificationRecord record) {
- final int userId = record.getUserId();
- if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
- final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(),
- Secure.SMS_DEFAULT_APPLICATION, userId);
- return Objects.equals(defaultApp, record.sbn.getPackageName());
- }
+ private final class RingerModeDelegate implements AudioManagerInternal.RingerModeDelegate {
+ @Override
+ public String toString() {
+ return TAG;
+ }
- private boolean isMessage(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
- }
+ @Override
+ public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
+ int ringerModeExternal, VolumePolicy policy) {
+ final boolean isChange = ringerModeOld != ringerModeNew;
+
+ int ringerModeExternalOut = ringerModeNew;
+
+ int newZen = -1;
+ switch (ringerModeNew) {
+ case AudioManager.RINGER_MODE_SILENT:
+ if (isChange && policy.doNotDisturbWhenSilent) {
+ if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS
+ && mZenMode != Global.ZEN_MODE_ALARMS) {
+ newZen = Global.ZEN_MODE_NO_INTERRUPTIONS;
+ }
+ }
+ break;
+ case AudioManager.RINGER_MODE_VIBRATE:
+ case AudioManager.RINGER_MODE_NORMAL:
+ if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT
+ && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
+ || mZenMode == Global.ZEN_MODE_ALARMS)) {
+ newZen = Global.ZEN_MODE_OFF;
+ } else if (mZenMode != Global.ZEN_MODE_OFF) {
+ ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
+ }
+ break;
+ }
+ if (newZen != -1) {
+ setManualZenMode(newZen, null, "ringerModeInternal", false /*setRingerMode*/);
+ }
- /**
- * @param extras extras of the notification with EXTRA_PEOPLE populated
- * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response
- * @param timeoutAffinity affinity to return when the timeout specified via
- * <code>contactsTimeoutMs</code> is hit
- */
- public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
- ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
- final int zen = mZenMode;
- if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
- if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm
- if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
- if (!mConfig.allowCalls) return false; // no calls get through
- if (validator != null) {
- final float contactAffinity = validator.getContactAffinity(userHandle, extras,
- contactsTimeoutMs, timeoutAffinity);
- return audienceMatches(contactAffinity);
+ if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
+ ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
+ ringerModeExternal, ringerModeExternalOut);
}
+ return ringerModeExternalOut;
}
- return true;
- }
- @Override
- public String toString() {
- return TAG;
- }
+ @Override
+ public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
+ int ringerModeInternal, VolumePolicy policy) {
+ int ringerModeInternalOut = ringerModeNew;
+ final boolean isChange = ringerModeOld != ringerModeNew;
+ final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
+
+ int newZen = -1;
+ switch (ringerModeNew) {
+ case AudioManager.RINGER_MODE_SILENT:
+ if (isChange) {
+ if (mZenMode == Global.ZEN_MODE_OFF) {
+ newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ }
+ ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE
+ : AudioManager.RINGER_MODE_NORMAL;
+ } else {
+ ringerModeInternalOut = ringerModeInternal;
+ }
+ break;
+ case AudioManager.RINGER_MODE_VIBRATE:
+ case AudioManager.RINGER_MODE_NORMAL:
+ if (mZenMode != Global.ZEN_MODE_OFF) {
+ newZen = Global.ZEN_MODE_OFF;
+ }
+ break;
+ }
+ if (newZen != -1) {
+ setManualZenMode(newZen, null, "ringerModeExternal", false /*setRingerMode*/);
+ }
- private boolean audienceMatches(float contactAffinity) {
- switch (mConfig.allowFrom) {
- case ZenModeConfig.SOURCE_ANYONE:
- return true;
- case ZenModeConfig.SOURCE_CONTACT:
- return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
- case ZenModeConfig.SOURCE_STAR:
- return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
- default:
- Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom);
- return true;
+ ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller,
+ ringerModeInternal, ringerModeInternalOut);
+ return ringerModeInternalOut;
}
}
- private class SettingsObserver extends ContentObserver {
+ private final class SettingsObserver extends ContentObserver {
private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE);
public SettingsObserver(Handler handler) {
@@ -546,12 +585,15 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
public void update(Uri uri) {
if (ZEN_MODE.equals(uri)) {
- readZenModeFromSetting();
+ if (mZenMode != getZenModeSetting()) {
+ if (DEBUG) Log.d(TAG, "Fixing zen mode setting");
+ setZenModeSetting(mZenMode);
+ }
}
}
}
- private class H extends Handler {
+ private final class H extends Handler {
private static final int MSG_DISPATCH = 1;
private H(Looper looper) {
@@ -577,4 +619,5 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
void onConfigChanged() {}
void onZenModeChanged() {}
}
+
}
diff --git a/services/core/java/com/android/server/pm/BasePermission.java b/services/core/java/com/android/server/pm/BasePermission.java
index ec290ef..30f8b37 100644
--- a/services/core/java/com/android/server/pm/BasePermission.java
+++ b/services/core/java/com/android/server/pm/BasePermission.java
@@ -74,10 +74,6 @@ final class BasePermission {
this.perUser = perUser;
}
- public boolean hasGids() {
- return ArrayUtils.isEmpty(gids);
- }
-
public int[] computeGids(int userId) {
if (perUser) {
final int[] userGids = new int[gids.length];
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index b4a44a6..ce31f98 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -20,7 +20,9 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageStats;
import android.os.Build;
+import android.text.TextUtils;
import android.util.Slog;
+
import dalvik.system.VMRuntime;
import com.android.internal.os.InstallerConnection;
@@ -42,9 +44,24 @@ public final class Installer extends SystemService {
ping();
}
+ private static String escapeNull(String arg) {
+ if (TextUtils.isEmpty(arg)) {
+ return "!";
+ } else {
+ return arg;
+ }
+ }
+
+ @Deprecated
public int install(String name, int uid, int gid, String seinfo) {
+ return install(null, name, uid, gid, seinfo);
+ }
+
+ public int install(String uuid, String name, int uid, int gid, String seinfo) {
StringBuilder builder = new StringBuilder("install");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
@@ -55,43 +72,25 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
- public int patchoat(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet) {
- if (!isValidInstructionSet(instructionSet)) {
- Slog.e(TAG, "Invalid instruction set: " + instructionSet);
- return -1;
- }
-
- return mInstaller.patchoat(apkPath, uid, isPublic, pkgName, instructionSet);
- }
-
- public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) {
+ public int dexopt(String apkPath, int uid, boolean isPublic,
+ String instructionSet, int dexoptNeeded) {
if (!isValidInstructionSet(instructionSet)) {
Slog.e(TAG, "Invalid instruction set: " + instructionSet);
return -1;
}
- return mInstaller.patchoat(apkPath, uid, isPublic, instructionSet);
- }
-
- public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) {
- if (!isValidInstructionSet(instructionSet)) {
- Slog.e(TAG, "Invalid instruction set: " + instructionSet);
- return -1;
- }
-
- return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet);
+ return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet, dexoptNeeded);
}
public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet, boolean vmSafeMode, boolean debuggable,
- @Nullable String outputPath) {
+ String instructionSet, int dexoptNeeded, boolean vmSafeMode,
+ boolean debuggable, @Nullable String outputPath) {
if (!isValidInstructionSet(instructionSet)) {
Slog.e(TAG, "Invalid instruction set: " + instructionSet);
return -1;
}
-
- return mInstaller.dexopt(apkPath, uid, isPublic, pkgName, instructionSet, vmSafeMode,
+ return mInstaller.dexopt(apkPath, uid, isPublic, pkgName,
+ instructionSet, dexoptNeeded, vmSafeMode,
debuggable, outputPath);
}
@@ -146,9 +145,16 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int remove(String name, int userId) {
+ return remove(null, name, userId);
+ }
+
+ public int remove(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("remove");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
@@ -164,9 +170,16 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int fixUid(String name, int uid, int gid) {
+ return fixUid(null, name, uid, gid);
+ }
+
+ public int fixUid(String uuid, String name, int uid, int gid) {
StringBuilder builder = new StringBuilder("fixuid");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
@@ -175,27 +188,48 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int deleteCacheFiles(String name, int userId) {
+ return deleteCacheFiles(null, name, userId);
+ }
+
+ public int deleteCacheFiles(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("rmcache");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int deleteCodeCacheFiles(String name, int userId) {
+ return deleteCodeCacheFiles(null, name, userId);
+ }
+
+ public int deleteCodeCacheFiles(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("rmcodecache");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int createUserData(String name, int uid, int userId, String seinfo) {
+ return createUserData(null, name, uid, userId, seinfo);
+ }
+
+ public int createUserData(String uuid, String name, int uid, int userId, String seinfo) {
StringBuilder builder = new StringBuilder("mkuserdata");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
@@ -213,16 +247,30 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int removeUserDataDirs(int userId) {
+ return removeUserDataDirs(null, userId);
+ }
+
+ public int removeUserDataDirs(String uuid, int userId) {
StringBuilder builder = new StringBuilder("rmuser");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(userId);
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int clearUserData(String name, int userId) {
+ return clearUserData(null, name, userId);
+ }
+
+ public int clearUserData(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("rmuserdata");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
@@ -249,15 +297,30 @@ public final class Installer extends SystemService {
}
}
+ @Deprecated
public int freeCache(long freeStorageSize) {
+ return freeCache(null, freeStorageSize);
+ }
+
+ public int freeCache(String uuid, long freeStorageSize) {
StringBuilder builder = new StringBuilder("freecache");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(String.valueOf(freeStorageSize));
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int getSizeInfo(String pkgName, int persona, String apkPath, String libDirPath,
String fwdLockApkPath, String asecPath, String[] instructionSets, PackageStats pStats) {
+ return getSizeInfo(null, pkgName, persona, apkPath, libDirPath, fwdLockApkPath, asecPath,
+ instructionSets, pStats);
+ }
+
+ public int getSizeInfo(String uuid, String pkgName, int persona, String apkPath,
+ String libDirPath, String fwdLockApkPath, String asecPath, String[] instructionSets,
+ PackageStats pStats) {
for (String instructionSet : instructionSets) {
if (!isValidInstructionSet(instructionSet)) {
Slog.e(TAG, "Invalid instruction set: " + instructionSet);
@@ -267,6 +330,8 @@ public final class Installer extends SystemService {
StringBuilder builder = new StringBuilder("getsize");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(pkgName);
builder.append(' ');
builder.append(persona);
@@ -306,6 +371,11 @@ public final class Installer extends SystemService {
return mInstaller.execute("movefiles");
}
+ @Deprecated
+ public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath32, int userId) {
+ return linkNativeLibraryDirectory(null, dataPath, nativeLibPath32, userId);
+ }
+
/**
* Links the 32 bit native library directory in an application's data directory to the
* real location for backward compatibility. Note that no such symlink is created for
@@ -313,7 +383,8 @@ public final class Installer extends SystemService {
*
* @return -1 on error
*/
- public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath32, int userId) {
+ public int linkNativeLibraryDirectory(String uuid, String dataPath, String nativeLibPath32,
+ int userId) {
if (dataPath == null) {
Slog.e(TAG, "linkNativeLibraryDirectory dataPath is null");
return -1;
@@ -322,7 +393,10 @@ public final class Installer extends SystemService {
return -1;
}
- StringBuilder builder = new StringBuilder("linklib ");
+ StringBuilder builder = new StringBuilder("linklib");
+ builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(dataPath);
builder.append(' ');
builder.append(nativeLibPath32);
@@ -332,9 +406,16 @@ public final class Installer extends SystemService {
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public boolean restoreconData(String pkgName, String seinfo, int uid) {
+ return restoreconData(null, pkgName, seinfo, uid);
+ }
+
+ public boolean restoreconData(String uuid, String pkgName, String seinfo, int uid) {
StringBuilder builder = new StringBuilder("restorecondata");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(pkgName);
builder.append(' ');
builder.append(seinfo != null ? seinfo : "!");
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 680ec4b..4c36fa6 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -113,64 +113,48 @@ final class PackageDexOptimizer {
for (String path : paths) {
try {
- // This will return DEXOPT_NEEDED if we either cannot find any odex file for this
- // package or the one we find does not match the image checksum (i.e. it was
- // compiled against an old image). It will return PATCHOAT_NEEDED if we can find a
- // odex file and it matches the checksum of the image but not its base address,
- // meaning we need to move it.
- final byte isDexOptNeeded = DexFile.isDexOptNeededInternal(path,
- pkg.packageName, dexCodeInstructionSet, defer);
- if (forceDex || (!defer && isDexOptNeeded == DexFile.DEXOPT_NEEDED)) {
- File oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
- Log.i(TAG, "Running dexopt on: " + path + " pkg="
- + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
- + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
- + " oatDir = " + oatDir);
- final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+ final int dexoptNeeded;
+ if (forceDex) {
+ dexoptNeeded = DexFile.DEX2OAT_NEEDED;
+ } else {
+ dexoptNeeded = DexFile.getDexOptNeeded(path,
+ pkg.packageName, dexCodeInstructionSet, defer);
+ }
- if (oatDir != null) {
- int ret = mPackageManagerService.mInstaller.dexopt(
- path, sharedGid, !pkg.isForwardLocked(), pkg.packageName,
- dexCodeInstructionSet, vmSafeMode, debuggable,
- oatDir.getAbsolutePath());
- if (ret < 0) {
- return DEX_OPT_FAILED;
- }
+ if (!forceDex && defer && dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ // We're deciding to defer a needed dexopt. Don't bother dexopting for other
+ // paths and instruction sets. We'll deal with them all together when we process
+ // our list of deferred dexopts.
+ addPackageForDeferredDexopt(pkg);
+ return DEX_OPT_DEFERRED;
+ }
+
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ final String dexoptType;
+ String oatDir = null;
+ if (dexoptNeeded == DexFile.DEX2OAT_NEEDED) {
+ dexoptType = "dex2oat";
+ oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
+ } else if (dexoptNeeded == DexFile.PATCHOAT_NEEDED) {
+ dexoptType = "patchoat";
+ } else if (dexoptNeeded == DexFile.SELF_PATCHOAT_NEEDED) {
+ dexoptType = "self patchoat";
} else {
- final int ret = mPackageManagerService.mInstaller
- .dexopt(path, sharedGid,
- !pkg.isForwardLocked(), pkg.packageName,
- dexCodeInstructionSet,
- vmSafeMode, debuggable, null);
- if (ret < 0) {
- return DEX_OPT_FAILED;
- }
+ throw new IllegalStateException("Invalid dexopt needed: " + dexoptNeeded);
}
-
- performedDexOpt = true;
- } else if (!defer && isDexOptNeeded == DexFile.PATCHOAT_NEEDED) {
- Log.i(TAG, "Running patchoat on: " + pkg.applicationInfo.packageName);
+ Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
+ + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
+ + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
+ + " oatDir = " + oatDir);
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
- final int ret = mPackageManagerService.mInstaller.patchoat(path, sharedGid,
- !pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet);
-
+ final int ret = mPackageManagerService.mInstaller.dexopt(path, sharedGid,
+ !pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet,
+ dexoptNeeded, vmSafeMode, debuggable, oatDir);
if (ret < 0) {
- // Don't bother running patchoat again if we failed, it will probably
- // just result in an error again. Also, don't bother dexopting for other
- // paths & ISAs.
return DEX_OPT_FAILED;
}
-
performedDexOpt = true;
}
-
- // We're deciding to defer a needed dexopt. Don't bother dexopting for other
- // paths and instruction sets. We'll deal with them all together when we process
- // our list of deferred dexopts.
- if (defer && isDexOptNeeded != DexFile.UP_TO_DATE) {
- addPackageForDeferredDexopt(pkg);
- return DEX_OPT_DEFERRED;
- }
} catch (FileNotFoundException e) {
Slog.w(TAG, "Apk not found for dexopt: " + path);
return DEX_OPT_FAILED;
@@ -187,7 +171,7 @@ final class PackageDexOptimizer {
}
// At this point we haven't failed dexopt and we haven't deferred dexopt. We must
- // either have either succeeded dexopt, or have had isDexOptNeededInternal tell us
+ // either have either succeeded dexopt, or have had getDexOptNeeded tell us
// it isn't required. We therefore mark that this package doesn't need dexopt unless
// it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
// it.
@@ -209,10 +193,11 @@ final class PackageDexOptimizer {
* <li>Package location is not a directory, i.e. monolithic install.</li>
* </ul>
*
- * @return oat directory or null, if oat directory cannot be created.
+ * @return Absolute path to the oat directory or null, if oat directory
+ * cannot be created.
*/
@Nullable
- private File createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet)
+ private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet)
throws IOException {
if (pkg.isSystemApp() && !pkg.isUpdatedSystemApp()) {
return null;
@@ -222,7 +207,7 @@ final class PackageDexOptimizer {
File oatDir = getOatDir(codePath);
mPackageManagerService.mInstaller.createOatDir(oatDir.getAbsolutePath(),
dexInstructionSet);
- return oatDir;
+ return oatDir.getAbsolutePath();
}
return null;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 591dbee..89fa320 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -30,6 +30,7 @@ import static com.android.internal.util.XmlUtils.writeUriAttribute;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -528,6 +529,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
}
+ // Only system components can circumvent runtime permissions when installing.
+ if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
+ && mContext.checkCallingOrSelfPermission(Manifest.permission
+ .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
+ throw new SecurityException("You need the "
+ + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
+ + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
+ }
+
// Defensively resize giant app icons
if (params.appIcon != null) {
final ActivityManager am = (ActivityManager) mContext.getSystemService(
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 83c30aa..a0313e7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1816,18 +1816,10 @@ public class PackageManagerService extends IPackageManager.Stub {
}
try {
- byte dexoptRequired = DexFile.isDexOptNeededInternal(lib, null,
- dexCodeInstructionSet,
- false);
- if (dexoptRequired != DexFile.UP_TO_DATE) {
+ int dexoptNeeded = DexFile.getDexOptNeeded(lib, null, dexCodeInstructionSet, false);
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
alreadyDexOpted.add(lib);
-
- // The list of "shared libraries" we have at this point is
- if (dexoptRequired == DexFile.DEXOPT_NEEDED) {
- mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
- } else {
- mInstaller.patchoat(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
- }
+ mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Library not found: " + lib);
@@ -1873,13 +1865,9 @@ public class PackageManagerService extends IPackageManager.Stub {
continue;
}
try {
- byte dexoptRequired = DexFile.isDexOptNeededInternal(path, null,
- dexCodeInstructionSet,
- false);
- if (dexoptRequired == DexFile.DEXOPT_NEEDED) {
- mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
- } else if (dexoptRequired == DexFile.PATCHOAT_NEEDED) {
- mInstaller.patchoat(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
+ int dexoptNeeded = DexFile.getDexOptNeeded(path, null, dexCodeInstructionSet, false);
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Jar not found: " + path);
@@ -8593,6 +8581,15 @@ public class PackageManagerService extends IPackageManager.Stub {
user = new UserHandle(userId);
}
+ // Only system components can circumvent runtime permissions when installing.
+ if ((installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
+ && mContext.checkCallingOrSelfPermission(Manifest.permission
+ .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
+ throw new SecurityException("You need the "
+ + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
+ + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
+ }
+
verificationParams.setInstallerUid(callingUid);
final File originFile = new File(originPath);
@@ -8750,7 +8747,6 @@ public class PackageManagerService extends IPackageManager.Stub {
long callingId = Binder.clearCallingIdentity();
try {
boolean sendAdded = false;
- Bundle extras = new Bundle(1);
// writer
synchronized (mPackages) {
diff --git a/services/core/java/com/android/server/pm/PermissionsState.java b/services/core/java/com/android/server/pm/PermissionsState.java
index 705abf8..fbb5090 100644
--- a/services/core/java/com/android/server/pm/PermissionsState.java
+++ b/services/core/java/com/android/server/pm/PermissionsState.java
@@ -221,13 +221,11 @@ public final class PermissionsState {
int result = PERMISSION_OPERATION_SUCCESS;
PermissionData permissionData = mPermissions.get(permission.name);
- if (permissionData.hasGids()) {
- for (int userId : permissionData.getUserIds()) {
- if (revokePermission(permission, userId)
- == PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
- result = PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
- break;
- }
+ for (int userId : permissionData.getUserIds()) {
+ if (revokePermission(permission, userId)
+ == PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
+ result = PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
+ break;
}
}
@@ -380,7 +378,7 @@ public final class PermissionsState {
return PERMISSION_OPERATION_FAILURE;
}
- final boolean hasGids = permission.hasGids();
+ final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
if (mPermissions == null) {
@@ -412,7 +410,7 @@ public final class PermissionsState {
return PERMISSION_OPERATION_FAILURE;
}
- final boolean hasGids = permission.hasGids();
+ final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
PermissionData permissionData = mPermissions.get(permission.name);
@@ -472,10 +470,6 @@ public final class PermissionsState {
}
}
- public boolean hasGids() {
- return mPerm.hasGids();
- }
-
public int[] computeGids(int userId) {
return mPerm.computeGids(userId);
}
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 1ff6fb8..c75a1d3 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -34,6 +34,7 @@ import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -52,14 +53,15 @@ import org.xmlpull.v1.XmlPullParserException;
*/
public final class SELinuxMMAC {
- private static final String TAG = "SELinuxMMAC";
+ static final String TAG = "SELinuxMMAC";
private static final boolean DEBUG_POLICY = false;
private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false;
+ private static final boolean DEBUG_POLICY_ORDER = DEBUG_POLICY || false;
// All policy stanzas read from mac_permissions.xml. This is also the lock
// to synchronize access during policy load and access attempts.
- private static final List<Policy> sPolicies = new ArrayList<Policy>();
+ private static List<Policy> sPolicies = new ArrayList<>();
// Data policy override version file.
private static final String DATA_VERSION_FILE =
@@ -112,17 +114,9 @@ public final class SELinuxMMAC {
* were loaded successfully; no partial loading is possible.
*/
public static boolean readInstallPolicy() {
- // Temp structure to hold the rules while we parse the xml file. We add
- // all the rules once we know there's no problems.
+ // Temp structure to hold the rules while we parse the xml file
List<Policy> policies = new ArrayList<>();
- // A separate structure to hold the default stanza. We need to add this to
- // the end of the policies list structure.
- Policy defaultPolicy = null;
-
- // Track sets of known policy certs so we can enforce rules across stanzas.
- Set<Set<Signature>> knownCerts = new HashSet<>();
-
FileReader policyFile = null;
XmlPullParser parser = Xml.newPullParser();
try {
@@ -138,31 +132,15 @@ public final class SELinuxMMAC {
continue;
}
- String tagName = parser.getName();
- if ("signer".equals(tagName)) {
- Policy signerPolicy = readSignerOrThrow(parser);
- // Return of a Policy instance ensures certain invariants have
- // passed, however, we still want to do some cross policy checking.
- // Thus, check that we haven't seen the certs in another stanza.
- Set<Signature> certs = signerPolicy.getSignatures();
- if (knownCerts.contains(certs)) {
- String msg = "Separate stanzas have identical certs";
- throw new IllegalStateException(msg);
- }
- knownCerts.add(certs);
- policies.add(signerPolicy);
- } else if ("default".equals(tagName)) {
- Policy defPolicy = readDefaultOrThrow(parser);
- // Return of a Policy instance ensures certain invariants have
- // passed, however, we still want to do some cross policy checking.
- // Thus, check that we haven't already seen a default stanza.
- if (defaultPolicy != null) {
- String msg = "Multiple default stanzas identified";
- throw new IllegalStateException(msg);
- }
- defaultPolicy = defPolicy;
- } else {
- skip(parser);
+ switch (parser.getName()) {
+ case "signer":
+ policies.add(readSignerOrThrow(parser));
+ break;
+ case "default":
+ policies.add(readDefaultOrThrow(parser));
+ break;
+ default:
+ skip(parser);
}
}
} catch (IllegalStateException | IllegalArgumentException |
@@ -182,15 +160,22 @@ public final class SELinuxMMAC {
IoUtils.closeQuietly(policyFile);
}
- // Add the default policy to the end if there is one. This will ensure that
- // the default stanza is consulted last when performing policy lookups.
- if (defaultPolicy != null) {
- policies.add(defaultPolicy);
+ // Now sort the policy stanzas
+ PolicyComparator policySort = new PolicyComparator();
+ Collections.sort(policies, policySort);
+ if (policySort.foundDuplicate()) {
+ Slog.w(TAG, "ERROR! Duplicate entries found parsing " + MAC_PERMISSIONS);
+ return false;
}
synchronized (sPolicies) {
- sPolicies.clear();
- sPolicies.addAll(policies);
+ sPolicies = policies;
+
+ if (DEBUG_POLICY_ORDER) {
+ for (Policy policy : sPolicies) {
+ Slog.d(TAG, "Policy: " + policy.toString());
+ }
+ }
}
return true;
@@ -494,30 +479,42 @@ public final class SELinuxMMAC {
* of invariants before being built and returned. Each instance can be guaranteed to
* hold one valid policy stanza as outlined in the external/sepolicy/mac_permissions.xml
* file.
- * </p>
+ * <p>
* The following is an example of how to use {@link Policy.PolicyBuilder} to create a
- * signer based Policy instance.
+ * signer based Policy instance with only inner package name refinements.
* </p>
* <pre>
* {@code
* Policy policy = new Policy.PolicyBuilder()
* .addSignature("308204a8...")
* .addSignature("483538c8...")
- * .setGlobalSeinfoOrThrow("paltform")
* .addInnerPackageMapOrThrow("com.foo.", "bar")
* .addInnerPackageMapOrThrow("com.foo.other", "bar")
* .build();
* }
* </pre>
* <p>
- * An example of how to use {@link Policy.PolicyBuilder} to create a default based Policy
- * instance.
+ * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
+ * signer based Policy instance with only a global seinfo tag.
+ * </p>
+ * <pre>
+ * {@code
+ * Policy policy = new Policy.PolicyBuilder()
+ * .addSignature("308204a8...")
+ * .addSignature("483538c8...")
+ * .setGlobalSeinfoOrThrow("paltform")
+ * .build();
+ * }
+ * </pre>
+ * <p>
+ * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
+ * default based Policy instance.
* </p>
* <pre>
* {@code
* Policy policy = new Policy.PolicyBuilder()
* .setAsDefaultPolicy()
- * .setGlobalSeinfoOrThrow("defualt")
+ * .setGlobalSeinfoOrThrow("default")
* .build();
* }
* </pre>
@@ -548,6 +545,65 @@ final class Policy {
}
/**
+ * Return whether this policy object represents a default stanza.
+ *
+ * @return A boolean indicating if this object represents a default policy stanza.
+ */
+ public boolean isDefaultStanza() {
+ return mDefaultStanza;
+ }
+
+ /**
+ * Return whether this policy object contains package name mapping refinements.
+ *
+ * @return A boolean indicating if this object has inner package name mappings.
+ */
+ public boolean hasInnerPackages() {
+ return !mPkgMap.isEmpty();
+ }
+
+ /**
+ * Return the mapping of all package name refinements.
+ *
+ * @return A Map object whose keys are the package names and whose values are
+ * the seinfo assignments.
+ */
+ public Map<String, String> getInnerPackages() {
+ return mPkgMap;
+ }
+
+ /**
+ * Return whether the policy object has a global seinfo tag attached.
+ *
+ * @return A boolean indicating if this stanza has a global seinfo tag.
+ */
+ public boolean hasGlobalSeinfo() {
+ return mSeinfo != null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (mDefaultStanza) {
+ sb.append("defaultStanza=true ");
+ }
+
+ for (Signature cert : mCerts) {
+ sb.append("cert=" + cert.toCharsString().substring(0, 11) + "... ");
+ }
+
+ if (mSeinfo != null) {
+ sb.append("seinfo=" + mSeinfo);
+ }
+
+ for (String name : mPkgMap.keySet()) {
+ sb.append(" " + name + "=" + mPkgMap.get(name));
+ }
+
+ return sb.toString();
+ }
+
+ /**
* <p>
* Determine the seinfo value to assign to an apk. The appropriate seinfo value
* is determined using the following steps:
@@ -620,7 +676,7 @@ final class Policy {
}
/**
- * Sets this stanza as a defualt stanza. All policy stanzas are assumed to
+ * Sets this stanza as a default stanza. All policy stanzas are assumed to
* be signer stanzas unless this method is explicitly called. Default stanzas
* are treated differently with respect to allowable child tags, ordering and
* when and how policy decisions are enforced.
@@ -754,7 +810,7 @@ final class Policy {
* <ul>
* <li> at least one cert must be found </li>
* <li> either a global seinfo value is present OR at least one
- * inner package mapping must be present. </li>
+ * inner package mapping must be present BUT not both. </li>
* </ul>
* </li>
* </ul>
@@ -783,9 +839,9 @@ final class Policy {
String err = "Missing certs with signer tag. Expecting at least one.";
throw new IllegalStateException(err);
}
- if ((p.mSeinfo == null) && (p.mPkgMap.isEmpty())) {
- String err = "Missing seinfo OR package tags with signer tag. At " +
- "least one must be present.";
+ if (!(p.mSeinfo == null ^ p.mPkgMap.isEmpty())) {
+ String err = "Only seinfo tag XOR package tags are allowed within " +
+ "a signer stanza.";
throw new IllegalStateException(err);
}
}
@@ -794,3 +850,58 @@ final class Policy {
}
}
}
+
+/**
+ * Comparision imposing an ordering on Policy objects. It is understood that Policy
+ * objects can only take one of three forms and ordered according to the following
+ * set of rules most specific to least.
+ * <ul>
+ * <li> signer stanzas with inner package mappings </li>
+ * <li> signer stanzas with global seinfo tags </li>
+ * <li> default stanza </li>
+ * </ul>
+ * This comparison also checks for duplicate entries on the input selectors. Any
+ * found duplicates will be flagged and can be checked with {@link #foundDuplicate}.
+ */
+
+final class PolicyComparator implements Comparator<Policy> {
+
+ private boolean duplicateFound = false;
+
+ public boolean foundDuplicate() {
+ return duplicateFound;
+ }
+
+ @Override
+ public int compare(Policy p1, Policy p2) {
+
+ // Give precedence to signature stanzas over default stanzas
+ if (p1.isDefaultStanza() != p2.isDefaultStanza()) {
+ return p1.isDefaultStanza() ? 1 : -1;
+ }
+
+ // Give precedence to stanzas with inner package mappings
+ if (p1.hasInnerPackages() != p2.hasInnerPackages()) {
+ return p1.hasInnerPackages() ? -1 : 1;
+ }
+
+ // Check for duplicate entries
+ if (p1.getSignatures().equals(p2.getSignatures())) {
+ // Checks if default stanza or a signer w/o inner package names
+ if (p1.hasGlobalSeinfo()) {
+ duplicateFound = true;
+ Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString());
+ }
+
+ // Look for common inner package name mappings
+ final Map<String, String> p1Packages = p1.getInnerPackages();
+ final Map<String, String> p2Packages = p2.getInnerPackages();
+ if (!Collections.disjoint(p1Packages.keySet(), p2Packages.keySet())) {
+ duplicateFound = true;
+ Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString());
+ }
+ }
+
+ return 0;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a8c5527..e79a206 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -327,16 +327,20 @@ public class UserManagerService extends IUserManager.Stub {
public UserInfo getProfileParent(int userHandle) {
checkManageUsersPermission("get the profile parent");
synchronized (mPackagesLock) {
- UserInfo profile = getUserInfoLocked(userHandle);
- if (profile == null) {
- return null;
- }
- int parentUserId = profile.profileGroupId;
- if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
- return null;
- } else {
- return getUserInfoLocked(parentUserId);
- }
+ return getProfileParentLocked(userHandle);
+ }
+ }
+
+ private UserInfo getProfileParentLocked(int userHandle) {
+ UserInfo profile = getUserInfoLocked(userHandle);
+ if (profile == null) {
+ return null;
+ }
+ int parentUserId = profile.profileGroupId;
+ if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
+ return null;
+ } else {
+ return getUserInfoLocked(parentUserId);
}
}
@@ -1867,6 +1871,27 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ @Override
+ public long getUserCreationTime(int userHandle) {
+ int callingUserId = UserHandle.getCallingUserId();
+ UserInfo userInfo = null;
+ synchronized (mPackagesLock) {
+ if (callingUserId == userHandle) {
+ userInfo = getUserInfoLocked(userHandle);
+ } else {
+ UserInfo parent = getProfileParentLocked(userHandle);
+ if (parent != null && parent.id == callingUserId) {
+ userInfo = getUserInfoLocked(userHandle);
+ }
+ }
+ }
+ if (userInfo == null) {
+ throw new SecurityException("userHandle can only be the calling user or a managed "
+ + "profile associated with this user");
+ }
+ return userInfo.creationTime;
+ }
+
/**
* Caches the list of user ids in an array, adjusting the array size when necessary.
*/
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ed14569..936840a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -990,7 +990,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
launchHomeFromHotKey();
break;
case SHORT_PRESS_POWER_GO_HOME:
- launchHomeFromHotKey();
+ launchHomeFromHotKey(true /* awakenFromDreams */, false /*respectKeyguard*/);
break;
}
}
@@ -1068,7 +1068,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0);
break;
case SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME:
- launchHomeFromHotKey(false /* awakenDreams */);
+ launchHomeFromHotKey(false /* awakenDreams */, true /*respectKeyguard*/);
mPowerManager.goToSleep(event.getEventTime(),
PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0);
break;
@@ -3059,50 +3059,56 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
void launchHomeFromHotKey() {
- launchHomeFromHotKey(true /* awakenFromDreams */);
+ launchHomeFromHotKey(true /* awakenFromDreams */, true /*respectKeyguard*/);
}
/**
* A home key -> launch home action was detected. Take the appropriate action
* given the situation with the keyguard.
*/
- void launchHomeFromHotKey(final boolean awakenFromDreams) {
- if (isKeyguardShowingAndNotOccluded()) {
- // don't launch home if keyguard showing
- } else if (!mHideLockScreen && mKeyguardDelegate.isInputRestricted()) {
- // when in keyguard restricted mode, must first verify unlock
- // before launching home
- mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
- @Override
- public void onKeyguardExitResult(boolean success) {
- if (success) {
- try {
- ActivityManagerNative.getDefault().stopAppSwitches();
- } catch (RemoteException e) {
+ void launchHomeFromHotKey(final boolean awakenFromDreams, final boolean respectKeyguard) {
+ if (respectKeyguard) {
+ if (isKeyguardShowingAndNotOccluded()) {
+ // don't launch home if keyguard showing
+ return;
+ }
+
+ if (!mHideLockScreen && mKeyguardDelegate.isInputRestricted()) {
+ // when in keyguard restricted mode, must first verify unlock
+ // before launching home
+ mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
+ @Override
+ public void onKeyguardExitResult(boolean success) {
+ if (success) {
+ try {
+ ActivityManagerNative.getDefault().stopAppSwitches();
+ } catch (RemoteException e) {
+ }
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+ startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
}
- sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
- startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
}
- }
- });
- } else {
- // no keyguard stuff to worry about, just launch home!
- try {
- ActivityManagerNative.getDefault().stopAppSwitches();
- } catch (RemoteException e) {
+ });
+ return;
}
- if (mRecentsVisible) {
- // Hide Recents and notify it to launch Home
- if (awakenFromDreams) {
- awakenDreams();
- }
- sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
- hideRecentApps(false, true);
- } else {
- // Otherwise, just launch Home
- sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
- startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
+ }
+
+ // no keyguard stuff to worry about, just launch home!
+ try {
+ ActivityManagerNative.getDefault().stopAppSwitches();
+ } catch (RemoteException e) {
+ }
+ if (mRecentsVisible) {
+ // Hide Recents and notify it to launch Home
+ if (awakenFromDreams) {
+ awakenDreams();
}
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+ hideRecentApps(false, true);
+ } else {
+ // Otherwise, just launch Home
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+ startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
}
}
diff --git a/services/core/java/com/android/server/power/DeviceIdleController.java b/services/core/java/com/android/server/power/DeviceIdleController.java
index dd00446..a23a87b 100644
--- a/services/core/java/com/android/server/power/DeviceIdleController.java
+++ b/services/core/java/com/android/server/power/DeviceIdleController.java
@@ -377,7 +377,7 @@ public class DeviceIdleController extends SystemService {
}
}
- void scheduleAlarmLocked(long delay, boolean wakeup) {
+ void scheduleAlarmLocked(long delay, boolean idleUntil) {
if (mSigMotionSensor == null) {
// If there is no significant motion sensor on this device, then we won't schedule
// alarms, because we can't determine if the device is not moving. This effectively
@@ -386,8 +386,13 @@ public class DeviceIdleController extends SystemService {
return;
}
mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
- mAlarmManager.set(wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP
- : AlarmManager.ELAPSED_REALTIME, mNextAlarmTime, mAlarmIntent);
+ if (idleUntil) {
+ mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mNextAlarmTime, mAlarmIntent);
+ } else {
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mNextAlarmTime, mAlarmIntent);
+ }
}
private void dumpHelp(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 9b4b522..3262cc6 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -204,6 +204,14 @@ public final class TvInputManagerService extends SystemService {
}
@Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ // The input list needs to be updated in any cases, regardless of whether
+ // it happened to the whole package or a specific component. Returning true so that
+ // the update can be handled in {@link #onSomePackagesChanged}.
+ return true;
+ }
+
+ @Override
public void onPackageRemoved(String packageName, int uid) {
synchronized (mLock) {
UserState userState = getUserStateLocked(getChangingUserId());
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 1d2180e..c1c5c56 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -372,8 +372,8 @@ public class MidiService extends IMidiManager.Stub {
int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
Bundle properties, int type) {
int uid = Binder.getCallingUid();
- if (type != MidiDeviceInfo.TYPE_VIRTUAL && uid != Process.SYSTEM_UID) {
- throw new SecurityException("only system can create non-virtual devices");
+ if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) {
+ throw new SecurityException("only system can create USB devices");
}
synchronized (mDevicesByInfo) {
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index 33edb11..ae19dac 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -204,8 +204,8 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
IntentSender intentSender = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
- | PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(mUserId))
- .getIntentSender();
+ | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ null, new UserHandle(mUserId)) .getIntentSender();
Bundle result = new Bundle();
result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob);
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 7383478..7ea5aa7 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -39,6 +39,7 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.anyLong;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
@@ -879,7 +880,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase {
expectLastCall().anyTimes();
mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
- isA(PendingIntent.class), isA(WorkSource.class),
+ anyInt(), isA(PendingIntent.class), isA(WorkSource.class),
isA(AlarmManager.AlarmClockInfo.class));
expectLastCall().atLeastOnce();
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 a0b8c94..bfa308e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -32,8 +32,9 @@ import java.util.List;
/** Test {@link UserManager} functionality. */
public class UserManagerTest extends AndroidTestCase {
- UserManager mUserManager = null;
- Object mUserLock = new Object();
+ private UserManager mUserManager = null;
+ private final Object mUserLock = new Object();
+ private List<Integer> usersToRemove;
@Override
public void setUp() throws Exception {
@@ -49,11 +50,19 @@ public class UserManagerTest extends AndroidTestCase {
}, filter);
removeExistingUsers();
+ usersToRemove = new ArrayList<>();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ for (Integer userId : usersToRemove) {
+ removeUser(userId);
+ }
+ super.tearDown();
}
private void removeExistingUsers() {
List<UserInfo> list = mUserManager.getUsers();
- boolean found = false;
for (UserInfo user : list) {
if (user.id != UserHandle.USER_OWNER) {
removeUser(user.id);
@@ -66,7 +75,7 @@ public class UserManagerTest extends AndroidTestCase {
}
public void testAddUser() throws Exception {
- UserInfo userInfo = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
+ UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST);
assertTrue(userInfo != null);
List<UserInfo> list = mUserManager.getUsers();
@@ -83,12 +92,11 @@ public class UserManagerTest extends AndroidTestCase {
}
}
assertTrue(found);
- removeUser(userInfo.id);
}
public void testAdd2Users() throws Exception {
- UserInfo user1 = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
- UserInfo user2 = mUserManager.createUser("User 2", UserInfo.FLAG_ADMIN);
+ UserInfo user1 = createUser("Guest 1", UserInfo.FLAG_GUEST);
+ UserInfo user2 = createUser("User 2", UserInfo.FLAG_ADMIN);
assertTrue(user1 != null);
assertTrue(user2 != null);
@@ -96,41 +104,65 @@ public class UserManagerTest extends AndroidTestCase {
assertTrue(findUser(0));
assertTrue(findUser(user1.id));
assertTrue(findUser(user2.id));
- removeUser(user1.id);
- removeUser(user2.id);
}
public void testRemoveUser() throws Exception {
- UserInfo userInfo = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
+ UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST);
removeUser(userInfo.id);
assertFalse(findUser(userInfo.id));
}
public void testAddGuest() throws Exception {
- UserInfo userInfo1 = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
- UserInfo userInfo2 = mUserManager.createUser("Guest 2", UserInfo.FLAG_GUEST);
+ UserInfo userInfo1 = createUser("Guest 1", UserInfo.FLAG_GUEST);
+ UserInfo userInfo2 = createUser("Guest 2", UserInfo.FLAG_GUEST);
assertNotNull(userInfo1);
assertNull(userInfo2);
-
- // Cleanup
- removeUser(userInfo1.id);
}
// Make sure only one managed profile can be created
public void testAddManagedProfile() throws Exception {
- UserInfo userInfo1 = mUserManager.createProfileForUser("Managed 1",
+ UserInfo userInfo1 = createProfileForUser("Managed 1",
UserInfo.FLAG_MANAGED_PROFILE, UserHandle.USER_OWNER);
- UserInfo userInfo2 = mUserManager.createProfileForUser("Managed 2",
+ UserInfo userInfo2 = createProfileForUser("Managed 2",
UserInfo.FLAG_MANAGED_PROFILE, UserHandle.USER_OWNER);
assertNotNull(userInfo1);
assertNull(userInfo2);
// Verify that current user is not a managed profile
assertFalse(mUserManager.isManagedProfile());
- // Cleanup
- removeUser(userInfo1.id);
}
+ public void testGetUserCreationTime() throws Exception {
+ UserInfo profile = createProfileForUser("Managed 1",
+ UserInfo.FLAG_MANAGED_PROFILE, UserHandle.USER_OWNER);
+ assertNotNull(profile);
+ assertTrue("creationTime must be set when the profile is created",
+ profile.creationTime > 0);
+ assertEquals(profile.creationTime, mUserManager.getUserCreationTime(profile.id));
+
+ long ownerCreationTime = mUserManager.getUserInfo(UserHandle.USER_OWNER).creationTime;
+ assertEquals(ownerCreationTime, mUserManager.getUserCreationTime(UserHandle.USER_OWNER));
+
+ try {
+ int noSuchUserId = 100500;
+ mUserManager.getUserCreationTime(noSuchUserId);
+ fail("SecurityException should be thrown for nonexistent user");
+ } catch (Exception e) {
+ assertTrue("SecurityException should be thrown for nonexistent user, but was: " + e,
+ e instanceof SecurityException);
+ }
+
+ UserInfo user = createUser("User 1", 0);
+ try {
+ mUserManager.getUserCreationTime(user.id);
+ fail("SecurityException should be thrown for other user");
+ } catch (Exception e) {
+ assertTrue("SecurityException should be thrown for other user, but was: " + e,
+ e instanceof SecurityException);
+ }
+ }
+
+
private boolean findUser(int id) {
List<UserInfo> list = mUserManager.getUsers();
@@ -143,40 +175,29 @@ public class UserManagerTest extends AndroidTestCase {
}
public void testSerialNumber() {
- UserInfo user1 = mUserManager.createUser("User 1", UserInfo.FLAG_RESTRICTED);
+ UserInfo user1 = createUser("User 1", UserInfo.FLAG_RESTRICTED);
int serialNumber1 = user1.serialNumber;
assertEquals(serialNumber1, mUserManager.getUserSerialNumber(user1.id));
assertEquals(user1.id, mUserManager.getUserHandle(serialNumber1));
- removeUser(user1.id);
- UserInfo user2 = mUserManager.createUser("User 2", UserInfo.FLAG_RESTRICTED);
+ UserInfo user2 = createUser("User 2", UserInfo.FLAG_RESTRICTED);
int serialNumber2 = user2.serialNumber;
assertFalse(serialNumber1 == serialNumber2);
assertEquals(serialNumber2, mUserManager.getUserSerialNumber(user2.id));
assertEquals(user2.id, mUserManager.getUserHandle(serialNumber2));
- removeUser(user2.id);
}
public void testMaxUsers() {
int N = UserManager.getMaxSupportedUsers();
int count = mUserManager.getUsers().size();
- List<UserInfo> created = new ArrayList<UserInfo>();
// Create as many users as permitted and make sure creation passes
while (count < N) {
- UserInfo ui = mUserManager.createUser("User " + count, 0);
+ UserInfo ui = createUser("User " + count, 0);
assertNotNull(ui);
- created.add(ui);
count++;
}
// Try to create one more user and make sure it fails
- UserInfo extra = null;
- assertNull(extra = mUserManager.createUser("One more", 0));
- if (extra != null) {
- removeUser(extra.id);
- }
- while (!created.isEmpty()) {
- UserInfo user = created.remove(0);
- removeUser(user.id);
- }
+ UserInfo extra = createUser("One more", 0);
+ assertNull(extra);
}
public void testRestrictions() {
@@ -198,11 +219,27 @@ public class UserManagerTest extends AndroidTestCase {
mUserManager.removeUser(userId);
while (mUserManager.getUserInfo(userId) != null) {
try {
- mUserLock.wait(1000);
+ mUserLock.wait(500);
} catch (InterruptedException ie) {
}
}
}
}
+ private UserInfo createUser(String name, int flags) {
+ UserInfo user = mUserManager.createUser(name, flags);
+ if (user != null) {
+ usersToRemove.add(user.id);
+ }
+ return user;
+ }
+
+ private UserInfo createProfileForUser(String name, int flags, int userHandle) {
+ UserInfo profile = mUserManager.createProfileForUser(name, flags, userHandle);
+ if (profile != null) {
+ usersToRemove.add(profile.id);
+ }
+ return profile;
+ }
+
}
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 2728af1..8f0c6c8 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -469,10 +469,12 @@ public final class UsbAlsaManager {
if (enabled && mPeripheralMidiDevice == null) {
Bundle properties = new Bundle();
Resources r = mContext.getResources();
+ properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString(
+ com.android.internal.R.string.usb_midi_peripheral_name));
properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString(
com.android.internal.R.string.usb_midi_peripheral_manufacturer_name));
properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString(
- com.android.internal.R.string.usb_midi_peripheral_model_name));
+ com.android.internal.R.string.usb_midi_peripheral_product_name));
properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device);
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index 6ece888..671cf01 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -45,7 +45,8 @@ public final class UsbMidiDevice implements Closeable {
private MidiDeviceServer mServer;
- private final MidiEventScheduler mEventScheduler;
+ // event schedulers for each output port
+ private final MidiEventScheduler[] mEventSchedulers;
private static final int BUFFER_SIZE = 512;
@@ -99,10 +100,11 @@ public final class UsbMidiDevice implements Closeable {
}
mOutputStreams = new FileOutputStream[outputCount];
+ mEventSchedulers = new MidiEventScheduler[outputCount];
for (int i = 0; i < outputCount; i++) {
mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
+ mEventSchedulers[i] = new MidiEventScheduler();
}
- mEventScheduler = new MidiEventScheduler(inputCount);
}
private boolean register(Context context, Bundle properties) {
@@ -116,7 +118,7 @@ public final class UsbMidiDevice implements Closeable {
int outputCount = mOutputStreams.length;
MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount];
for (int port = 0; port < inputCount; port++) {
- inputPortReceivers[port] = mEventScheduler.getReceiver(port);
+ inputPortReceivers[port] = mEventSchedulers[port].getReceiver();
}
mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount,
@@ -126,7 +128,7 @@ public final class UsbMidiDevice implements Closeable {
}
final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
- // Create input thread
+ // Create input thread which will read from all input ports
new Thread("UsbMidiDevice input thread") {
@Override
public void run() {
@@ -161,38 +163,46 @@ public final class UsbMidiDevice implements Closeable {
}
}.start();
- // Create output thread
- new Thread("UsbMidiDevice output thread") {
- @Override
- public void run() {
- while (true) {
- MidiEvent event;
- try {
- event = (MidiEvent)mEventScheduler.waitNextEvent();
- } catch (InterruptedException e) {
- // try again
- continue;
- }
- if (event == null) {
- break;
- }
- try {
- mOutputStreams[event.portNumber].write(event.data, 0, event.count);
- } catch (IOException e) {
- Log.e(TAG, "write failed for port " + event.portNumber);
+ // Create output thread for each output port
+ for (int port = 0; port < outputCount; port++) {
+ final MidiEventScheduler eventSchedulerF = mEventSchedulers[port];
+ final FileOutputStream outputStreamF = mOutputStreams[port];
+ final int portF = port;
+
+ new Thread("UsbMidiDevice output thread " + port) {
+ @Override
+ public void run() {
+ while (true) {
+ MidiEvent event;
+ try {
+ event = (MidiEvent)eventSchedulerF.waitNextEvent();
+ } catch (InterruptedException e) {
+ // try again
+ continue;
+ }
+ if (event == null) {
+ break;
+ }
+ try {
+ outputStreamF.write(event.data, 0, event.count);
+ } catch (IOException e) {
+ Log.e(TAG, "write failed for port " + portF);
+ }
+ eventSchedulerF.addEventToPool(event);
}
- mEventScheduler.addEventToPool(event);
+ Log.d(TAG, "output thread exit");
}
- Log.d(TAG, "output thread exit");
- }
- }.start();
+ }.start();
+ }
return true;
}
@Override
public void close() throws IOException {
- mEventScheduler.close();
+ for (int i = 0; i < mEventSchedulers.length; i++) {
+ mEventSchedulers[i].close();
+ }
if (mServer != null) {
mServer.close();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 607df2d..fb83956 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -162,8 +162,8 @@ final class VoiceInteractionSessionConnection implements ServiceConnection {
mBindIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
mBindIntent.setComponent(mSessionComponentName);
mBound = mContext.bindServiceAsUser(mBindIntent, this,
- Context.BIND_AUTO_CREATE|Context.BIND_WAIVE_PRIORITY
- |Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mUser));
+ Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
+ | Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mUser));
if (mBound) {
try {
mIWindowManager.addWindowToken(mToken,
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 70ac268..24bdb7a 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -157,6 +157,34 @@ interface ISms {
in PendingIntent deliveryIntent);
/**
+ * Send a data SMS. Only for use internally.
+ *
+ * @param smsc the SMSC to send the message through, or NULL for the
+ * default SMSC
+ * @param data the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is sucessfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applicaitons,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ * @param subId the subId id.
+ */
+ void sendDataForSubscriberWithSelfPermissions(int subId, String callingPkg, in String destAddr,
+ in String scAddr, in int destPort, in byte[] data, in PendingIntent sentIntent,
+ in PendingIntent deliveryIntent);
+
+ /**
* Send an SMS.
*
* @param smsc the SMSC to send the message through, or NULL for the
@@ -211,6 +239,34 @@ interface ISms {
in PendingIntent deliveryIntent);
/**
+ * Send an SMS. Internal use only.
+ *
+ * @param smsc the SMSC to send the message through, or NULL for the
+ * default SMSC
+ * @param text the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is sucessfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ * @param subId the subId on which the SMS has to be sent.
+ */
+ void sendTextForSubscriberWithSelfPermissions(in int subId, String callingPkg,
+ in String destAddr, in String scAddr, in String text, in PendingIntent sentIntent,
+ in PendingIntent deliveryIntent);
+
+ /**
* Inject an SMS PDU into the android platform.
*
* @param pdu is the byte array of pdu to be injected into android application framework
diff --git a/test-runner/src/android/test/mock/MockCursor.java b/test-runner/src/android/test/mock/MockCursor.java
index a37c6eb..28fa0f8 100644
--- a/test-runner/src/android/test/mock/MockCursor.java
+++ b/test-runner/src/android/test/mock/MockCursor.java
@@ -35,162 +35,209 @@ import android.os.Bundle;
* </P>
*/
public class MockCursor implements Cursor {
+ @Override
public int getColumnCount() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public int getColumnIndex(String columnName) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public int getColumnIndexOrThrow(String columnName) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public String getColumnName(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public String[] getColumnNames() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public int getCount() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean isNull(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public int getInt(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public long getLong(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public short getShort(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public float getFloat(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public double getDouble(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public byte[] getBlob(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public String getString(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
+ public void setExtras(Bundle extras) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
public Bundle getExtras() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public int getPosition() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean isAfterLast() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean isBeforeFirst() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean isFirst() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean isLast() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean move(int offset) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean moveToFirst() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean moveToLast() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean moveToNext() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean moveToPrevious() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean moveToPosition(int position) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
+ @Deprecated
public void deactivate() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public void close() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean isClosed() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
+ @Deprecated
public boolean requery() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public void registerContentObserver(ContentObserver observer) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public void registerDataSetObserver(DataSetObserver observer) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public Bundle respond(Bundle extras) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean getWantsAllOnMoveCalls() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public void setNotificationUri(ContentResolver cr, Uri uri) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public Uri getNotificationUri() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public void unregisterContentObserver(ContentObserver observer) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public void unregisterDataSetObserver(DataSetObserver observer) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public int getType(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
diff --git a/tests/Compatibility/Android.mk b/tests/Compatibility/Android.mk
index 0ec4d9d..c2f89dd 100644
--- a/tests/Compatibility/Android.mk
+++ b/tests/Compatibility/Android.mk
@@ -25,7 +25,7 @@ LOCAL_SRC_FILES := \
LOCAL_PACKAGE_NAME := AppCompatibilityTest
-
+LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/Compatibility/AndroidManifest.xml b/tests/Compatibility/AndroidManifest.xml
index 2884532..8ae5bc5 100644
--- a/tests/Compatibility/AndroidManifest.xml
+++ b/tests/Compatibility/AndroidManifest.xml
@@ -19,7 +19,7 @@
<application >
<uses-library android:name="android.test.runner" />
</application>
-
+ <uses-permission android:name="android.permission.REAL_GET_TASKS" />
<instrumentation
android:name=".AppCompatibilityRunner"
android:targetPackage="com.android.compatibilitytest"
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 9cea176..0622dc6 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -27,14 +27,17 @@ main := Main.cpp
sources := \
BigBuffer.cpp \
BinaryResourceParser.cpp \
+ BindingXmlPullParser.cpp \
ConfigDescription.cpp \
Files.cpp \
+ Flag.cpp \
JavaClassGenerator.cpp \
Linker.cpp \
Locale.cpp \
Logger.cpp \
ManifestParser.cpp \
ManifestValidator.cpp \
+ Png.cpp \
ResChunkPullParser.cpp \
Resolver.cpp \
Resource.cpp \
@@ -52,6 +55,7 @@ sources := \
testSources := \
BigBuffer_test.cpp \
+ BindingXmlPullParser_test.cpp \
Compat_test.cpp \
ConfigDescription_test.cpp \
JavaClassGenerator_test.cpp \
@@ -69,7 +73,10 @@ testSources := \
XliffXmlPullParser_test.cpp \
XmlFlattener_test.cpp
-cIncludes :=
+cIncludes := \
+ external/libpng \
+ external/libz
+
hostLdLibs :=
hostStaticLibs := \
@@ -78,7 +85,8 @@ hostStaticLibs := \
liblog \
libcutils \
libexpat \
- libziparchive-host
+ libziparchive-host \
+ libpng
ifneq ($(strip $(USE_MINGW)),)
hostStaticLibs += libz
diff --git a/tools/aapt2/BindingXmlPullParser.cpp b/tools/aapt2/BindingXmlPullParser.cpp
new file mode 100644
index 0000000..58b96e8
--- /dev/null
+++ b/tools/aapt2/BindingXmlPullParser.cpp
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BindingXmlPullParser.h"
+#include "Util.h"
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+constexpr const char16_t* kBindingNamespaceUri = u"http://schemas.android.com/apk/binding";
+constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android";
+constexpr const char16_t* kVariableTagName = u"variable";
+constexpr const char* kBindingTagPrefix = "android:binding_";
+
+BindingXmlPullParser::BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) :
+ mParser(parser), mOverride(false), mNextTagId(0) {
+}
+
+bool BindingXmlPullParser::readVariableDeclaration() {
+ VarDecl var;
+
+ const auto endAttrIter = mParser->endAttributes();
+ for (auto attrIter = mParser->beginAttributes(); attrIter != endAttrIter; ++attrIter) {
+ if (!attrIter->namespaceUri.empty()) {
+ continue;
+ }
+
+ if (attrIter->name == u"name") {
+ var.name = util::utf16ToUtf8(attrIter->value);
+ } else if (attrIter->name == u"type") {
+ var.type = util::utf16ToUtf8(attrIter->value);
+ }
+ }
+
+ XmlPullParser::skipCurrentElement(mParser.get());
+
+ if (var.name.empty()) {
+ mLastError = "variable declaration missing name";
+ return false;
+ }
+
+ if (var.type.empty()) {
+ mLastError = "variable declaration missing type";
+ return false;
+ }
+
+ mVarDecls.push_back(std::move(var));
+ return true;
+}
+
+bool BindingXmlPullParser::readExpressions() {
+ mOverride = true;
+ std::vector<XmlPullParser::Attribute> expressions;
+ std::string idValue;
+
+ const auto endAttrIter = mParser->endAttributes();
+ for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) {
+ if (attr->namespaceUri == kAndroidNamespaceUri && attr->name == u"id") {
+ idValue = util::utf16ToUtf8(attr->value);
+ } else {
+ StringPiece16 value = util::trimWhitespace(attr->value);
+ if (util::stringStartsWith<char16_t>(value, u"@{") &&
+ util::stringEndsWith<char16_t>(value, u"}")) {
+ // This is attribute's value is an expression of the form
+ // @{expression}. We need to capture the expression inside.
+ expressions.push_back(XmlPullParser::Attribute{
+ attr->namespaceUri,
+ attr->name,
+ value.substr(2, value.size() - 3).toString()
+ });
+ } else {
+ // This is a normal attribute, use as is.
+ mAttributes.emplace_back(*attr);
+ }
+ }
+ }
+
+ // Check if we have any expressions.
+ if (!expressions.empty()) {
+ // We have expressions, so let's assign the target a tag number
+ // and add it to our targets list.
+ int32_t targetId = mNextTagId++;
+ mTargets.push_back(Target{
+ util::utf16ToUtf8(mParser->getElementName()),
+ idValue,
+ targetId,
+ std::move(expressions)
+ });
+
+ std::stringstream numGen;
+ numGen << kBindingTagPrefix << targetId;
+ mAttributes.push_back(XmlPullParser::Attribute{
+ std::u16string(kAndroidNamespaceUri),
+ std::u16string(u"tag"),
+ util::utf8ToUtf16(numGen.str())
+ });
+ }
+ return true;
+}
+
+XmlPullParser::Event BindingXmlPullParser::next() {
+ // Clear old state in preparation for the next event.
+ mOverride = false;
+ mAttributes.clear();
+
+ while (true) {
+ Event event = mParser->next();
+ if (event == Event::kStartElement) {
+ if (mParser->getElementNamespace().empty() &&
+ mParser->getElementName() == kVariableTagName) {
+ // This is a variable tag. Record data from it, and
+ // then discard the entire element.
+ if (!readVariableDeclaration()) {
+ // mLastError is set, so getEvent will return kBadDocument.
+ return getEvent();
+ }
+ continue;
+ } else {
+ // Check for expressions of the form @{} in attribute text.
+ const auto endAttrIter = mParser->endAttributes();
+ for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) {
+ StringPiece16 value = util::trimWhitespace(attr->value);
+ if (util::stringStartsWith<char16_t>(value, u"@{") &&
+ util::stringEndsWith<char16_t>(value, u"}")) {
+ if (!readExpressions()) {
+ return getEvent();
+ }
+ break;
+ }
+ }
+ }
+ } else if (event == Event::kStartNamespace || event == Event::kEndNamespace) {
+ if (mParser->getNamespaceUri() == kBindingNamespaceUri) {
+ // Skip binding namespace tags.
+ continue;
+ }
+ }
+ return event;
+ }
+ return Event::kBadDocument;
+}
+
+bool BindingXmlPullParser::writeToFile(std::ostream& out) const {
+ out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+ out << "<Layout directory=\"\" layout=\"\" layoutId=\"\">\n";
+
+ // Write the variables.
+ out << " <Variables>\n";
+ for (const VarDecl& v : mVarDecls) {
+ out << " <entries name=\"" << v.name << "\" type=\"" << v.type << "\"/>\n";
+ }
+ out << " </Variables>\n";
+
+ // Write the imports.
+
+ std::stringstream tagGen;
+
+ // Write the targets.
+ out << " <Targets>\n";
+ for (const Target& t : mTargets) {
+ tagGen.str({});
+ tagGen << kBindingTagPrefix << t.tagId;
+ out << " <Target boundClass=\"" << t.className << "\" id=\"" << t.id
+ << "\" tag=\"" << tagGen.str() << "\">\n";
+ out << " <Expressions>\n";
+ for (const XmlPullParser::Attribute& a : t.expressions) {
+ out << " <Expression attribute=\"" << a.namespaceUri << ":" << a.name
+ << "\" text=\"" << a.value << "\"/>\n";
+ }
+ out << " </Expressions>\n";
+ out << " </Target>\n";
+ }
+ out << " </Targets>\n";
+
+ out << "</Layout>\n";
+ return bool(out);
+}
+
+XmlPullParser::const_iterator BindingXmlPullParser::beginAttributes() const {
+ if (mOverride) {
+ return mAttributes.begin();
+ }
+ return mParser->beginAttributes();
+}
+
+XmlPullParser::const_iterator BindingXmlPullParser::endAttributes() const {
+ if (mOverride) {
+ return mAttributes.end();
+ }
+ return mParser->endAttributes();
+}
+
+size_t BindingXmlPullParser::getAttributeCount() const {
+ if (mOverride) {
+ return mAttributes.size();
+ }
+ return mParser->getAttributeCount();
+}
+
+XmlPullParser::Event BindingXmlPullParser::getEvent() const {
+ if (!mLastError.empty()) {
+ return Event::kBadDocument;
+ }
+ return mParser->getEvent();
+}
+
+const std::string& BindingXmlPullParser::getLastError() const {
+ if (!mLastError.empty()) {
+ return mLastError;
+ }
+ return mParser->getLastError();
+}
+
+const std::u16string& BindingXmlPullParser::getComment() const {
+ return mParser->getComment();
+}
+
+size_t BindingXmlPullParser::getLineNumber() const {
+ return mParser->getLineNumber();
+}
+
+size_t BindingXmlPullParser::getDepth() const {
+ return mParser->getDepth();
+}
+
+const std::u16string& BindingXmlPullParser::getText() const {
+ return mParser->getText();
+}
+
+const std::u16string& BindingXmlPullParser::getNamespacePrefix() const {
+ return mParser->getNamespacePrefix();
+}
+
+const std::u16string& BindingXmlPullParser::getNamespaceUri() const {
+ return mParser->getNamespaceUri();
+}
+
+const std::u16string& BindingXmlPullParser::getElementNamespace() const {
+ return mParser->getElementNamespace();
+}
+
+const std::u16string& BindingXmlPullParser::getElementName() const {
+ return mParser->getElementName();
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/BindingXmlPullParser.h b/tools/aapt2/BindingXmlPullParser.h
new file mode 100644
index 0000000..c892b09
--- /dev/null
+++ b/tools/aapt2/BindingXmlPullParser.h
@@ -0,0 +1,88 @@
+/*
+ * 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_BINDING_XML_PULL_PARSER_H
+#define AAPT_BINDING_XML_PULL_PARSER_H
+
+#include "XmlPullParser.h"
+
+#include <iostream>
+#include <memory>
+#include <string>
+
+namespace aapt {
+
+class BindingXmlPullParser : public XmlPullParser {
+public:
+ BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser);
+ BindingXmlPullParser(const BindingXmlPullParser& rhs) = delete;
+
+ Event getEvent() const override;
+ const std::string& getLastError() const override;
+ Event next() override;
+
+ const std::u16string& getComment() const override;
+ size_t getLineNumber() const override;
+ size_t getDepth() const override;
+
+ const std::u16string& getText() const override;
+
+ const std::u16string& getNamespacePrefix() const override;
+ const std::u16string& getNamespaceUri() const override;
+
+ const std::u16string& getElementNamespace() const override;
+ const std::u16string& getElementName() const override;
+
+ const_iterator beginAttributes() const override;
+ const_iterator endAttributes() const override;
+ size_t getAttributeCount() const override;
+
+ bool writeToFile(std::ostream& out) const;
+
+private:
+ struct VarDecl {
+ std::string name;
+ std::string type;
+ };
+
+ struct Import {
+ std::string name;
+ std::string type;
+ };
+
+ struct Target {
+ std::string className;
+ std::string id;
+ int32_t tagId;
+
+ std::vector<XmlPullParser::Attribute> expressions;
+ };
+
+ bool readVariableDeclaration();
+ bool readExpressions();
+
+ std::shared_ptr<XmlPullParser> mParser;
+ std::string mLastError;
+ bool mOverride;
+ std::vector<XmlPullParser::Attribute> mAttributes;
+ std::vector<VarDecl> mVarDecls;
+ std::vector<Target> mTargets;
+ int32_t mNextTagId;
+};
+
+} // namespace aapt
+
+#endif // AAPT_BINDING_XML_PULL_PARSER_H
diff --git a/tools/aapt2/BindingXmlPullParser_test.cpp b/tools/aapt2/BindingXmlPullParser_test.cpp
new file mode 100644
index 0000000..28edcb6
--- /dev/null
+++ b/tools/aapt2/BindingXmlPullParser_test.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SourceXmlPullParser.h"
+#include "BindingXmlPullParser.h"
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+namespace aapt {
+
+constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android";
+
+TEST(BindingXmlPullParserTest, SubstituteBindingExpressionsWithTag) {
+ std::stringstream input;
+ input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
+ << " android:id=\"@+id/content\">\n"
+ << " <variable name=\"user\" type=\"com.android.test.User\"/>\n"
+ << " <TextView android:text=\"@{user.name}\" android:layout_width=\"wrap_content\"\n"
+ << " android:layout_height=\"wrap_content\"/>\n"
+ << "</LinearLayout>\n";
+ std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
+ BindingXmlPullParser parser(sourceParser);
+
+ ASSERT_EQ(XmlPullParser::Event::kStartNamespace, parser.next());
+ EXPECT_EQ(std::u16string(u"http://schemas.android.com/apk/res/android"),
+ parser.getNamespaceUri());
+
+ ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.next());
+ EXPECT_EQ(std::u16string(u"LinearLayout"), parser.getElementName());
+
+ while (parser.next() == XmlPullParser::Event::kText) {}
+
+ ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+ EXPECT_EQ(std::u16string(u"TextView"), parser.getElementName());
+
+ ASSERT_EQ(3u, parser.getAttributeCount());
+ const auto endAttr = parser.endAttributes();
+ EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_width"));
+ EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_height"));
+ EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"tag"));
+
+ while (parser.next() == XmlPullParser::Event::kText) {}
+
+ ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent());
+
+ while (parser.next() == XmlPullParser::Event::kText) {}
+
+ ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent());
+ ASSERT_EQ(XmlPullParser::Event::kEndNamespace, parser.next());
+}
+
+TEST(BindingXmlPullParserTest, GenerateVariableDeclarations) {
+ std::stringstream input;
+ input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
+ << " android:id=\"@+id/content\">\n"
+ << " <variable name=\"user\" type=\"com.android.test.User\"/>\n"
+ << "</LinearLayout>\n";
+ std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
+ BindingXmlPullParser parser(sourceParser);
+
+ while (XmlPullParser::isGoodEvent(parser.next())) {
+ ASSERT_NE(XmlPullParser::Event::kBadDocument, parser.getEvent());
+ }
+
+ std::stringstream output;
+ ASSERT_TRUE(parser.writeToFile(output));
+
+ std::string result = output.str();
+ EXPECT_NE(std::string::npos,
+ result.find("<entries name=\"user\" type=\"com.android.test.User\"/>"));
+}
+
+TEST(BindingXmlPullParserTest, FailOnMissingNameOrTypeInVariableDeclaration) {
+ std::stringstream input;
+ input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n"
+ << " android:id=\"@+id/content\">\n"
+ << " <variable name=\"user\"/>\n"
+ << "</LinearLayout>\n";
+ std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
+ BindingXmlPullParser parser(sourceParser);
+
+ while (XmlPullParser::isGoodEvent(parser.next())) {}
+
+ EXPECT_EQ(XmlPullParser::Event::kBadDocument, parser.getEvent());
+ EXPECT_FALSE(parser.getLastError().empty());
+}
+
+
+} // namespace aapt
diff --git a/tools/aapt2/Files.cpp b/tools/aapt2/Files.cpp
index 349abbd..8484148 100644
--- a/tools/aapt2/Files.cpp
+++ b/tools/aapt2/Files.cpp
@@ -105,6 +105,17 @@ bool mkdirs(const StringPiece& path) {
return mkdirImpl(path) == 0 || errno == EEXIST;
}
+std::string getStem(const StringPiece& path) {
+ const char* start = path.begin();
+ const char* end = path.end();
+ for (const char* current = end - 1; current != start - 1; --current) {
+ if (*current == sDirSep) {
+ return std::string(start, current - start);
+ }
+ }
+ return {};
+}
+
bool FileFilter::setPattern(const StringPiece& pattern) {
mPatternTokens = util::splitAndLowercase(pattern, ':');
return true;
diff --git a/tools/aapt2/Files.h b/tools/aapt2/Files.h
index 37e6f8c..844fd2b 100644
--- a/tools/aapt2/Files.h
+++ b/tools/aapt2/Files.h
@@ -71,6 +71,11 @@ void appendPath(std::string* base, const StringPiece& part, const Ts&... parts);
*/
bool mkdirs(const StringPiece& path);
+/**
+ * Returns all but the last part of the path.
+ */
+std::string getStem(const StringPiece& path);
+
/*
* Filter that determines which resource files/directories are
* processed by AAPT. Takes a pattern string supplied by the user.
diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp
new file mode 100644
index 0000000..b1ee8e7
--- /dev/null
+++ b/tools/aapt2/Flag.cpp
@@ -0,0 +1,109 @@
+#include "Flag.h"
+#include "StringPiece.h"
+
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace flag {
+
+struct Flag {
+ std::string name;
+ std::string description;
+ std::function<void(const StringPiece&)> action;
+ bool required;
+ bool* flagResult;
+ bool parsed;
+};
+
+static std::vector<Flag> sFlags;
+static std::vector<std::string> sArgs;
+
+void optionalFlag(const StringPiece& name, const StringPiece& description,
+ std::function<void(const StringPiece&)> action) {
+ sFlags.push_back(
+ Flag{ name.toString(), description.toString(), action, false, nullptr, false });
+}
+
+void requiredFlag(const StringPiece& name, const StringPiece& description,
+ std::function<void(const StringPiece&)> action) {
+ sFlags.push_back(
+ Flag{ name.toString(), description.toString(), action, true, nullptr, false });
+}
+
+void optionalSwitch(const StringPiece& name, const StringPiece& description, bool* result) {
+ sFlags.push_back(
+ Flag{ name.toString(), description.toString(), {}, false, result, false });
+}
+
+static void usageAndDie(const StringPiece& command) {
+ std::cerr << command << " [options]";
+ for (const Flag& flag : sFlags) {
+ if (flag.required) {
+ std::cerr << " " << flag.name << " arg";
+ }
+ }
+ std::cerr << " files..." << std::endl << std::endl << "Options:" << std::endl;
+
+ for (const Flag& flag : sFlags) {
+ std::string command = flag.name;
+ if (!flag.flagResult) {
+ command += " arg ";
+ }
+ std::cerr << " " << std::setw(30) << std::left << command
+ << flag.description << std::endl;
+ }
+ exit(1);
+}
+
+void parse(int argc, char** argv, const StringPiece& command) {
+ for (int i = 0; i < argc; i++) {
+ const StringPiece arg(argv[i]);
+ if (*arg.data() != '-') {
+ sArgs.emplace_back(arg.toString());
+ continue;
+ }
+
+ bool match = false;
+ for (Flag& flag : sFlags) {
+ if (arg == flag.name) {
+ match = true;
+ flag.parsed = true;
+ if (flag.flagResult) {
+ *flag.flagResult = true;
+ } else {
+ i++;
+ if (i >= argc) {
+ std::cerr << flag.name << " missing argument." << std::endl
+ << std::endl;
+ usageAndDie(command);
+ }
+ flag.action(argv[i]);
+ }
+ break;
+ }
+ }
+
+ if (!match) {
+ std::cerr << "unknown option '" << arg << "'." << std::endl << std::endl;
+ usageAndDie(command);
+ }
+ }
+
+ for (const Flag& flag : sFlags) {
+ if (flag.required && !flag.parsed) {
+ std::cerr << "missing required flag " << flag.name << std::endl << std::endl;
+ usageAndDie(command);
+ }
+ }
+}
+
+const std::vector<std::string>& getArgs() {
+ return sArgs;
+}
+
+} // namespace flag
+} // namespace aapt
diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h
new file mode 100644
index 0000000..32f5f2c
--- /dev/null
+++ b/tools/aapt2/Flag.h
@@ -0,0 +1,28 @@
+#ifndef AAPT_FLAG_H
+#define AAPT_FLAG_H
+
+#include "StringPiece.h"
+
+#include <functional>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace flag {
+
+void requiredFlag(const StringPiece& name, const StringPiece& description,
+ std::function<void(const StringPiece&)> action);
+
+void optionalFlag(const StringPiece& name, const StringPiece& description,
+ std::function<void(const StringPiece&)> action);
+
+void optionalSwitch(const StringPiece& name, const StringPiece& description, bool* result);
+
+void parse(int argc, char** argv, const StringPiece& command);
+
+const std::vector<std::string>& getArgs();
+
+} // namespace flag
+} // namespace aapt
+
+#endif // AAPT_FLAG_H
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index cfc5874..0215a2b 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -17,11 +17,14 @@
#include "AppInfo.h"
#include "BigBuffer.h"
#include "BinaryResourceParser.h"
+#include "BindingXmlPullParser.h"
#include "Files.h"
+#include "Flag.h"
#include "JavaClassGenerator.h"
#include "Linker.h"
#include "ManifestParser.h"
#include "ManifestValidator.h"
+#include "Png.h"
#include "ResourceParser.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
@@ -41,6 +44,7 @@
#include <iostream>
#include <sstream>
#include <sys/stat.h>
+#include <utils/Errors.h>
using namespace aapt;
@@ -107,12 +111,12 @@ std::unique_ptr<FileReference> makeFileReference(StringPool& pool, const StringP
* Collect files from 'root', filtering out any files that do not
* match the FileFilter 'filter'.
*/
-bool walkTree(const StringPiece& root, const FileFilter& filter,
- std::vector<Source>& outEntries) {
+bool walkTree(const Source& root, const FileFilter& filter,
+ std::vector<Source>* outEntries) {
bool error = false;
- for (const std::string& dirName : listFiles(root)) {
- std::string dir(root.toString());
+ for (const std::string& dirName : listFiles(root.path)) {
+ std::string dir = root.path;
appendPath(&dir, dirName);
FileType ft = getFileType(dir);
@@ -134,13 +138,11 @@ bool walkTree(const StringPiece& root, const FileFilter& filter,
}
if (ft != FileType::kRegular) {
- Logger::error(Source{ file })
- << "not a regular file."
- << std::endl;
+ Logger::error(Source{ file }) << "not a regular file." << std::endl;
error = true;
continue;
}
- outEntries.emplace_back(Source{ file });
+ outEntries->push_back(Source{ file });
}
}
return !error;
@@ -171,9 +173,6 @@ bool loadBinaryResourceTable(std::shared_ptr<ResourceTable> table, const Source&
}
bool loadResTable(android::ResTable* table, const Source& source) {
- // For NO_ERROR (which on Windows is a MACRO).
- using namespace android;
-
std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
if (!ifs) {
Logger::error(source) << strerror(errno) << std::endl;
@@ -190,7 +189,7 @@ bool loadResTable(android::ResTable* table, const Source& source) {
char* buf = new char[dataSize];
ifs.read(buf, dataSize);
- bool result = table->add(buf, dataSize, -1, true) == NO_ERROR;
+ bool result = table->add(buf, dataSize, -1, true) == android::NO_ERROR;
delete [] buf;
return result;
@@ -287,8 +286,7 @@ void versionStylesForCompat(std::shared_ptr<ResourceTable> table) {
}
bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source,
- const ResourceName& name,
- const ConfigDescription& config) {
+ const ResourceName& name, const ConfigDescription& config) {
std::ifstream in(source.path, std::ifstream::binary);
if (!in) {
Logger::error(source) << strerror(errno) << std::endl;
@@ -297,14 +295,14 @@ bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source,
std::set<size_t> sdkLevels;
- SourceXmlPullParser pullParser(in);
- while (XmlPullParser::isGoodEvent(pullParser.next())) {
- if (pullParser.getEvent() != XmlPullParser::Event::kStartElement) {
+ SourceXmlPullParser parser(in);
+ while (XmlPullParser::isGoodEvent(parser.next())) {
+ if (parser.getEvent() != XmlPullParser::Event::kStartElement) {
continue;
}
- const auto endIter = pullParser.endAttributes();
- for (auto iter = pullParser.beginAttributes(); iter != endIter; ++iter) {
+ const auto endIter = parser.endAttributes();
+ for (auto iter = parser.beginAttributes(); iter != endIter; ++iter) {
if (iter->namespaceUri == u"http://schemas.android.com/apk/res/android") {
size_t sdkLevel = findAttributeSdkLevel(iter->name);
if (sdkLevel > 1) {
@@ -317,19 +315,12 @@ bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source,
bool privateRef = false;
if (ResourceParser::tryParseReference(iter->value, &refName, &create, &privateRef) &&
create) {
- table->addResource(refName, {}, source.line(pullParser.getLineNumber()),
+ table->addResource(refName, {}, source.line(parser.getLineNumber()),
util::make_unique<Id>());
}
}
}
- std::unique_ptr<FileReference> fileResource = makeFileReference(
- table->getValueStringPool(),
- util::utf16ToUtf8(name.entry) + ".xml",
- name.type,
- config);
- table->addResource(name, config, source.line(0), std::move(fileResource));
-
for (size_t level : sdkLevels) {
Logger::note(source)
<< "creating v" << level << " versioned file."
@@ -347,22 +338,29 @@ bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source,
return true;
}
-struct CompileXml {
+struct CompileItem {
Source source;
ResourceName name;
ConfigDescription config;
+ std::string extension;
};
-bool compileXml(std::shared_ptr<Resolver> resolver, const CompileXml& item,
- const Source& outputSource, std::queue<CompileXml>* queue) {
+bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item,
+ const Source& outputSource, std::queue<CompileItem>* queue) {
std::ifstream in(item.source.path, std::ifstream::binary);
if (!in) {
Logger::error(item.source) << strerror(errno) << std::endl;
return false;
}
- BigBuffer outBuffer(1024);
+ std::shared_ptr<BindingXmlPullParser> binding;
std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
+ if (item.name.type == ResourceType::kLayout) {
+ binding = std::make_shared<BindingXmlPullParser>(xmlParser);
+ xmlParser = binding;
+ }
+
+ BigBuffer outBuffer(1024);
XmlFlattener flattener(resolver);
// We strip attributes that do not belong in this version of the resource.
@@ -376,7 +374,7 @@ bool compileXml(std::shared_ptr<Resolver> resolver, const CompileXml& item,
if (minStrippedSdk.value() > 0) {
// Something was stripped, so let's generate a new file
// with the version of the smallest SDK version stripped.
- CompileXml newWork = item;
+ CompileItem newWork = item;
newWork.config.sdkVersion = minStrippedSdk.value();
queue->push(newWork);
}
@@ -391,12 +389,70 @@ bool compileXml(std::shared_ptr<Resolver> resolver, const CompileXml& item,
Logger::error(outputSource) << strerror(errno) << std::endl;
return false;
}
+
+ if (binding) {
+ // We generated a binding xml file, write it out beside the output file.
+ Source bindingOutput = outputSource;
+ bindingOutput.path += ".bind.xml";
+ std::ofstream bout(bindingOutput.path);
+ if (!bout) {
+ Logger::error(bindingOutput) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ if (!binding->writeToFile(bout)) {
+ Logger::error(bindingOutput) << strerror(errno) << std::endl;
+ return false;
+ }
+ }
+ return true;
+}
+
+bool compilePng(const Source& source, const Source& output) {
+ std::ifstream in(source.path, std::ifstream::binary);
+ if (!in) {
+ Logger::error(source) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ std::ofstream out(output.path, std::ofstream::binary);
+ if (!out) {
+ Logger::error(output) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ std::string err;
+ Png png;
+ if (!png.process(source, in, out, {}, &err)) {
+ Logger::error(source) << err << std::endl;
+ return false;
+ }
return true;
}
+bool copyFile(const Source& source, const Source& output) {
+ std::ifstream in(source.path, std::ifstream::binary);
+ if (!in) {
+ Logger::error(source) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ std::ofstream out(output.path, std::ofstream::binary);
+ if (!out) {
+ Logger::error(output) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ if (out << in.rdbuf()) {
+ Logger::error(output) << strerror(errno) << std::endl;
+ return true;
+ }
+ return false;
+}
+
struct AaptOptions {
enum class Phase {
- LegacyFull,
+ Full,
Collect,
Link,
Compile,
@@ -411,16 +467,26 @@ struct AaptOptions {
// The location of the manifest file.
Source manifest;
- // The files to process.
- std::vector<Source> sources;
+ // The source directories to walk and find resource files.
+ std::vector<Source> sourceDirs;
+
+ // The resource files to process and collect.
+ std::vector<Source> collectFiles;
+
+ // The binary table files to link.
+ std::vector<Source> linkFiles;
+
+ // The resource files to compile.
+ std::vector<Source> compileFiles;
// The libraries these files may reference.
std::vector<Source> libraries;
- // Output directory.
+ // Output path. This can be a directory or file
+ // depending on the phase.
Source output;
- // Whether to generate a Java Class.
+ // Directory to in which to generate R.java.
Maybe<Source> generateJavaClass;
// Whether to output verbose details about
@@ -428,9 +494,8 @@ struct AaptOptions {
bool verbose = false;
};
-bool compileAndroidManifest(std::shared_ptr<Resolver> resolver, const AaptOptions& options) {
- using namespace android;
-
+bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver,
+ const AaptOptions& options) {
Source outSource = options.output;
appendPath(&outSource.path, "AndroidManifest.xml");
@@ -461,8 +526,8 @@ bool compileAndroidManifest(std::shared_ptr<Resolver> resolver, const AaptOption
p += b.size;
}
- ResXMLTree tree;
- if (tree.setTo(data.get(), outBuffer.size()) != NO_ERROR) {
+ android::ResXMLTree tree;
+ if (tree.setTo(data.get(), outBuffer.size()) != android::NO_ERROR) {
return false;
}
@@ -496,246 +561,117 @@ bool loadAppInfo(const Source& source, AppInfo* outInfo) {
return parser.parse(source, pullParser, outInfo);
}
-/**
- * Parses legacy options and walks the source directories collecting
- * files to process.
- */
-bool prepareLegacy(std::vector<StringPiece>::const_iterator argsIter,
- const std::vector<StringPiece>::const_iterator argsEndIter,
- AaptOptions &options) {
- options.phase = AaptOptions::Phase::LegacyFull;
-
- std::vector<StringPiece> sourceDirs;
- while (argsIter != argsEndIter) {
- if (*argsIter == "-S") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-S missing argument." << std::endl;
- return false;
- }
- sourceDirs.push_back(*argsIter);
- } else if (*argsIter == "-I") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-I missing argument." << std::endl;
- return false;
- }
- options.libraries.push_back(Source{ argsIter->toString() });
- } else if (*argsIter == "-M") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-M missing argument." << std::endl;
- return false;
- }
+static AaptOptions prepareArgs(int argc, char** argv) {
+ if (argc < 2) {
+ std::cerr << "no command specified." << std::endl;
+ exit(1);
+ }
- if (!options.manifest.path.empty()) {
- Logger::error() << "multiple -M flags are not allowed." << std::endl;
- return false;
- }
- options.manifest.path = argsIter->toString();
- } else if (*argsIter == "-o") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-o missing argument." << std::endl;
- return false;
- }
- options.output = Source{ argsIter->toString() };
- } else if (*argsIter == "-J") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-J missing argument." << std::endl;
- return false;
- }
- options.generateJavaClass = make_value<Source>(Source{ argsIter->toString() });
- } else if (*argsIter == "-v") {
- options.verbose = true;
- } else {
- Logger::error() << "unrecognized option '" << *argsIter << "'." << std::endl;
- return false;
- }
+ const StringPiece command(argv[1]);
+ argc -= 2;
+ argv += 2;
- ++argsIter;
- }
+ AaptOptions options;
- if (options.manifest.path.empty()) {
- Logger::error() << "must specify manifest file with -M." << std::endl;
- return false;
+ StringPiece outputDescription = "place output in file";
+ if (command == "package") {
+ options.phase = AaptOptions::Phase::Full;
+ outputDescription = "place output in directory";
+ } else if (command == "collect") {
+ options.phase = AaptOptions::Phase::Collect;
+ } else if (command == "link") {
+ options.phase = AaptOptions::Phase::Link;
+ } else if (command == "compile") {
+ options.phase = AaptOptions::Phase::Compile;
+ outputDescription = "place output in directory";
+ } else {
+ std::cerr << "invalid command '" << command << "'." << std::endl;
+ exit(1);
}
- // Load the App's package name, etc.
- if (!loadAppInfo(options.manifest, &options.appInfo)) {
- return false;
- }
+ if (options.phase == AaptOptions::Phase::Full) {
+ flag::requiredFlag("-S", "add a directory in which to find resources",
+ [&options](const StringPiece& arg) {
+ options.sourceDirs.push_back(Source{ arg.toString() });
+ });
+
+ flag::requiredFlag("-M", "path to AndroidManifest.xml",
+ [&options](const StringPiece& arg) {
+ options.manifest = Source{ arg.toString() };
+ });
+
+ flag::optionalFlag("-I", "add an Android APK to link against",
+ [&options](const StringPiece& arg) {
+ options.libraries.push_back(Source{ arg.toString() });
+ });
+
+ flag::optionalFlag("--java", "directory in which to generate R.java",
+ [&options](const StringPiece& arg) {
+ options.generateJavaClass = Source{ arg.toString() };
+ });
- /**
- * Set up the file filter to ignore certain files.
- */
- const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
- FileFilter fileFilter;
- if (customIgnore && customIgnore[0]) {
- fileFilter.setPattern(customIgnore);
} else {
- fileFilter.setPattern(
- "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
- }
+ flag::requiredFlag("--package", "Android package name",
+ [&options](const StringPiece& arg) {
+ options.appInfo.package = util::utf8ToUtf16(arg);
+ });
- /*
- * Enumerate the files in each source directory.
- */
- for (const StringPiece& source : sourceDirs) {
- if (!walkTree(source, fileFilter, options.sources)) {
- return false;
+ if (options.phase != AaptOptions::Phase::Collect) {
+ flag::optionalFlag("-I", "add an Android APK to link against",
+ [&options](const StringPiece& arg) {
+ options.libraries.push_back(Source{ arg.toString() });
+ });
}
- }
- return true;
-}
-
-bool prepareCollect(std::vector<StringPiece>::const_iterator argsIter,
- const std::vector<StringPiece>::const_iterator argsEndIter,
- AaptOptions& options) {
- options.phase = AaptOptions::Phase::Collect;
- while (argsIter != argsEndIter) {
- if (*argsIter == "--package") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "--package missing argument." << std::endl;
- return false;
- }
- options.appInfo.package = util::utf8ToUtf16(*argsIter);
- } else if (*argsIter == "-o") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-o missing argument." << std::endl;
- return false;
- }
- options.output = Source{ argsIter->toString() };
- } else if (*argsIter == "-v") {
- options.verbose = true;
- } else if (argsIter->data()[0] != '-') {
- options.sources.push_back(Source{ argsIter->toString() });
- } else {
- Logger::error()
- << "unknown option '"
- << *argsIter
- << "'."
- << std::endl;
- return false;
+ if (options.phase == AaptOptions::Phase::Link) {
+ flag::optionalFlag("--java", "directory in which to generate R.java",
+ [&options](const StringPiece& arg) {
+ options.generateJavaClass = Source{ arg.toString() };
+ });
}
- ++argsIter;
}
- return true;
-}
-bool prepareLink(std::vector<StringPiece>::const_iterator argsIter,
- const std::vector<StringPiece>::const_iterator argsEndIter,
- AaptOptions& options) {
- options.phase = AaptOptions::Phase::Link;
+ // Common flags for all steps.
+ flag::requiredFlag("-o", outputDescription, [&options](const StringPiece& arg) {
+ options.output = Source{ arg.toString() };
+ });
+ flag::optionalSwitch("-v", "enables verbose logging", &options.verbose);
- while (argsIter != argsEndIter) {
- if (*argsIter == "--package") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "--package missing argument." << std::endl;
- return false;
- }
- options.appInfo.package = util::utf8ToUtf16(*argsIter);
- } else if (*argsIter == "-o") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-o missing argument." << std::endl;
- return false;
- }
- options.output = Source{ argsIter->toString() };
- } else if (*argsIter == "-I") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-I missing argument." << std::endl;
- return false;
- }
- options.libraries.push_back(Source{ argsIter->toString() });
- } else if (*argsIter == "--java") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "--java missing argument." << std::endl;
- return false;
- }
- options.generateJavaClass = make_value<Source>(Source{ argsIter->toString() });
- } else if (*argsIter == "-v") {
- options.verbose = true;
- } else if (argsIter->data()[0] != '-') {
- options.sources.push_back(Source{ argsIter->toString() });
- } else {
- Logger::error()
- << "unknown option '"
- << *argsIter
- << "'."
- << std::endl;
- return false;
- }
- ++argsIter;
- }
- return true;
-}
+ // Build the command string for output (eg. "aapt2 compile").
+ std::string fullCommand = "aapt2";
+ fullCommand += " ";
+ fullCommand += command.toString();
-bool prepareCompile(std::vector<StringPiece>::const_iterator argsIter,
- const std::vector<StringPiece>::const_iterator argsEndIter,
- AaptOptions& options) {
- options.phase = AaptOptions::Phase::Compile;
+ // Actually read the command line flags.
+ flag::parse(argc, argv, fullCommand);
- while (argsIter != argsEndIter) {
- if (*argsIter == "--package") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "--package missing argument." << std::endl;
- return false;
- }
- options.appInfo.package = util::utf8ToUtf16(*argsIter);
- } else if (*argsIter == "-o") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-o missing argument." << std::endl;
- return false;
- }
- options.output = Source{ argsIter->toString() };
- } else if (*argsIter == "-I") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-I missing argument." << std::endl;
- return false;
- }
- options.libraries.push_back(Source{ argsIter->toString() });
- } else if (*argsIter == "-v") {
- options.verbose = true;
- } else if (argsIter->data()[0] != '-') {
- options.sources.push_back(Source{ argsIter->toString() });
- } else {
- Logger::error()
- << "unknown option '"
- << *argsIter
- << "'."
- << std::endl;
- return false;
+ // Copy all the remaining arguments.
+ if (options.phase == AaptOptions::Phase::Collect) {
+ for (const std::string& arg : flag::getArgs()) {
+ options.collectFiles.push_back(Source{ arg });
+ }
+ } else if (options.phase == AaptOptions::Phase::Compile) {
+ for (const std::string& arg : flag::getArgs()) {
+ options.compileFiles.push_back(Source{ arg });
+ }
+ } else if (options.phase == AaptOptions::Phase::Link) {
+ for (const std::string& arg : flag::getArgs()) {
+ options.linkFiles.push_back(Source{ arg });
}
- ++argsIter;
}
- return true;
+ return options;
}
-struct CollectValuesItem {
- Source source;
- ConfigDescription config;
-};
-
-bool collectValues(std::shared_ptr<ResourceTable> table, const CollectValuesItem& item) {
- std::ifstream in(item.source.path, std::ifstream::binary);
+static bool collectValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
+ const ConfigDescription& config) {
+ std::ifstream in(source.path, std::ifstream::binary);
if (!in) {
- Logger::error(item.source) << strerror(errno) << std::endl;
+ Logger::error(source) << strerror(errno) << std::endl;
return false;
}
std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
- ResourceParser parser(table, item.source, item.config, xmlParser);
+ ResourceParser parser(table, source, config, xmlParser);
return parser.parse();
}
@@ -750,7 +686,7 @@ struct ResourcePathData {
* Resource file paths are expected to look like:
* [--/res/]type[-config]/name
*/
-Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
+static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
if (parts.size() < 2) {
Logger::error(source) << "bad resource path." << std::endl;
@@ -792,16 +728,58 @@ Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
};
}
-static bool doLegacy(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
- const AaptOptions& options) {
+bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<Resolver>& resolver) {
+ const bool versionStyles = (options->phase == AaptOptions::Phase::Full ||
+ options->phase == AaptOptions::Phase::Link);
+ const bool verifyNoMissingSymbols = (options->phase == AaptOptions::Phase::Full ||
+ options->phase == AaptOptions::Phase::Link);
+ const bool compileFiles = (options->phase == AaptOptions::Phase::Full ||
+ options->phase == AaptOptions::Phase::Compile);
+ const bool flattenTable = (options->phase == AaptOptions::Phase::Full ||
+ options->phase == AaptOptions::Phase::Collect ||
+ options->phase == AaptOptions::Phase::Link);
+ const bool useExtendedChunks = options->phase == AaptOptions::Phase::Collect;
+
+ // Build the output table path.
+ Source outputTable = options->output;
+ if (options->phase == AaptOptions::Phase::Full) {
+ appendPath(&outputTable.path, "resources.arsc");
+ }
+
bool error = false;
- std::queue<CompileXml> xmlCompileQueue;
+ std::queue<CompileItem> compileQueue;
+
+ // If source directories were specified, walk them looking for resource files.
+ if (!options->sourceDirs.empty()) {
+ const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
+ FileFilter fileFilter;
+ if (customIgnore && customIgnore[0]) {
+ fileFilter.setPattern(customIgnore);
+ } else {
+ fileFilter.setPattern(
+ "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
+ }
- //
- // Read values XML files and XML/PNG files.
+ for (const Source& source : options->sourceDirs) {
+ if (!walkTree(source, fileFilter, &options->collectFiles)) {
+ return false;
+ }
+ }
+ }
+
+ // Load all binary resource tables.
+ for (const Source& source : options->linkFiles) {
+ error |= !loadBinaryResourceTable(table, source);
+ }
+
+ if (error) {
+ return false;
+ }
+
+ // Collect all the resource files.
// Need to parse the resource type/config/filename.
- //
- for (const Source& source : options.sources) {
+ for (const Source& source : options->collectFiles) {
Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
if (!maybePathData) {
return false;
@@ -809,351 +787,154 @@ static bool doLegacy(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resol
const ResourcePathData& pathData = maybePathData.value();
if (pathData.resourceDir == u"values") {
- if (options.verbose) {
+ if (options->verbose) {
Logger::note(source) << "collecting values..." << std::endl;
}
- error |= !collectValues(table, CollectValuesItem{ source, pathData.config });
+ error |= !collectValues(table, source, pathData.config);
continue;
}
const ResourceType* type = parseResourceType(pathData.resourceDir);
if (!type) {
- Logger::error(source)
- << "invalid resource type '"
- << pathData.resourceDir
- << "'."
- << std::endl;
+ Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
+ << std::endl;
return false;
}
ResourceName resourceName = { table->getPackage(), *type, pathData.name };
- if (pathData.extension == "xml") {
- if (options.verbose) {
- Logger::note(source) << "collecting XML..." << std::endl;
- }
- error |= !collectXml(table, source, resourceName, pathData.config);
- xmlCompileQueue.push(CompileXml{
- source,
- resourceName,
- pathData.config
- });
- } else {
- std::unique_ptr<FileReference> fileReference = makeFileReference(
- table->getValueStringPool(),
- util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
- *type, pathData.config);
+ // Add the file name to the resource table.
+ std::unique_ptr<FileReference> fileReference = makeFileReference(
+ table->getValueStringPool(),
+ util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
+ *type, pathData.config);
+ error |= !table->addResource(resourceName, pathData.config, source.line(0),
+ std::move(fileReference));
- error |= !table->addResource(resourceName, pathData.config, source.line(0),
- std::move(fileReference));
+ if (pathData.extension == "xml") {
+ error |= !collectXml(table, source, resourceName, pathData.config);
}
+
+ compileQueue.push(
+ CompileItem{ source, resourceName, pathData.config, pathData.extension });
}
if (error) {
return false;
}
- versionStylesForCompat(table);
+ // Version all styles referencing attributes outside of their specified SDK version.
+ if (versionStyles) {
+ versionStylesForCompat(table);
+ }
- //
- // Verify all references and data types.
- //
+ // Verify that all references are valid.
Linker linker(table, resolver);
if (!linker.linkAndValidate()) {
- Logger::error()
- << "linking failed."
- << std::endl;
return false;
}
- const auto& unresolvedRefs = linker.getUnresolvedReferences();
- if (!unresolvedRefs.empty()) {
- for (const auto& entry : unresolvedRefs) {
- for (const auto& source : entry.second) {
- Logger::error(source)
- << "unresolved symbol '"
- << entry.first
- << "'."
- << std::endl;
+ // Verify that all symbols exist.
+ if (verifyNoMissingSymbols) {
+ const auto& unresolvedRefs = linker.getUnresolvedReferences();
+ if (!unresolvedRefs.empty()) {
+ for (const auto& entry : unresolvedRefs) {
+ for (const auto& source : entry.second) {
+ Logger::error(source) << "unresolved symbol '" << entry.first << "'."
+ << std::endl;
+ }
}
- }
- return false;
- }
-
- //
- // Compile the XML files.
- //
- while (!xmlCompileQueue.empty()) {
- const CompileXml& item = xmlCompileQueue.front();
-
- // Create the output path from the resource name.
- std::stringstream outputPath;
- outputPath << item.name.type;
- if (item.config != ConfigDescription{}) {
- outputPath << "-" << item.config.toString();
- }
-
- Source outSource = options.output;
- appendPath(&outSource.path, "res");
- appendPath(&outSource.path, outputPath.str());
-
- if (!mkdirs(outSource.path)) {
- Logger::error(outSource) << strerror(errno) << std::endl;
- return false;
- }
-
- appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + ".xml");
-
- if (options.verbose) {
- Logger::note(outSource) << "compiling XML file." << std::endl;
- }
-
- error |= !compileXml(resolver, item, outSource, &xmlCompileQueue);
- xmlCompileQueue.pop();
- }
-
- if (error) {
- return false;
- }
-
- //
- // Compile the AndroidManifest.xml file.
- //
- if (!compileAndroidManifest(resolver, options)) {
- return false;
- }
-
- //
- // Generate the Java R class.
- //
- if (options.generateJavaClass) {
- Source outPath = options.generateJavaClass.value();
- if (options.verbose) {
- Logger::note()
- << "writing symbols to "
- << outPath
- << "."
- << std::endl;
- }
-
- for (std::string& part : util::split(util::utf16ToUtf8(table->getPackage()), '.')) {
- appendPath(&outPath.path, part);
- }
-
- if (!mkdirs(outPath.path)) {
- Logger::error(outPath) << strerror(errno) << std::endl;
- return false;
- }
-
- appendPath(&outPath.path, "R.java");
-
- std::ofstream fout(outPath.path);
- if (!fout) {
- Logger::error(outPath) << strerror(errno) << std::endl;
- return false;
- }
-
- JavaClassGenerator generator(table, JavaClassGenerator::Options{});
- if (!generator.generate(fout)) {
- Logger::error(outPath)
- << generator.getError()
- << "."
- << std::endl;
return false;
}
}
- //
- // Flatten resource table.
- //
- if (table->begin() != table->end()) {
- BigBuffer buffer(1024);
- TableFlattener::Options tableOptions;
- tableOptions.useExtendedChunks = false;
- TableFlattener flattener(tableOptions);
- if (!flattener.flatten(&buffer, *table)) {
- Logger::error()
- << "failed to flatten resource table->"
- << std::endl;
- return false;
- }
-
- if (options.verbose) {
- Logger::note()
- << "Final resource table size="
- << util::formatSize(buffer.size())
- << std::endl;
- }
-
- std::string outTable(options.output.path);
- appendPath(&outTable, "resources.arsc");
+ // Compile files.
+ if (compileFiles) {
+ // First process any input compile files.
+ for (const Source& source : options->compileFiles) {
+ Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+ if (!maybePathData) {
+ return false;
+ }
- std::ofstream fout(outTable, std::ofstream::binary);
- if (!fout) {
- Logger::error(Source{outTable})
- << strerror(errno)
- << "."
- << std::endl;
- return false;
- }
+ const ResourcePathData& pathData = maybePathData.value();
+ const ResourceType* type = parseResourceType(pathData.resourceDir);
+ if (!type) {
+ Logger::error(source) << "invalid resource type '" << pathData.resourceDir
+ << "'." << std::endl;
+ return false;
+ }
- if (!util::writeAll(fout, buffer)) {
- Logger::error(Source{outTable})
- << strerror(errno)
- << "."
- << std::endl;
- return false;
+ ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+ compileQueue.push(
+ CompileItem{ source, resourceName, pathData.config, pathData.extension });
}
- fout.flush();
- }
- return true;
-}
-static bool doCollect(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
- const AaptOptions& options) {
- bool error = false;
+ // Now process the actual compile queue.
+ for (; !compileQueue.empty(); compileQueue.pop()) {
+ const CompileItem& item = compileQueue.front();
- //
- // Read values XML files and XML/PNG files.
- // Need to parse the resource type/config/filename.
- //
- for (const Source& source : options.sources) {
- Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
- if (!maybePathData) {
- return false;
- }
-
- const ResourcePathData& pathData = maybePathData.value();
- if (pathData.resourceDir == u"values") {
- if (options.verbose) {
- Logger::note(source) << "collecting values..." << std::endl;
+ // Create the output directory path from the resource type and config.
+ std::stringstream outputPath;
+ outputPath << item.name.type;
+ if (item.config != ConfigDescription{}) {
+ outputPath << "-" << item.config.toString();
}
- error |= !collectValues(table, CollectValuesItem{ source, pathData.config });
- continue;
- }
-
- const ResourceType* type = parseResourceType(pathData.resourceDir);
- if (!type) {
- Logger::error(source)
- << "invalid resource type '"
- << pathData.resourceDir
- << "'."
- << std::endl;
- return false;
- }
+ Source outSource = options->output;
+ appendPath(&outSource.path, "res");
+ appendPath(&outSource.path, outputPath.str());
- ResourceName resourceName = { table->getPackage(), *type, pathData.name };
- if (pathData.extension == "xml") {
- if (options.verbose) {
- Logger::note(source) << "collecting XML..." << std::endl;
+ // Make the directory.
+ if (!mkdirs(outSource.path)) {
+ Logger::error(outSource) << strerror(errno) << std::endl;
+ return false;
}
- error |= !collectXml(table, source, resourceName, pathData.config);
- } else {
- std::unique_ptr<FileReference> fileReference = makeFileReference(
- table->getValueStringPool(),
- util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
- *type,
- pathData.config);
- error |= !table->addResource(resourceName, pathData.config, source.line(0),
- std::move(fileReference));
- }
- }
+ // Add the file name to the directory path.
+ appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + "." + item.extension);
- if (error) {
- return false;
- }
-
- Linker linker(table, resolver);
- if (!linker.linkAndValidate()) {
- return false;
- }
+ if (item.extension == "xml") {
+ if (options->verbose) {
+ Logger::note(outSource) << "compiling XML file." << std::endl;
+ }
- //
- // Flatten resource table->
- //
- if (table->begin() != table->end()) {
- BigBuffer buffer(1024);
- TableFlattener::Options tableOptions;
- tableOptions.useExtendedChunks = true;
- TableFlattener flattener(tableOptions);
- if (!flattener.flatten(&buffer, *table)) {
- Logger::error()
- << "failed to flatten resource table->"
- << std::endl;
- return false;
- }
+ error |= !compileXml(resolver, item, outSource, &compileQueue);
+ } else if (item.extension == "png" || item.extension == "9.png") {
+ if (options->verbose) {
+ Logger::note(outSource) << "compiling png file." << std::endl;
+ }
- std::ofstream fout(options.output.path, std::ofstream::binary);
- if (!fout) {
- Logger::error(options.output)
- << strerror(errno)
- << "."
- << std::endl;
- return false;
+ error |= !compilePng(item.source, outSource);
+ } else {
+ error |= !copyFile(item.source, outSource);
+ }
}
- if (!util::writeAll(fout, buffer)) {
- Logger::error(options.output)
- << strerror(errno)
- << "."
- << std::endl;
+ if (error) {
return false;
}
- fout.flush();
- }
- return true;
-}
-
-static bool doLink(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
- const AaptOptions& options) {
- bool error = false;
-
- for (const Source& source : options.sources) {
- error |= !loadBinaryResourceTable(table, source);
- }
-
- if (error) {
- return false;
}
- versionStylesForCompat(table);
-
- Linker linker(table, resolver);
- if (!linker.linkAndValidate()) {
- return false;
- }
-
- const auto& unresolvedRefs = linker.getUnresolvedReferences();
- if (!unresolvedRefs.empty()) {
- for (const auto& entry : unresolvedRefs) {
- for (const auto& source : entry.second) {
- Logger::error(source)
- << "unresolved symbol '"
- << entry.first
- << "'."
- << std::endl;
- }
+ // Compile and validate the AndroidManifest.xml.
+ if (!options->manifest.path.empty()) {
+ if (!compileAndroidManifest(resolver, *options)) {
+ return false;
}
- return false;
}
- //
- // Generate the Java R class.
- //
- if (options.generateJavaClass) {
- Source outPath = options.generateJavaClass.value();
- if (options.verbose) {
- Logger::note()
- << "writing symbols to "
- << outPath
- << "."
- << std::endl;
+ // Generate the Java class file.
+ if (options->generateJavaClass) {
+ Source outPath = options->generateJavaClass.value();
+ if (options->verbose) {
+ Logger::note() << "writing symbols to " << outPath << "." << std::endl;
}
- for (std::string& part : util::split(util::utf16ToUtf8(table->getPackage()), '.')) {
+ // Build the output directory from the package name.
+ // Eg. com.android.app -> com/android/app
+ const std::string packageUtf8 = util::utf16ToUtf8(table->getPackage());
+ for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
appendPath(&outPath.path, part);
}
@@ -1170,52 +951,37 @@ static bool doLink(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolve
return false;
}
- JavaClassGenerator generator(table, JavaClassGenerator::Options{});
+ JavaClassGenerator generator(table, {});
if (!generator.generate(fout)) {
- Logger::error(outPath)
- << generator.getError()
- << "."
- << std::endl;
+ Logger::error(outPath) << generator.getError() << "." << std::endl;
return false;
}
}
- //
- // Flatten resource table.
- //
- if (table->begin() != table->end()) {
+ // Flatten the resource table.
+ if (flattenTable && table->begin() != table->end()) {
BigBuffer buffer(1024);
TableFlattener::Options tableOptions;
- tableOptions.useExtendedChunks = false;
+ tableOptions.useExtendedChunks = useExtendedChunks;
TableFlattener flattener(tableOptions);
if (!flattener.flatten(&buffer, *table)) {
- Logger::error()
- << "failed to flatten resource table->"
- << std::endl;
+ Logger::error() << "failed to flatten resource table." << std::endl;
return false;
}
- if (options.verbose) {
- Logger::note()
- << "Final resource table size="
- << util::formatSize(buffer.size())
- << std::endl;
+ if (options->verbose) {
+ Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
+ << std::endl;
}
- std::ofstream fout(options.output.path, std::ofstream::binary);
+ std::ofstream fout(outputTable.path, std::ofstream::binary);
if (!fout) {
- Logger::error(options.output)
- << strerror(errno)
- << "."
- << std::endl;
+ Logger::error(outputTable) << strerror(errno) << "." << std::endl;
return false;
}
if (!util::writeAll(fout, buffer)) {
- Logger::error(options.output)
- << strerror(errno)
- << "."
- << std::endl;
+ Logger::error(outputTable) << strerror(errno) << "." << std::endl;
return false;
}
fout.flush();
@@ -1223,134 +989,24 @@ static bool doLink(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolve
return true;
}
-static bool doCompile(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
- const AaptOptions& options) {
- std::queue<CompileXml> xmlCompileQueue;
-
- for (const Source& source : options.sources) {
- Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
- if (!maybePathData) {
- return false;
- }
-
- ResourcePathData& pathData = maybePathData.value();
- const ResourceType* type = parseResourceType(pathData.resourceDir);
- if (!type) {
- Logger::error(source)
- << "invalid resource type '"
- << pathData.resourceDir
- << "'."
- << std::endl;
- return false;
- }
-
- ResourceName resourceName = { table->getPackage(), *type, pathData.name };
- if (pathData.extension == "xml") {
- xmlCompileQueue.push(CompileXml{
- source,
- resourceName,
- pathData.config
- });
- } else {
- // TODO(adamlesinski): Handle images here.
- }
- }
-
- bool error = false;
- while (!xmlCompileQueue.empty()) {
- const CompileXml& item = xmlCompileQueue.front();
-
- // Create the output path from the resource name.
- std::stringstream outputPath;
- outputPath << item.name.type;
- if (item.config != ConfigDescription{}) {
- outputPath << "-" << item.config.toString();
- }
-
- Source outSource = options.output;
- appendPath(&outSource.path, "res");
- appendPath(&outSource.path, outputPath.str());
-
- if (!mkdirs(outSource.path)) {
- Logger::error(outSource) << strerror(errno) << std::endl;
- return false;
- }
-
- appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + ".xml");
-
- if (options.verbose) {
- Logger::note(outSource) << "compiling XML file." << std::endl;
- }
-
- error |= !compileXml(resolver, item, outSource, &xmlCompileQueue);
- xmlCompileQueue.pop();
- }
- return !error;
-}
-
int main(int argc, char** argv) {
Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
+ AaptOptions options = prepareArgs(argc, argv);
- std::vector<StringPiece> args;
- args.reserve(argc - 1);
- for (int i = 1; i < argc; i++) {
- args.emplace_back(argv[i], strlen(argv[i]));
- }
-
- if (args.empty()) {
- Logger::error() << "no command specified." << std::endl;
- return 1;
- }
-
- AaptOptions options;
-
- // Check the command we're running.
- const StringPiece& command = args.front();
- if (command == "package") {
- if (!prepareLegacy(std::begin(args) + 1, std::end(args), options)) {
- return 1;
- }
- } else if (command == "collect") {
- if (!prepareCollect(std::begin(args) + 1, std::end(args), options)) {
- return 1;
- }
- } else if (command == "link") {
- if (!prepareLink(std::begin(args) + 1, std::end(args), options)) {
- return 1;
- }
- } else if (command == "compile") {
- if (!prepareCompile(std::begin(args) + 1, std::end(args), options)) {
- return 1;
+ // If we specified a manifest, go ahead and load the package name from the manifest.
+ if (!options.manifest.path.empty()) {
+ if (!loadAppInfo(options.manifest, &options.appInfo)) {
+ return false;
}
- } else {
- Logger::error() << "unknown command '" << command << "'." << std::endl;
- return 1;
}
- //
// Verify we have some common options set.
- //
-
- if (options.sources.empty()) {
- Logger::error() << "no sources specified." << std::endl;
- return false;
- }
-
- if (options.output.path.empty()) {
- Logger::error() << "no output directory specified." << std::endl;
- return false;
- }
-
if (options.appInfo.package.empty()) {
Logger::error() << "no package name specified." << std::endl;
return false;
}
-
- //
- // Every phase needs a resource table and a resolver/linker.
- //
-
+ // Every phase needs a resource table.
std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
table->setPackage(options.appInfo.package);
if (options.appInfo.package == u"android") {
@@ -1359,12 +1015,10 @@ int main(int argc, char** argv) {
table->setPackageId(0x7f);
}
- //
// Load the included libraries.
- //
std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
for (const Source& source : options.libraries) {
- if (util::stringEndsWith(source.path, ".arsc")) {
+ if (util::stringEndsWith<char>(source.path, ".arsc")) {
// We'll process these last so as to avoid a cookie issue.
continue;
}
@@ -1377,7 +1031,7 @@ int main(int argc, char** argv) {
}
for (const Source& source : options.libraries) {
- if (!util::stringEndsWith(source.path, ".arsc")) {
+ if (!util::stringEndsWith<char>(source.path, ".arsc")) {
// We've already processed this.
continue;
}
@@ -1393,33 +1047,9 @@ int main(int argc, char** argv) {
// Make the resolver that will cache IDs for us.
std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
- //
- // Dispatch to the real phase here.
- //
-
- bool result = true;
- switch (options.phase) {
- case AaptOptions::Phase::LegacyFull:
- result = doLegacy(table, resolver, options);
- break;
-
- case AaptOptions::Phase::Collect:
- result = doCollect(table, resolver, options);
- break;
-
- case AaptOptions::Phase::Link:
- result = doLink(table, resolver, options);
- break;
-
- case AaptOptions::Phase::Compile:
- result = doCompile(table, resolver, options);
- break;
- }
-
- if (!result) {
- Logger::error()
- << "aapt exiting with failures."
- << std::endl;
+ // Do the work.
+ if (!doAll(&options, table, resolver)) {
+ Logger::error() << "aapt exiting with failures." << std::endl;
return 1;
}
return 0;
diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/Png.cpp
new file mode 100644
index 0000000..76120ac
--- /dev/null
+++ b/tools/aapt2/Png.cpp
@@ -0,0 +1,1284 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Logger.h"
+#include "Png.h"
+#include "Source.h"
+#include "Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <iostream>
+#include <png.h>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <zlib.h>
+
+namespace aapt {
+
+constexpr bool kDebug = false;
+constexpr size_t kPngSignatureSize = 8u;
+
+struct PngInfo {
+ ~PngInfo() {
+ for (png_bytep row : rows) {
+ if (row != nullptr) {
+ delete[] row;
+ }
+ }
+
+ delete[] xDivs;
+ delete[] yDivs;
+ }
+
+ void* serialize9Patch() {
+ void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, yDivs,
+ colors.data());
+ reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
+ return serialized;
+ }
+
+ uint32_t width = 0;
+ uint32_t height = 0;
+ std::vector<png_bytep> rows;
+
+ bool is9Patch = false;
+ android::Res_png_9patch info9Patch;
+ int32_t* xDivs = nullptr;
+ int32_t* yDivs = nullptr;
+ std::vector<uint32_t> colors;
+
+ // Layout padding.
+ bool haveLayoutBounds = false;
+ int32_t layoutBoundsLeft;
+ int32_t layoutBoundsTop;
+ int32_t layoutBoundsRight;
+ int32_t layoutBoundsBottom;
+
+ // Round rect outline description.
+ int32_t outlineInsetsLeft;
+ int32_t outlineInsetsTop;
+ int32_t outlineInsetsRight;
+ int32_t outlineInsetsBottom;
+ float outlineRadius;
+ uint8_t outlineAlpha;
+};
+
+static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) {
+ std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
+ if (!input->read(reinterpret_cast<char*>(data), length)) {
+ png_error(readPtr, strerror(errno));
+ }
+}
+
+static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) {
+ std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr));
+ if (!output->write(reinterpret_cast<const char*>(data), length)) {
+ png_error(writePtr, strerror(errno));
+ }
+}
+
+static void flushDataToStream(png_structp writePtr) {
+ std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr));
+ if (!output->flush()) {
+ png_error(writePtr, strerror(errno));
+ }
+}
+
+static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
+ SourceLogger* logger = reinterpret_cast<SourceLogger*>(png_get_error_ptr(readPtr));
+ logger->warn() << warningMessage << "." << std::endl;
+}
+
+
+static bool readPng(png_structp readPtr, png_infop infoPtr, PngInfo* outInfo,
+ std::string* outError) {
+ if (setjmp(png_jmpbuf(readPtr))) {
+ *outError = "failed reading png";
+ return false;
+ }
+
+ png_set_sig_bytes(readPtr, kPngSignatureSize);
+ png_read_info(readPtr, infoPtr);
+
+ int colorType, bitDepth, interlaceType, compressionType;
+ png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType,
+ &interlaceType, &compressionType, nullptr);
+
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_palette_to_rgb(readPtr);
+ }
+
+ if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
+ png_set_expand_gray_1_2_4_to_8(readPtr);
+ }
+
+ if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
+ png_set_tRNS_to_alpha(readPtr);
+ }
+
+ if (bitDepth == 16) {
+ png_set_strip_16(readPtr);
+ }
+
+ if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
+ png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
+ }
+
+ if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(readPtr);
+ }
+
+ png_set_interlace_handling(readPtr);
+ png_read_update_info(readPtr, infoPtr);
+
+ const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
+ outInfo->rows.resize(outInfo->height);
+ for (size_t i = 0; i < outInfo->height; i++) {
+ outInfo->rows[i] = new png_byte[rowBytes];
+ }
+
+ png_read_image(readPtr, outInfo->rows.data());
+ png_read_end(readPtr, infoPtr);
+ return true;
+}
+
+static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, void* data) {
+ size_t patchSize = inPatch->serializedSize();
+ void* newData = malloc(patchSize);
+ memcpy(newData, data, patchSize);
+ android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
+ outPatch->fileToDevice();
+ // deserialization is done in place, so outPatch == newData
+ assert(outPatch == newData);
+ assert(outPatch->numXDivs == inPatch->numXDivs);
+ assert(outPatch->numYDivs == inPatch->numYDivs);
+ assert(outPatch->paddingLeft == inPatch->paddingLeft);
+ assert(outPatch->paddingRight == inPatch->paddingRight);
+ assert(outPatch->paddingTop == inPatch->paddingTop);
+ assert(outPatch->paddingBottom == inPatch->paddingBottom);
+/* for (int i = 0; i < outPatch->numXDivs; i++) {
+ assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]);
+ }
+ for (int i = 0; i < outPatch->numYDivs; i++) {
+ assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]);
+ }
+ for (int i = 0; i < outPatch->numColors; i++) {
+ assert(outPatch->getColors()[i] == inPatch->getColors()[i]);
+ }*/
+ free(newData);
+}
+
+/*static void dump_image(int w, int h, const png_byte* const* rows, int color_type) {
+ int i, j, rr, gg, bb, aa;
+
+ int bpp;
+ if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
+ bpp = 1;
+ } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ bpp = 2;
+ } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+ // We use a padding byte even when there is no alpha
+ bpp = 4;
+ } else {
+ printf("Unknown color type %d.\n", color_type);
+ }
+
+ for (j = 0; j < h; j++) {
+ const png_byte* row = rows[j];
+ for (i = 0; i < w; i++) {
+ rr = row[0];
+ gg = row[1];
+ bb = row[2];
+ aa = row[3];
+ row += bpp;
+
+ if (i == 0) {
+ printf("Row %d:", j);
+ }
+ switch (bpp) {
+ case 1:
+ printf(" (%d)", rr);
+ break;
+ case 2:
+ printf(" (%d %d", rr, gg);
+ break;
+ case 3:
+ printf(" (%d %d %d)", rr, gg, bb);
+ break;
+ case 4:
+ printf(" (%d %d %d %d)", rr, gg, bb, aa);
+ break;
+ }
+ if (i == (w - 1)) {
+ printf("\n");
+ }
+ }
+ }
+}*/
+
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#define ABS(a) ((a)<0?-(a):(a))
+
+static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int grayscaleTolerance,
+ png_colorp rgbPalette, png_bytep alphaPalette,
+ int *paletteEntries, bool *hasTransparency, int *colorType,
+ png_bytepp outRows) {
+ int w = imageInfo.width;
+ int h = imageInfo.height;
+ int i, j, rr, gg, bb, aa, idx;
+ uint32_t colors[256], col;
+ int num_colors = 0;
+ int maxGrayDeviation = 0;
+
+ bool isOpaque = true;
+ bool isPalette = true;
+ bool isGrayscale = true;
+
+ // Scan the entire image and determine if:
+ // 1. Every pixel has R == G == B (grayscale)
+ // 2. Every pixel has A == 255 (opaque)
+ // 3. There are no more than 256 distinct RGBA colors
+
+ if (kDebug) {
+ printf("Initial image data:\n");
+ //dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA);
+ }
+
+ for (j = 0; j < h; j++) {
+ const png_byte* row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
+
+ int odev = maxGrayDeviation;
+ maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
+ if (maxGrayDeviation > odev) {
+ if (kDebug) {
+ printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
+ maxGrayDeviation, i, j, rr, gg, bb, aa);
+ }
+ }
+
+ // Check if image is really grayscale
+ if (isGrayscale) {
+ if (rr != gg || rr != bb) {
+ if (kDebug) {
+ printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
+ i, j, rr, gg, bb, aa);
+ }
+ isGrayscale = false;
+ }
+ }
+
+ // Check if image is really opaque
+ if (isOpaque) {
+ if (aa != 0xff) {
+ if (kDebug) {
+ printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
+ i, j, rr, gg, bb, aa);
+ }
+ isOpaque = false;
+ }
+ }
+
+ // Check if image is really <= 256 colors
+ if (isPalette) {
+ col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
+ bool match = false;
+ for (idx = 0; idx < num_colors; idx++) {
+ if (colors[idx] == col) {
+ match = true;
+ break;
+ }
+ }
+
+ // Write the palette index for the pixel to outRows optimistically
+ // We might overwrite it later if we decide to encode as gray or
+ // gray + alpha
+ *out++ = idx;
+ if (!match) {
+ if (num_colors == 256) {
+ if (kDebug) {
+ printf("Found 257th color at %d, %d\n", i, j);
+ }
+ isPalette = false;
+ } else {
+ colors[num_colors++] = col;
+ }
+ }
+ }
+ }
+ }
+
+ *paletteEntries = 0;
+ *hasTransparency = !isOpaque;
+ int bpp = isOpaque ? 3 : 4;
+ int paletteSize = w * h + bpp * num_colors;
+
+ if (kDebug) {
+ printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
+ printf("isOpaque = %s\n", isOpaque ? "true" : "false");
+ printf("isPalette = %s\n", isPalette ? "true" : "false");
+ printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
+ paletteSize, 2 * w * h, bpp * w * h);
+ printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance);
+ }
+
+ // Choose the best color type for the image.
+ // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
+ // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
+ // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
+ // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
+ // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
+ if (isGrayscale) {
+ if (isOpaque) {
+ *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
+ } else {
+ // Use a simple heuristic to determine whether using a palette will
+ // save space versus using gray + alpha for each pixel.
+ // This doesn't take into account chunk overhead, filtering, LZ
+ // compression, etc.
+ if (isPalette && (paletteSize < 2 * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
+ } else {
+ *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
+ }
+ }
+ } else if (isPalette && (paletteSize < bpp * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE;
+ } else {
+ if (maxGrayDeviation <= grayscaleTolerance) {
+ logger->note() << "forcing image to gray (max deviation = " << maxGrayDeviation
+ << ")."
+ << std::endl;
+ *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
+ } else {
+ *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+ }
+
+ // Perform postprocessing of the image or palette data based on the final
+ // color type chosen
+
+ if (*colorType == PNG_COLOR_TYPE_PALETTE) {
+ // Create separate RGB and Alpha palettes and set the number of colors
+ *paletteEntries = num_colors;
+
+ // Create the RGB and alpha palettes
+ for (int idx = 0; idx < num_colors; idx++) {
+ col = colors[idx];
+ rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff);
+ rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
+ rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff);
+ alphaPalette[idx] = (png_byte) (col & 0xff);
+ }
+ } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ // If the image is gray or gray + alpha, compact the pixels into outRows
+ for (j = 0; j < h; j++) {
+ const png_byte* row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
+
+ if (isGrayscale) {
+ *out++ = rr;
+ } else {
+ *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+ }
+ if (!isOpaque) {
+ *out++ = aa;
+ }
+ }
+ }
+ }
+}
+
+static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
+ int grayScaleTolerance, SourceLogger* logger, std::string* outError) {
+ if (setjmp(png_jmpbuf(writePtr))) {
+ *outError = "failed to write png";
+ return false;
+ }
+
+ uint32_t width, height;
+ int colorType, bitDepth, interlaceType, compressionType;
+
+ png_unknown_chunk unknowns[3];
+ unknowns[0].data = nullptr;
+ unknowns[1].data = nullptr;
+ unknowns[2].data = nullptr;
+
+ png_bytepp outRows = (png_bytepp) malloc((int) info->height * sizeof(png_bytep));
+ if (outRows == (png_bytepp) 0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
+ }
+ for (uint32_t i = 0; i < info->height; i++) {
+ outRows[i] = (png_bytep) malloc(2 * (int) info->width);
+ if (outRows[i] == (png_bytep) 0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
+ }
+ }
+
+ png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
+
+ if (kDebug) {
+ logger->note() << "writing image: w = " << info->width
+ << ", h = " << info->height
+ << std::endl;
+ }
+
+ png_color rgbPalette[256];
+ png_byte alphaPalette[256];
+ bool hasTransparency;
+ int paletteEntries;
+
+ analyze_image(logger, *info, grayScaleTolerance, rgbPalette, alphaPalette,
+ &paletteEntries, &hasTransparency, &colorType, outRows);
+
+ // If the image is a 9-patch, we need to preserve it as a ARGB file to make
+ // sure the pixels will not be pre-dithered/clamped until we decide they are
+ if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB ||
+ colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_PALETTE)) {
+ colorType = PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+
+ if (kDebug) {
+ switch (colorType) {
+ case PNG_COLOR_TYPE_PALETTE:
+ logger->note() << "has " << paletteEntries
+ << " colors" << (hasTransparency ? " (with alpha)" : "")
+ << ", using PNG_COLOR_TYPE_PALLETTE."
+ << std::endl;
+ break;
+ case PNG_COLOR_TYPE_GRAY:
+ logger->note() << "is opaque gray, using PNG_COLOR_TYPE_GRAY." << std::endl;
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ logger->note() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA." << std::endl;
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ logger->note() << "is opaque RGB, using PNG_COLOR_TYPE_RGB." << std::endl;
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ logger->note() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA." << std::endl;
+ break;
+ }
+ }
+
+ png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
+ if (hasTransparency) {
+ png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p) 0);
+ }
+ png_set_filter(writePtr, 0, PNG_NO_FILTERS);
+ } else {
+ png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
+ }
+
+ if (info->is9Patch) {
+ int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0);
+ int pIndex = info->haveLayoutBounds ? 2 : 1;
+ int bIndex = 1;
+ int oIndex = 0;
+
+ // Chunks ordered thusly because older platforms depend on the base 9 patch data being last
+ png_bytep chunkNames = info->haveLayoutBounds
+ ? (png_bytep)"npOl\0npLb\0npTc\0"
+ : (png_bytep)"npOl\0npTc";
+
+ // base 9 patch data
+ if (kDebug) {
+ logger->note() << "adding 9-patch info..." << std::endl;
+ }
+ strcpy((char*)unknowns[pIndex].name, "npTc");
+ unknowns[pIndex].data = (png_byte*) info->serialize9Patch();
+ unknowns[pIndex].size = info->info9Patch.serializedSize();
+ // TODO: remove the check below when everything works
+ checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data);
+
+ // automatically generated 9 patch outline data
+ int chunkSize = sizeof(png_uint_32) * 6;
+ strcpy((char*)unknowns[oIndex].name, "npOl");
+ unknowns[oIndex].data = (png_byte*) calloc(chunkSize, 1);
+ png_byte outputData[chunkSize];
+ memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
+ ((float*) outputData)[4] = info->outlineRadius;
+ ((png_uint_32*) outputData)[5] = info->outlineAlpha;
+ memcpy(unknowns[oIndex].data, &outputData, chunkSize);
+ unknowns[oIndex].size = chunkSize;
+
+ // optional optical inset / layout bounds data
+ if (info->haveLayoutBounds) {
+ int chunkSize = sizeof(png_uint_32) * 4;
+ strcpy((char*)unknowns[bIndex].name, "npLb");
+ unknowns[bIndex].data = (png_byte*) calloc(chunkSize, 1);
+ memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
+ unknowns[bIndex].size = chunkSize;
+ }
+
+ for (int i = 0; i < chunkCount; i++) {
+ unknowns[i].location = PNG_HAVE_PLTE;
+ }
+ png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS,
+ chunkNames, chunkCount);
+ png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
+
+#if PNG_LIBPNG_VER < 10600
+ // Deal with unknown chunk location bug in 1.5.x and earlier.
+ png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE);
+ if (info->haveLayoutBounds) {
+ png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE);
+ }
+#endif
+ }
+
+ png_write_info(writePtr, infoPtr);
+
+ png_bytepp rows;
+ if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
+ if (colorType == PNG_COLOR_TYPE_RGB) {
+ png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
+ }
+ rows = info->rows.data();
+ } else {
+ rows = outRows;
+ }
+ png_write_image(writePtr, rows);
+
+ if (kDebug) {
+ printf("Final image data:\n");
+ //dump_image(info->width, info->height, rows, colorType);
+ }
+
+ png_write_end(writePtr, infoPtr);
+
+ for (uint32_t i = 0; i < info->height; i++) {
+ free(outRows[i]);
+ }
+ free(outRows);
+ free(unknowns[0].data);
+ free(unknowns[1].data);
+ free(unknowns[2].data);
+
+ png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType,
+ &compressionType, nullptr);
+
+ if (kDebug) {
+ logger->note() << "image written: w = " << width << ", h = " << height
+ << ", d = " << bitDepth << ", colors = " << colorType
+ << ", inter = " << interlaceType << ", comp = " << compressionType
+ << std::endl;
+ }
+ return true;
+}
+
+constexpr uint32_t kColorWhite = 0xffffffffu;
+constexpr uint32_t kColorTick = 0xff000000u;
+constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu;
+
+enum class TickType {
+ kNone,
+ kTick,
+ kLayoutBounds,
+ kBoth
+};
+
+static TickType tickType(png_bytep p, bool transparent, const char** outError) {
+ png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+
+ if (transparent) {
+ if (p[3] == 0) {
+ return TickType::kNone;
+ }
+ if (color == kColorLayoutBoundsTick) {
+ return TickType::kLayoutBounds;
+ }
+ if (color == kColorTick) {
+ return TickType::kTick;
+ }
+
+ // Error cases
+ if (p[3] != 0xff) {
+ *outError = "Frame pixels must be either solid or transparent "
+ "(not intermediate alphas)";
+ return TickType::kNone;
+ }
+
+ if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+ *outError = "Ticks in transparent frame must be black or red";
+ }
+ return TickType::kTick;
+ }
+
+ if (p[3] != 0xFF) {
+ *outError = "White frame must be a solid color (no alpha)";
+ }
+ if (color == kColorWhite) {
+ return TickType::kNone;
+ }
+ if (color == kColorTick) {
+ return TickType::kTick;
+ }
+ if (color == kColorLayoutBoundsTick) {
+ return TickType::kLayoutBounds;
+ }
+
+ if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+ *outError = "Ticks in white frame must be black or red";
+ return TickType::kNone;
+ }
+ return TickType::kTick;
+}
+
+enum class TickState {
+ kStart,
+ kInside1,
+ kOutside1
+};
+
+static bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required,
+ int32_t* outLeft, int32_t* outRight, const char** outError,
+ uint8_t* outDivs, bool multipleAllowed) {
+ *outLeft = *outRight = -1;
+ TickState state = TickState::kStart;
+ bool found = false;
+
+ for (int i = 1; i < width - 1; i++) {
+ if (tickType(row+i*4, transparent, outError) == TickType::kTick) {
+ if (state == TickState::kStart ||
+ (state == TickState::kOutside1 && multipleAllowed)) {
+ *outLeft = i-1;
+ *outRight = width-2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
+ }
+ state = TickState::kInside1;
+ } else if (state == TickState::kOutside1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outLeft = i;
+ return false;
+ }
+ } else if (!*outError) {
+ if (state == TickState::kInside1) {
+ // We're done with this div. Move on to the next.
+ *outRight = i-1;
+ outRight += 2;
+ outLeft += 2;
+ state = TickState::kOutside1;
+ }
+ } else {
+ *outLeft = i;
+ return false;
+ }
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outLeft = -1;
+ return false;
+ }
+ return true;
+}
+
+static bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent,
+ bool required, int32_t* outTop, int32_t* outBottom,
+ const char** outError, uint8_t* outDivs, bool multipleAllowed) {
+ *outTop = *outBottom = -1;
+ TickState state = TickState::kStart;
+ bool found = false;
+
+ for (int i = 1; i < height - 1; i++) {
+ if (tickType(rows[i]+offset, transparent, outError) == TickType::kTick) {
+ if (state == TickState::kStart ||
+ (state == TickState::kOutside1 && multipleAllowed)) {
+ *outTop = i-1;
+ *outBottom = height-2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
+ }
+ state = TickState::kInside1;
+ } else if (state == TickState::kOutside1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outTop = i;
+ return false;
+ }
+ } else if (!*outError) {
+ if (state == TickState::kInside1) {
+ // We're done with this div. Move on to the next.
+ *outBottom = i-1;
+ outTop += 2;
+ outBottom += 2;
+ state = TickState::kOutside1;
+ }
+ } else {
+ *outTop = i;
+ return false;
+ }
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outTop = -1;
+ return false;
+ }
+ return true;
+}
+
+static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent,
+ bool /* required */, int32_t* outLeft,
+ int32_t* outRight, const char** outError) {
+ *outLeft = *outRight = 0;
+
+ // Look for left tick
+ if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) {
+ // Starting with a layout padding tick
+ int i = 1;
+ while (i < width - 1) {
+ (*outLeft)++;
+ i++;
+ if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+
+ // Look for right tick
+ if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) {
+ // Ending with a layout padding tick
+ int i = width - 2;
+ while (i > 1) {
+ (*outRight)++;
+ i--;
+ if (tickType(row+i*4, transparent, outError) != TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent,
+ bool /* required */, int32_t* outTop, int32_t* outBottom,
+ const char** outError) {
+ *outTop = *outBottom = 0;
+
+ // Look for top tick
+ if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) {
+ // Starting with a layout padding tick
+ int i = 1;
+ while (i < height - 1) {
+ (*outTop)++;
+ i++;
+ if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+
+ // Look for bottom tick
+ if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) {
+ // Ending with a layout padding tick
+ int i = height - 2;
+ while (i > 1) {
+ (*outBottom)++;
+ i--;
+ if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY,
+ int dX, int dY, int* outInset) {
+ uint8_t maxOpacity = 0;
+ int inset = 0;
+ *outInset = 0;
+ for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
+ png_byte* color = rows[y] + x * 4;
+ uint8_t opacity = color[3];
+ if (opacity > maxOpacity) {
+ maxOpacity = opacity;
+ *outInset = inset;
+ }
+ if (opacity == 0xff) return;
+ }
+}
+
+static uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) {
+ uint8_t maxAlpha = 0;
+ for (int x = startX; x < endX; x++) {
+ uint8_t alpha = (row + x * 4)[3];
+ if (alpha > maxAlpha) maxAlpha = alpha;
+ }
+ return maxAlpha;
+}
+
+static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) {
+ uint8_t maxAlpha = 0;
+ for (int y = startY; y < endY; y++) {
+ uint8_t alpha = (rows[y] + offsetX * 4)[3];
+ if (alpha > maxAlpha) maxAlpha = alpha;
+ }
+ return maxAlpha;
+}
+
+static void getOutline(PngInfo* image) {
+ int midX = image->width / 2;
+ int midY = image->height / 2;
+ int endX = image->width - 2;
+ int endY = image->height - 2;
+
+ // find left and right extent of nine patch content on center row
+ if (image->width > 4) {
+ findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
+ findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
+ &image->outlineInsetsRight);
+ } else {
+ image->outlineInsetsLeft = 0;
+ image->outlineInsetsRight = 0;
+ }
+
+ // find top and bottom extent of nine patch content on center column
+ if (image->height > 4) {
+ findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
+ findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
+ &image->outlineInsetsBottom);
+ } else {
+ image->outlineInsetsTop = 0;
+ image->outlineInsetsBottom = 0;
+ }
+
+ int innerStartX = 1 + image->outlineInsetsLeft;
+ int innerStartY = 1 + image->outlineInsetsTop;
+ int innerEndX = endX - image->outlineInsetsRight;
+ int innerEndY = endY - image->outlineInsetsBottom;
+ int innerMidX = (innerEndX + innerStartX) / 2;
+ int innerMidY = (innerEndY + innerStartY) / 2;
+
+ // assuming the image is a round rect, compute the radius by marching
+ // diagonally from the top left corner towards the center
+ image->outlineAlpha = std::max(
+ maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
+ maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
+
+ int diagonalInset = 0;
+ findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
+ &diagonalInset);
+
+ /* Determine source radius based upon inset:
+ * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
+ * sqrt(2) * r = sqrt(2) * i + r
+ * (sqrt(2) - 1) * r = sqrt(2) * i
+ * r = sqrt(2) / (sqrt(2) - 1) * i
+ */
+ image->outlineRadius = 3.4142f * diagonalInset;
+
+ if (kDebug) {
+ printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
+ image->outlineInsetsLeft,
+ image->outlineInsetsTop,
+ image->outlineInsetsRight,
+ image->outlineInsetsBottom,
+ image->outlineRadius,
+ image->outlineAlpha);
+ }
+}
+
+static uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) {
+ png_bytep color = rows[top] + left*4;
+
+ if (left > right || top > bottom) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
+
+ while (top <= bottom) {
+ for (int i = left; i <= right; i++) {
+ png_bytep p = rows[top]+i*4;
+ if (color[3] == 0) {
+ if (p[3] != 0) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
+ } else if (p[0] != color[0] || p[1] != color[1] ||
+ p[2] != color[2] || p[3] != color[3]) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
+ }
+ top++;
+ }
+
+ if (color[3] == 0) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
+ return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
+}
+
+static bool do9Patch(PngInfo* image, std::string* outError) {
+ image->is9Patch = true;
+
+ int W = image->width;
+ int H = image->height;
+ int i, j;
+
+ const int maxSizeXDivs = W * sizeof(int32_t);
+ const int maxSizeYDivs = H * sizeof(int32_t);
+ int32_t* xDivs = image->xDivs = new int32_t[W];
+ int32_t* yDivs = image->yDivs = new int32_t[H];
+ uint8_t numXDivs = 0;
+ uint8_t numYDivs = 0;
+
+ int8_t numColors;
+ int numRows;
+ int numCols;
+ int top;
+ int left;
+ int right;
+ int bottom;
+ memset(xDivs, -1, maxSizeXDivs);
+ memset(yDivs, -1, maxSizeYDivs);
+ image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1;
+ image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
+ image->layoutBoundsLeft = image->layoutBoundsRight = 0;
+ image->layoutBoundsTop = image->layoutBoundsBottom = 0;
+
+ png_bytep p = image->rows[0];
+ bool transparent = p[3] == 0;
+ bool hasColor = false;
+
+ const char* errorMsg = nullptr;
+ int errorPixel = -1;
+ const char* errorEdge = nullptr;
+
+ int colorIndex = 0;
+ std::vector<png_bytep> newRows;
+
+ // Validate size...
+ if (W < 3 || H < 3) {
+ errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
+ goto getout;
+ }
+
+ // Validate frame...
+ if (!transparent &&
+ (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
+ errorMsg = "Must have one-pixel frame that is either transparent or white";
+ goto getout;
+ }
+
+ // Find left and right of sizing areas...
+ if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs,
+ true)) {
+ errorPixel = xDivs[0];
+ errorEdge = "top";
+ goto getout;
+ }
+
+ // Find top and bottom of sizing areas...
+ if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1],
+ &errorMsg, &numYDivs, true)) {
+ errorPixel = yDivs[0];
+ errorEdge = "left";
+ goto getout;
+ }
+
+ // Copy patch size data into image...
+ image->info9Patch.numXDivs = numXDivs;
+ image->info9Patch.numYDivs = numYDivs;
+
+ // Find left and right of padding area...
+ if (!getHorizontalTicks(image->rows[H-1], W, transparent, false,
+ &image->info9Patch.paddingLeft, &image->info9Patch.paddingRight,
+ &errorMsg, nullptr, false)) {
+ errorPixel = image->info9Patch.paddingLeft;
+ errorEdge = "bottom";
+ goto getout;
+ }
+
+ // Find top and bottom of padding area...
+ if (!getVerticalTicks(image->rows.data(), (W-1)*4, H, transparent, false,
+ &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom,
+ &errorMsg, nullptr, false)) {
+ errorPixel = image->info9Patch.paddingTop;
+ errorEdge = "right";
+ goto getout;
+ }
+
+ // Find left and right of layout padding...
+ getHorizontalLayoutBoundsTicks(image->rows[H-1], W, transparent, false,
+ &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg);
+
+ getVerticalLayoutBoundsTicks(image->rows.data(), (W-1)*4, H, transparent, false,
+ &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg);
+
+ image->haveLayoutBounds = image->layoutBoundsLeft != 0
+ || image->layoutBoundsRight != 0
+ || image->layoutBoundsTop != 0
+ || image->layoutBoundsBottom != 0;
+
+ if (image->haveLayoutBounds) {
+ if (kDebug) {
+ printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
+ image->layoutBoundsRight, image->layoutBoundsBottom);
+ }
+ }
+
+ // use opacity of pixels to estimate the round rect outline
+ getOutline(image);
+
+ // If padding is not yet specified, take values from size.
+ if (image->info9Patch.paddingLeft < 0) {
+ image->info9Patch.paddingLeft = xDivs[0];
+ image->info9Patch.paddingRight = W - 2 - xDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
+ }
+ if (image->info9Patch.paddingTop < 0) {
+ image->info9Patch.paddingTop = yDivs[0];
+ image->info9Patch.paddingBottom = H - 2 - yDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
+ }
+
+/* if (kDebug) {
+ printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
+ xDivs[0], xDivs[1],
+ yDivs[0], yDivs[1]);
+ printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
+ image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
+ image->info9Patch.paddingTop, image->info9Patch.paddingBottom);
+ }*/
+
+ // Remove frame from image.
+ newRows.resize(H - 2);
+ for (i = 0; i < H - 2; i++) {
+ newRows[i] = image->rows[i + 1];
+ memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
+ }
+ image->rows.swap(newRows);
+
+ image->width -= 2;
+ W = image->width;
+ image->height -= 2;
+ H = image->height;
+
+ // Figure out the number of rows and columns in the N-patch
+ numCols = numXDivs + 1;
+ if (xDivs[0] == 0) { // Column 1 is strechable
+ numCols--;
+ }
+ if (xDivs[numXDivs - 1] == W) {
+ numCols--;
+ }
+ numRows = numYDivs + 1;
+ if (yDivs[0] == 0) { // Row 1 is strechable
+ numRows--;
+ }
+ if (yDivs[numYDivs - 1] == H) {
+ numRows--;
+ }
+
+ // Make sure the amount of rows and columns will fit in the number of
+ // colors we can use in the 9-patch format.
+ if (numRows * numCols > 0x7F) {
+ errorMsg = "Too many rows and columns in 9-patch perimeter";
+ goto getout;
+ }
+
+ numColors = numRows * numCols;
+ image->info9Patch.numColors = numColors;
+ image->colors.resize(numColors);
+
+ // Fill in color information for each patch.
+
+ uint32_t c;
+ top = 0;
+
+ // The first row always starts with the top being at y=0 and the bottom
+ // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
+ // the first row is stretchable along the Y axis, otherwise it is fixed.
+ // The last row always ends with the bottom being bitmap.height and the top
+ // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+ // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+ // the Y axis, otherwise it is fixed.
+ //
+ // The first and last columns are similarly treated with respect to the X
+ // axis.
+ //
+ // The above is to help explain some of the special casing that goes on the
+ // code below.
+
+ // The initial yDiv and whether the first row is considered stretchable or
+ // not depends on whether yDiv[0] was zero or not.
+ for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) {
+ if (j == numYDivs) {
+ bottom = H;
+ } else {
+ bottom = yDivs[j];
+ }
+ left = 0;
+ // The initial xDiv and whether the first column is considered
+ // stretchable or not depends on whether xDiv[0] was zero or not.
+ for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) {
+ if (i == numXDivs) {
+ right = W;
+ } else {
+ right = xDivs[i];
+ }
+ c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
+ image->colors[colorIndex++] = c;
+ if (kDebug) {
+ if (c != android::Res_png_9patch::NO_COLOR) {
+ hasColor = true;
+ }
+ }
+ left = right;
+ }
+ top = bottom;
+ }
+
+ assert(colorIndex == numColors);
+
+ if (kDebug && hasColor) {
+ for (i = 0; i < numColors; i++) {
+ if (i == 0) printf("Colors:\n");
+ printf(" #%08x", image->colors[i]);
+ if (i == numColors - 1) printf("\n");
+ }
+ }
+getout:
+ if (errorMsg) {
+ std::stringstream err;
+ err << "9-patch malformed: " << errorMsg;
+ if (!errorEdge) {
+ err << "." << std::endl;
+ if (errorPixel >= 0) {
+ err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge";
+ } else {
+ err << "Found along " << errorEdge << " edge";
+ }
+ }
+ *outError = err.str();
+ return false;
+ }
+ return true;
+}
+
+
+bool Png::process(const Source& source, std::istream& input, std::ostream& output,
+ const Options& options, std::string* outError) {
+ png_byte signature[kPngSignatureSize];
+
+ // Read the PNG signature first.
+ if (!input.read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
+ *outError = strerror(errno);
+ return false;
+ }
+
+ // If the PNG signature doesn't match, bail early.
+ if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+ *outError = "not a valid png file";
+ return false;
+ }
+
+ SourceLogger logger(source);
+ bool result = false;
+ png_structp readPtr = nullptr;
+ png_infop infoPtr = nullptr;
+ png_structp writePtr = nullptr;
+ png_infop writeInfoPtr = nullptr;
+ PngInfo pngInfo = {};
+
+ readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+ if (!readPtr) {
+ *outError = "failed to allocate read ptr";
+ goto bail;
+ }
+
+ infoPtr = png_create_info_struct(readPtr);
+ if (!infoPtr) {
+ *outError = "failed to allocate info ptr";
+ goto bail;
+ }
+
+ png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(&logger), nullptr, logWarning);
+
+ // Set the read function to read from std::istream.
+ png_set_read_fn(readPtr, (png_voidp)&input, readDataFromStream);
+
+ if (!readPng(readPtr, infoPtr, &pngInfo, outError)) {
+ goto bail;
+ }
+
+ if (util::stringEndsWith<char>(source.path, ".9.png")) {
+ if (!do9Patch(&pngInfo, outError)) {
+ goto bail;
+ }
+ }
+
+ writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+ if (!writePtr) {
+ *outError = "failed to allocate write ptr";
+ goto bail;
+ }
+
+ writeInfoPtr = png_create_info_struct(writePtr);
+ if (!writeInfoPtr) {
+ *outError = "failed to allocate write info ptr";
+ goto bail;
+ }
+
+ png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
+
+ // Set the write function to write to std::ostream.
+ png_set_write_fn(writePtr, (png_voidp)&output, writeDataToStream, flushDataToStream);
+
+ if (!writePng(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger,
+ outError)) {
+ goto bail;
+ }
+
+ result = true;
+bail:
+ if (readPtr) {
+ png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
+ }
+
+ if (writePtr) {
+ png_destroy_write_struct(&writePtr, &writeInfoPtr);
+ }
+ return result;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Png.h b/tools/aapt2/Png.h
new file mode 100644
index 0000000..bc80754
--- /dev/null
+++ b/tools/aapt2/Png.h
@@ -0,0 +1,38 @@
+/*
+ * 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_PNG_H
+#define AAPT_PNG_H
+
+#include "Source.h"
+
+#include <iostream>
+#include <string>
+
+namespace aapt {
+
+struct Png {
+ struct Options {
+ int grayScaleTolerance = 0;
+ };
+
+ bool process(const Source& source, std::istream& input, std::ostream& output,
+ const Options& options, std::string* outError);
+};
+
+} // namespace aapt
+
+#endif // AAPT_PNG_H
diff --git a/tools/aapt2/Util.cpp b/tools/aapt2/Util.cpp
index 8a4c88f..c2418eb 100644
--- a/tools/aapt2/Util.cpp
+++ b/tools/aapt2/Util.cpp
@@ -54,13 +54,6 @@ std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep) {
return splitAndTransform(str, sep, ::tolower);
}
-bool stringEndsWith(const StringPiece& str, const StringPiece& suffix) {
- if (str.size() < suffix.size()) {
- return false;
- }
- return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
-}
-
StringPiece16 trimWhitespace(const StringPiece16& str) {
if (str.size() == 0 || str.data() == nullptr) {
return str;
diff --git a/tools/aapt2/Util.h b/tools/aapt2/Util.h
index 510ed76..9f9707c 100644
--- a/tools/aapt2/Util.h
+++ b/tools/aapt2/Util.h
@@ -34,9 +34,26 @@ std::vector<std::string> split(const StringPiece& str, char sep);
std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep);
/**
+ * Returns true if the string starts with prefix.
+ */
+template <typename T>
+bool stringStartsWith(const BasicStringPiece<T>& str, const BasicStringPiece<T>& prefix) {
+ if (str.size() < prefix.size()) {
+ return false;
+ }
+ return str.substr(0, prefix.size()) == prefix;
+}
+
+/**
* Returns true if the string ends with suffix.
*/
-bool stringEndsWith(const StringPiece& str, const StringPiece& suffix);
+template <typename T>
+bool stringEndsWith(const BasicStringPiece<T>& str, const BasicStringPiece<T>& suffix) {
+ if (str.size() < suffix.size()) {
+ return false;
+ }
+ return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
+}
/**
* Creates a new StringPiece16 that points to a substring
diff --git a/tools/aapt2/Util_test.cpp b/tools/aapt2/Util_test.cpp
index 7dbe7e0..c16f6bb 100644
--- a/tools/aapt2/Util_test.cpp
+++ b/tools/aapt2/Util_test.cpp
@@ -31,7 +31,11 @@ TEST(UtilTest, TrimOnlyWhitespace) {
}
TEST(UtilTest, StringEndsWith) {
- EXPECT_TRUE(util::stringEndsWith("hello.xml", ".xml"));
+ EXPECT_TRUE(util::stringEndsWith<char>("hello.xml", ".xml"));
+}
+
+TEST(UtilTest, StringStartsWith) {
+ EXPECT_TRUE(util::stringStartsWith<char>("hello.xml", "he"));
}
TEST(UtilTest, StringBuilderWhitespaceRemoval) {
diff --git a/tools/aapt2/data/res/drawable/icon.png b/tools/aapt2/data/res/drawable/icon.png
new file mode 100644
index 0000000..4bff9b9
--- /dev/null
+++ b/tools/aapt2/data/res/drawable/icon.png
Binary files differ
diff --git a/tools/aapt2/data/res/drawable/test.9.png b/tools/aapt2/data/res/drawable/test.9.png
new file mode 100644
index 0000000..33daa11
--- /dev/null
+++ b/tools/aapt2/data/res/drawable/test.9.png
Binary files differ
diff --git a/tools/aapt2/data/res/layout/main.xml b/tools/aapt2/data/res/layout/main.xml
index e0b55c0..5160570 100644
--- a/tools/aapt2/data/res/layout/main.xml
+++ b/tools/aapt2/data/res/layout/main.xml
@@ -3,9 +3,13 @@
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
+
+ <variable name="user" type="com.android.User" />
+
<View xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/me"
android:layout_width="1dp"
+ android:text="@{user.name}"
android:layout_height="match_parent"
app:layout_width="false"
app:flags="complex|weak"
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 72ee343..df76bc9 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -518,7 +518,7 @@ def verify_intent_builder(clazz):
if m.name.startswith("create") and m.name.endswith("Intent"):
pass
else:
- error(clazz, m, "FW1", "Methods creating an Intent must be named createFooIntent()")
+ warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
def verify_helper_classes(clazz):